Tuesday, July 8, 2008

Using Static Maps and HTTP Geocoding to Location-enable Lonely Planet Mobile

We at Lonely Planet are thrilled to share some experiences from our latest project, http://m.lonelyplanet.com, a location-based world travel guide for mobile devices enabled by mashing the Google Static Maps API, the Google HTTP geocoder, and our new Content API. We’ve been publishing travel content for decades, (in case you’re not familiar, we’re the guides with blue spines in your local bookshop), but we’re excited because this is the first time we’ve been able to deliver our information in a mobile and spatially relevant way, allowing us to ask questions like “what should I explore today?” and “what’s around me worth exploring?”

Of course, to answer these questions, we need to know the lat/lng of both our user and the points of interest (POIs) around them. POIs aren't a problem, as our authors plot these on a map during their on-the-ground research. User locations are trickier. As our mobile site is a browser-based service, we don't (yet!) have the luxury of APIs for grabbing precise locations via GPS, wifi, or cell tower triangulation.

So, for the moment we’ve asked our users to self-select their location by entering it into a form field. Because typing on typical mobile keypad is painful, we wanted to save thumbs and offload parsing to a geocoder which could deal with the ambiguity of short location descriptions, various zip/post code formats etc. Enter Google's geocoder (HTTP flavour – no JavaScript available on most mobile browsers!). We can proxy this API from our server and allow users to enter a wide variety of location descriptors, from zip codes (90210) to tube stations (holborn) to street names (oxford street) to suburbs (footscray) and the geocoder will decipher it, geocode it, and return either a lat/lng or a list of options for disambiguation.

Once we know the user's lat/lng, we can assess which content is most spatially relevant to them by calculating proximity to POIs in our database. To this end, we use the POI proximity method from our Content API, in which we pass in the user's location and a radius in kilometers, returning POIs within range in their order to proximity. This service uses the Haversine formula, a tip we picked up from Pamela a couple of months ago.

Once we’ve got the POIs, the next step is plotting them (and the user’s location) on a map. Since ours is an application for a baseline of devices with limited capabilities, we don't have the luxury of Flash, AJAX, or JavaScript. Enter the Google Static Maps API. By passing in our geocodes, a set of parameters for labeling markers, and a map height / width (calculated per device by querying screen sizes from WURFL) the Static Maps API produces for us an image displaying a marker for the user's location and for each of the POIs.

To help our users explore further, we augmented the map with a lightweight navigation bar (north, south, east, west, zoom in, zoom out) so that users can pan around the map to surrounding areas. This little navigation bar uses a bit of code we thought we’d share:

Let's say we have a static map of width w pixels, height h pixels, centered at (lat,lng) and zoom level z.

$map_link = "http://maps.google.com/staticmap?key={$key}&size={$w}x{$h}¢er={$lat},{$lng}&zoom={$z}";

Then, we can generate north, south, east, and west 'panning' links as follows (this example assumes the existence of a mercator projection class with standard xToLng, yToLat, latToY, lngToX routines):

// a mercator object
$mercator = new mercator();

// we'll pan 1/2 the map height / width in each go
 
// y coordinate of center lat
$y = $mercator->latToY($lat);

// subtract (north) or add (south) half the height, then turn back into a latitude
$north = $mercator->yToLat($y - $h/2, $z);
$south = $mercator->yToLat($y + $h/2, $z);

// x coordinate of center lng
$x = $mercator->lngToX($lng);

// subtract (west) or add (east) half the width, then turn back into a longitude
$east = $mercator->xToLng($x + $w/2, $z);
$west = $mercator->xToLng($x - $w/2, $z);

So that our north, south, east, west links are:

$north = "http://maps.google.com/staticmap?key={$key}&size={$w}x{$h}¢er={$north},{$lng}&zoom={$z}";
$south = "http://maps.google.com/staticmap?key={$key}&size={$w}x{$h}¢er={$south},{$lng}&zoom={$z}";
$east = "http://maps.google.com/staticmap?key={$key}&size={$w}x{$h}¢er={$lat},{$east}&zoom={$z}";
$west = "http://maps.google.com/staticmap?key={$key}&size={$w}x{$h}¢er={$lat},{$west}&zoom={$z}";

To see the site in action, go to http://m.lonelyplanet.com in your mobile browser, select the "What's around me?" feature and enter your location or a place you're planning to travel. Make sure to send us feedback - as this is our first location-based service there are plenty of things we're learning as we go. At the moment we're experimenting with additional ways of enhancing the accuracy of the user's location - like everyone else, we're excited about the location capabilities available on the Nokia, Android and iPhone platforms and are thinking through services which could make best use of those.

As you've seen from this post, our mobile site is really just a mash-up of our own Lonely Planet Content API and the Google geo API's. The Content API is available through our newly launched developer program, so if you head over to developer.lonelyplanet.com and join our developer community, you can build the site yourself! The only thing we ask (actually, 2 things) is that you remain non-commercial and that you don’t openly compete with us. So, sign up! We’d love to see what you can create using our content, Google’s geo services, and any other API you choose (YouTube? Flickr? Twitter? Others?).

Ken Hoetmer – Digital Mapping Manager
Chris Boden - Head of Wireless & Innovation
Kris Nyunt – Senior Engineer, Wireless & Innovation
Follow us on Twitter @ lplabs