this is aaronland

We do it for the war stories, right?

filtr 0.3

Sketch (13).sketch

For a variety of reasons I've been using my N810 again, flashing the operating system and reinstalling stuff from scratch. The N810 does not make the Earth move, especially in a world of jail-broken iPhones. Its wireless chip is not terrifically strong, the GPS chip takes forever to establish a signal and the touch screen — let's be honest — makes you pine for Apple.

But it is a pretty reliable workhorse of a device if you approach it with a measure of tolerance (compassion?) and — especially in a world of jail-broken iPhones — there is an impressive wealth of software already written and/or ported to run with little hassle.

Which really means that installing anything not already in the application manager or with a handy click to install button on the maemo.org download pages is also mostly a pain in the ass but to be fair this is more a by-product of the Linux/Debian community than it is any individual architecture astronauts at Nokia. Thank god for Jörg Gronmayer, who maintains a searchable index of all the various packages and repositories that you need to configure to install anything.

The N810's operating system (Maemo) is pure open source as is 99% (100?) of the software that runs on it. I have no beef with commercial software or developers but that something like Maemo came out of an environment that otherwise embraces stuff like Symbian's security model is impressive and refreshing and deserves to be encouraged.

Just for kicks a few months ago I pulled down a copy of ModestMaps from trunk, no less, using Subversion and wouldn't you know it ws-compose.py ran out of the box. Just like that. It blew its brains out trying to do Atkinson filtering but otherwise generating maps and pinwins Just Worked !

Anyway, imagine my surprise when I discovered that someone had actually gone to the trouble of porting ImageMagick, of all things, to run on the N810. Which got me wondering : Would filtr run under Maemo? At its core it is just a glorified shell script that calls a whole bunch of other command line utlities. It took a little bit of wrangling on a Saturday afternoon to satisfy all the dependencie but, lo, it works!

Setting it up is a bit of a chore so here is a step-by-step guide :

  1. Install the OpenSSH packages that come (listed in the application manager) with the N810 by default, or at least by default with the most recent OS update.
  2. Install the gainroot application. It's just easier and faster than the alternatives.
    Actually, I am less convinced that gainroot — or the root shell at all — is even useful. Everything in Maemo is wrapped in the iron hug of sudo so you are probably better off just logging in (via ssh) as root once (yes, root) and adding the ability to su up to root to the /etc/sudoers file. At that point, go ahead and update your sshd_config file to disallow logins as root, if that creeps you out (it should).
    The only caveat if you do disable root login is that you will hold yourself at gun point every time you edit the /etc/sudoers file. If you are sloppy, like me, and make a mistake your N810 will continue to work in its current state but little things like the application manager will be permanently hosed until you reflash the device (because sudo will stop working and logging in as root, in order to fix its config file, is disabled).
    This is the Unix permissions model doing exactly what it was designed to do, by the way. Given that there is no other way to log in to the device as root — say, some obscure (but documented, please) keyboard dance that could be performed at startup (but not without first checking that the default root password had been reset) — then you're left with one of two bad choices : do exactly what I did (or just disable root login altogether) and hope for the best or just leave root logins enabled. And hope for the best.
    All things considered, I prefer the latter. I had previously written something about making the sshd_config file writable by the default user but, with a moment's reflection, that proved to be little more than window dressing : You can edit the file to your heart's content but without the ability to restart sshd (which requires that you are root after all) it's pretty much moot. It's true that you could just restart the device in order to reflect the changes but assuming that you've chosen decent passwords, the ability to log in doesn't really give a person much but it is does provide a way for you to stop stabbing yourself in the face. You also won't give every other application running as user user the ability to silently modify your sshd_config file.
    So, with all that in mind, moving right along...
  3. Install the wifiinfo application so you can figure out what your IP address is. That's right, the N810 has no ifconfig command. ifconfig is also available but it won't be in your default PATH so choose your poison...
  4. Install Python which is a total nuisance but basically involves ssh-ing to your N810 as root (see above) and then following the instructions. For whatever reason there is no standard package for Python even though all you end up doing is typing to commands in a shell. Once you get over the bad taste in your mouth it's pretty straightforward.
  5. While you're there you might as well install the Python GPS bindings, since they don't come built-in and you're going to be running apt-get, anyway.
  6. Install ImageMagick. If you've ever had to do this before you will perhaps find some cold comfort knowing that this is actually the easiest part of the process.
  7. Finally, pull down the most recent version of filtr or at least version 0.3 and higher. filtr is just a shell script and it tries to be smart about knowing where the things it needs to run are located so you should be able to simply uncompress the package and run it as-is.

The big difference between versions 0.23 and 0.3 is that two of the dependencies, written in C, have been replaced with Python versions. Specifically : jhead has been replaced by a short script that uses Gheorghe Milas' jpeg.py to move EXIF data from the source file to the filtr-ed version and md5sum with Nick Craig-Wood's port.

This probably means that filtr itself will be that much slower but, really, who cares and it means that the program will run on more hardware than I have the time or energy to suffer compiling jhead for. Despite the glorious madness of tools like PAMP (or a PHP port for Maemo) Python continues to be the thing that finds its way on to just about every computer, large or small. This probably also means that version 0.31 will check for the faster C applications and then fall back to the Python ones.

Sketch (2).sketch

That is all. It's not particularly fast, by any means. But it works and that is good and it opens up some interesting possibilities. For example, if I installed the py-inotify libraries (I just did) I could send via Bluetooth or whatever a day's worth of (geotagged) photos to a special directory before going to bed. That would trigger a process to filtr the photos, plot them on a map with ws-pinwin and then use the Moo API to generate postcards. Then wake up in the morning to complete the process (read : enter my credit card number). I'm not actually sure that poor little Python could do all of that under Maemo without completing melting down but, done with a little care and hand-holding, it's definitely possible.

Or something like that. Meanwhile : filtr 0.3

FireDopplGängEaglr

See also : Dan's Twitter API updates, FireEagle and new Flickr API fun for a variation on the theme and a little more detail on some of the Flickr API methods mentioned below.

First, there was filtr but that's another story entirely. The point being that I gave up carrying around a capital-C camera a few years ago choosing instead to make do with cameraphones and the availability of cheap, unlimited data-plans in the U.S.

I am mostly lazy and can't really be bothered to shuttle photos around from one device to another only to move them again to the giant device in the sky called Flickr. Before filtr I relied on the upload by email feature to snag a photo and quickly share it with the future-past but the desire to touch up — or filter — the photos before upload meant that I needed to write my own service to accept, process and then finally upload pictures to Flickr using the API.

ph:camera=n82

Which is what I want to talk about. Sort of.

The advantage of an intermediary service is that you can configure it to use the photographs it receives as triggers for other services. In my case this has always meant tags and geotags. Sometimes it is easy : It's pretty clear that I can tag anything that comes through with cameraphone. Sometimes it is possible : I can query a photo's EXIF data for a model number and assign an appropriate machinetag, say ph:camera=n82. Other times, it's just plain wrong or so not right enough as to be useless : If I hardcode the tag san francisco for all my uploads it will probably be right unless I happen to go out of town for the weekend or otherwise forget to update the tools.

Remember: Mostly lazy.

Applications like ZoneTag and Shozu have always had the ability to geotag your photos using a combination of GPS data and surrounding cell tower data. ZoneTag even tries to learn what tags are likely to map to a given spot but since neither application have any way to let me massage the actual photo they've never really fit the bill.

For a short time, I tried to use del.icio.us as a datastore for frequently photographed places. For example, if I took a photo of something posted to the Wall of Rant, in San Francisco's Mission District, I could add some magic text to the photo that would tell my intermediary service where the photo was taken. The text g:wallofrant would tell the computer-squirrels to ask the del.icio.us API for all my posts tags del:bookmark=geo, find the one titled wallofrant (Why the title instead of a tag? Who knows, it was a long time ago...) and then pull out the geo:lat and geo:lon tags and use them to call the Flickr geotagging API methods.

This eventually morphed in to sometime completely different called (drumroll) delicious maps but that's also another story and the lesson of trying to use this for photo uploads was (another drumroll, please) that I am mostly lazy and could never be bothered to add new photo places to del.icio.us let alone remember whether the special code was wall of rant or wallofrant. So, what to do instead?

You're thinking about FireEagle, aren't you? Me too, but back then it was still just a sparkle in Tom's eye so there wasn't any way for my computers to talk to his computers and make beautiful mus^H^H^H I mean, powerful synergies. Sad face. In the meantime, Dopplr launched a public API that allowed you to query for both your current location and where you said you'd be travelling on a specific date.

Angels sing.

So, I wrote Flickr::Upload::Dopplr which piggy-backs on top of the excellent Flickr::Upload and Net::Dopplr Perl libraries, written by Christophe Beauregard and Simon Wistow respectively. If you've ever used Flickr::Upload to upload a photo there isn't much difference except for some extra Dopplr specific arguments that you need to pass to the constuctor. Like this :

 use Flickr::Upload::Dopplr;

 my %dopplr_args = ('auth_token' => 'JONES!!!!',
                    'tagify' => 'delicious');

 my %flickr_args = ('key' => 'OH HAI',
                    'secret' => 'OH NOES',,
                    'dopplr' => \%dopplr_args);

 my $uploadr = Flickr::Upload::Dopplr->new(\%flickr_args);

 my $photo_id = $uploadr->upload('photo' => "/path/to/photo",
                                 'auth_token' => 'O RLY');
                    

Meanwhile, prior to actually sending the photo to Flickr the library asks the photo when it was taken by checking the DateTimeOriginal EXIF header and then asking the Dopplr API where you were on that date. (If there is no EXIF data then the current date is assumed. So it goes.) What comes back is a big bag of data about the city you said you were in including its name (tag!), latitude and longitude (geotag!) and a unique identifier in the Geonames database (machine tag!) Finally, since Dopplr uses different unique identifiers for places, the library tries to find a matching sister location in the Flickrverse using the flickr.places.find API method (profit!).

Because Dopplr deals in day-long chunks of time it can sometimes happen that the space-time tube will eat itself and your photos may end on the other side of the country. That's what happened to me shortly after the official launch of FireEagle and Dopplr happily told it I had a 09:00 AM flight to Austin even though a few short inches of snow in Dallas had paralyzed most of the air travel in and out of Texas that day.

Computers are sometimes dumb that way and I find that I am happier if I don't try to fight it.

The holy grail remains magic-style talk to the cloud GPS integration with cameras, to record location data, so it was a pleasant shock to wake up one morning a couple months ago and see that Nokia had released a freely available piece of software called Location Tagger. Nokia, and others, have been shipping phones with built-in GPS units for a while now but until recently they've suffered from battery issues and generally poor integration with other applications, notably the camera. Location Tagger does pretty much what it sounds like : It sits quietly in the background until the camera's shutter button is pressed and then it asks the sky for its location and writes latitude and longitude data to the photo's EXIF data.

The answer is yes. It means you can use (drum solo, please) Flickr::Upload::FireEagle to not only geotag your photos but, in turn, use your photos to update FireEagle itself.

Angels rock out.

It goes without saying that the dynamics between where a photo says you are, the offset in time engendered by a service that brokers your location and what you really mean get fuzzy and potentially complicated very quickly. So, this is how it works :

    It's a bit involved.

    The first thing that happens is the photo you're trying to upload is
    poked for EXIF data, specifically any GPS information and when it was
    taken (the using *DateTimeOrginal* field).

    If there is no date information, the current time is assumed.

    If there is GPS data then the date is tested to see if the photo was
    taken falls within an allowable window of time. By default, this is (1)
    hour from "right now". An alternate value may be set by passing an
    *offset_gps* argument, measured in seconds, to the *upload* method.

    If the GPS information was added recently enough then FireEagle is
    queried for your most recent location hierarchy. If the GPS information
    is more recent than the data stored in the hierarchy (the location with
    the "best guess" of being correct) then FireEagle is updated with the
    latitude and longitude recorded in the photo.

    Moving right along, whether or not we've just updated FireEagle the
    service is queried for your current location (again).

    Once the hierarchy has been retrieved, the next step is to try and
    retrieve a "context" node. Whereas when testing GPS information the
    "best guess" node is assumed this is not necessarily the case when
    trying to use FireEagle to add tags.

    The context node is determined by comparing the photo's date against the
    *located-at* (or date recorded) attribute for specific items in the
    FireEagle hierarchy. Since most cameras still don't record GPS
    information it is necessary to do some work to gues^H^H^H I mean infer
    how "close" you are to the last recorded location.

    For example, if it's been more than a couple of hours since you last
    updated FireEagle you might still be in the same neighbourhood but if
    it's been more than half a day chances are good that you're been on the
    move but are still in the same city.

    The following tests are applied :

    * First a "best guess" location is queried
        If it is present and its *located-at* date is less than or equal to
        an hour, it is the context node.

        An alternate value may be set by passing a *offset_fireeagle_exact*
        argument, measured in seconds, to the *upload* method.

    * Next a location of type "neighborhood" is queried
        If it is present and its *located-at* date is less than or equal to
        two hours, it is the context node.

        An alternate value may be set by passing a
        *offset_fireeagle_neighbourhood* (or neighborhood) argument,
        measured in seconds, to the *upload* method.

    * Next a location of type "locality" is queried
        If it is present and its *located-at* date is less than or equal to
        twelve hours, it is the context node.

        An alternate value may be set by passing a
        *offset_fireeagle_locality* argument, measured in seconds, to the
        *upload* method.

    * If none of those tests pass then...
        ...there is no context node.

    Assuming that a context node has been identified *and* there is GPS
    information stored in the photo, the *flickr.places.findByLatLon* method
    is called (passing the photo's latitude and longitude) to ensure that
    the (Flickr) places IDs for both the response and the context node
    match.

    If they *don't* match then the context node is destroyed and the
    following tags are added : places:PLACETYPE=PLACEID; woe:id=WOEID; the
    name of the location (formatted according to the object's "tagify"
    rules).

    On the other hand, if the context node is still around, after all that,
    then it is used to add tags.

    At a minimum a fireeagle:id=CONTEXTNODEID tag is added. If the place
    type for the context node is equal to or more precise than a
    neighbourhood, the neighbourhood's name is added as a tag. If the place
    type for the context node is equal to or more precise than a locality,
    the locality's name is added as a tag as well as fireeagle:id=ID,
    places:locality=PLACEID and woe:id=WOEID tags.

    We're almost done : Assuming a context node and no GPS information in
    the photo, the nodes latitude and longitude are calculated to use as
    arguments when calling the *flickr.photos.geo.setLocation* method.

    The coordinates are "calculated" because not every location in the
    FireEagle hierarchy has a centroid. If no centroid is present then the
    node's bounding box is used and the centroid is assumed to be the center
    of the box. The photo's "accuracy" (in Flickr terms) is determined
    according to the node's place type.

    Finally, the photo is uploaded (and geotagged if necessary).

    No really.
                    

Like Flickr::Upload::Dopplr, the FireEagle version simply defines a few extra arguments to the constructor and then hands all heavy lifting off to Simon Wistow's Net::FireEagle library.

<shameless_plug>Please for you to write an extension for the Flickr Uploadr that does the same thing. I am lazy and fear that I will never actually get around to doing it no matter how much I say I want to.</shameless_plug>

The next step is to work through the two libraries, find the common methods and move them into a base class. So far, the only name I've been able to come up with is Flickr::Upload::Locatify so I would welcome any better suggestions. Once that's done I'd like to work out how to structure things so that it is easy for developers to chain a series a libraries together to read and write location data across services.

For example, Flickr::Upload::FireDopplr will ... uh, well I'm still working through the possibilities. Ask Dopplr for your current city if the FireEagle/GPS magic returns nothing? Tell Dopplr to create a new trip if the location data inferred from a photo doesn't match your current city? All of the above?

bike

Something like that, anyway.