Using the Google Static Maps API and HTTP Geocoder to power Lonely Planet's
mobile travel guide
This post is part of the Who's @ Google I/O, a series of blog posts that give a
closer look at developers who'll be speaking or demoing at Google I/O. Today's post is a guest post written
by Ken Hoetmer of Lonely Planet.Lonely Planet has been using Google Geo
APIs since 2006 - you can currently find them in use on destination profiles at
lonelyplanet.com , in our
trip planner
application , in our hotel and hostel
booking engine , on
lonelyplanet.tv ,
and in our mobile site,
m.lonelyplanet.com . I could talk for hours about any of these sites,
but in preparation for
Google I/O and my talk at the Maps APIs and Mobile session, I'll spend
this post discussing our use of the
Google Static
Maps API and the
HTTP geocoding service on m.lonelyplanet.com.
Our mobile site's primary feature is highlighting points of interest (POIs) around you,
as selected by Lonely Planet. The site is browser based and targeted at a baseline of devices.
This accessibility is great for on the road, but because of this choice, we can't obtain
precise user locations via a location API. Instead, we've asked our users to self-select their
location by entering it into a free form text field when they first arrive at the site. This
location is then posted to our server, geocoded on the back end by forwarding the text to the
Google HTTP geocoding API, and then used to either set the user's location or return a list of
options for disambiguation.
Knowing the user's position, we then
forward the position and a radius in kilometers to our
Content API's POI proximity
method, returning a list of points within range, in order of proximity. Once we have the POIs,
we need to present them on a map, relative to the user's location. This is where the Google
Static Maps API comes in. We can't rely on the availability of Flash, JavaScript, and Ajax,
but the Static Maps API enables us to serve a JPEG map by simply providing our list of POI
geocodes, a few bits about labeling markers, and a height / width (which we calculate per
device by querying screen sizes from
WURFL) as query parameters to a URL. Below
the map we put a few links for switching the map between (road)map, satellite, hybrid, and
terrain base maps.
That gives us a
basic map, but what if the user wants to look a little farther to the north or east? To enable
this, we augmented the map with a lightweight navigation bar (north, south, east, west, zoom
in, zoom out), with links to new static maps that represent a pan or zoom action. Here's how
we generated the links:
Let's say our page has 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 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 pixel 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 pixel 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}";
Of
course if you're serving a page knowing only a list of points and their geocodes, then you
don't have a zoom level value for calculating the map links. Thankfully, mercator projection
implementations often offer a 'getBoundsZoomLevel(bounds)' function, which serves this purpose
(create your bounds by finding the minimum and maximum latitudes and longitudes of your list
of geocodes). If your implementation doesn't provide this function, it's not complicated to
write, but I'll leave that to the reader (hint: find difference in x and y values at various
zoom levels and compare these to your map width and height).
As
mentioned earlier, I'll be joining Susannah Raub and Aaron Jacobs to delve deeper into maps
and mobile devices at
Google I/O. In addition, Matthew Cashmore (Lonely Planet Ecosystems
Manager) and I will be meeting developers and demoing our apps in the
Developer
Sandbox on Thursday, May 28. We'd love to meet you and we'll be easy to find -
simply follow the noise to the
jovial Welshman.
Guest Post by Ken Hoetmer,
Lonely Planet