2008-12-06

No-interface interactive maps

I've wanted to do this kind of hack for ages, since social software became commonplace on the web. Being both very bad at geography (even worse so, US geography, having moved here only just recently), and rather well versed with mucking about with other web developers' user interfaces, I finally took to it yesterday night (I've admittedly done a similar Greasemonkey hack for Facebook at some time, but it wasn't nearly as fun, and has most likely rotted since then, anyway).

The idea is basic: you tend to see location names (cities, addresses and the like) associated with users or events strewn out all over the place. Those don't tell me a whole lot unless I've been there, live near them, or similar. Maps do, though -- at least if I have some reference point I know to relate them to. For a site like OkCupid which is all about getting to know people (or yourself, but that's a different story), the natural point is of course home. And these days, Google Maps offers some really nifty geocoding and travel directions APIs. Add some Greasemonkey magic to the mix, and voilà -- a context sensitive map section which shows you where people are, in relation to yourself, as you hover a location name. It's quite snappy, and completely effortless from the user's point of view; no typing, clicking, dragging or otherwise involved:

OkCupid Maps This really is my favourite kind of interface: it doesn't have a learning curve, takes no clicking about, and has intuitive discovery properties; hover a place, and the same place shows up, name and all, in the map view.

It can of course use some additional polish like drag-a-corner-to-resize, a get-out-of-my-face icon to turn it off and the like, but suddenly it's a whole little software project rather than a one-nighter. That said, I think I like it well enough to want it across the entire web. Add those, a little toolbar to drag it around with and whip up geo and adr micro formats detection for it, and all of a sudden it's a neat all-round tool.

To finish off, closing the circle of the original OkCupid hack, replace that one with one that just sweeps OkCupids pages and converts its place name HTML to the adr micro format -- and the two tools snap together forming a stigmergy of cooperating Greasemonkey scripts. (It's a development pattern I really enjoy, for the unanticipated emergent bonus effects it tends to bring.)

Anyway, I thought I'd shine some light in the rest of the post on how I put it together, as it's painful doing things with Google Maps from Greasemonkey (can't inject the Maps API comfily, as its loader mucks about with document.write, which only works as intended for inline script tags, as opposed to dynamically injected code), and since my technique is rather easy to borrow.

The map area and geocoding is all done in a little iframe I load from here, starting it off with some URL encoded query parameters like at=Mountain View, California, United States (to center the initial map and put a little blob there), country=United States (which helps the geocoder, IIRC), nav=2 (if we want a medium-size zoom/pan control) and z=9 for a useful default zoom level. (The latter adapts to an appropriate scale when we ask for routes.) It will fill the whole page, or iframe, so we don't need to state any pixel dimensions.

So how do we tell it to look up places for us? This is where it's getting fun. Using window.postMessage on the iframe's window object, we send the geocoder its instructions, and using window.addEventListener("message", listener, false), we listen to whatever it figures out for us (Firefox 3+, and other modern web browsers).

For convenience, I went for a rather minimal messaging API; send it a newline-separated list of locations, optionally preceded by a "focus" (to move the view and blob there) or "route" line (to try drawing a route between the first and last address). All places in the list get geocoded, and the return value passed is a JSON structure {"coords":{"place name":[long, lat], "other place name":[long, lat], ...}, "took":t, "type":"geocode"} where t is the lookup time in milliseconds. Route requests are in addition populated with "distance" and "time" properties, either containing a "html" property (for a human readable form), and a "meters" or "seconds" property respectively.

And that's really all you need to use it.

Wanting it somewhat speedy (as the iframe only has page longevity, and thus won't learn and retain address to lat/long mappings over time), I opted to add a little local cache of places already looked up in the past with globalStorage (Firefox 2+, non-standard). Quick and dirty, but functional, it puts places in a little JSON tree indexed on country, region, city, and so on, however deeply the hierarchy goes. As OkCupid's place names are all on a tidy machine-readable comma-separated list format with consistent capitalization this is a rather small representation; place names in the wild might find it less useful, if still not worthless. As your web browser's IP might get blocked from using the Google Maps geocoder if you use it too recklessly (>15,000 requests per day -- but still), the persistence might be good to have there, just in case.

As usual, feel free to adopt, embrace, extend and hack about madly with it; code wants love too. The full source (and script, docs, et cetera) of the Greasemonkey script is on userscripts.org, and the Google Maps part is linked above. Happy hacking!
blog comments powered by Disqus