Hacking Maps with the Google Maps API
August 10, 2005
Google didn't get around to releasing the documentation of its maps API until relatively recently, but many developers were experimenting with the service as soon as it went into public beta. I built the Irving, Texas (a suburb of Dallas) parks demo before the API was publicly documented. In addition to providing a useful service, Google also reinvigorated web user interface expectations, as has been widely noted. In particular, being able to fluidly drag and zoom maps from a web browser is an engrossing feature for the user.
Google Maps API
The Google Maps API is based on JavaScript, which makes life pretty easy, since you
don't
need to worry about installing software. You can directly run JavaScript in just about
any
web browser. Knowledge of XML and XSL helps to build a rich user interface, but it's
not
required. Google Maps currently supports Firefox 0.8+, Mozilla 1.4+, IE 5.5+, Safari
1.2+,
Netscape 7.1+, Opera 7+, but E 5.0 is not supported. You can use the global method
GBrowserIsCompatible()
to check compatibility and show user-friendly error
message for non compatible browsers.
There are several limitations to the Maps API:
- It doesn't provide a geocoding service; that is, you have to provide it with longitude and latitude. This means you need to rely on third-party geocoding tools to get longitude and latitude for an address.
- It doesn't include a routing or driving directions service.
- The Google Maps API key is restricted to generate maps only for URLs (website or a local host). You need to have a web server even for development.
- The key you receive is only valid for a single directory on your web site.
- It can be used for commercial purposes, but it should be available to end users for free.
- It can be used on a site that is password protected, but consumers should be able to sign up for a password without charge.
- Usage is not necessarily restricted, but if your site is getting more than 50,000 hits per day, you'd better contact Google.
Yahoo also has a mapping API, with some significant differences. The Google Maps API requires long-and-lat, while Yahoo offers a geocoding facility.
Google maps API | Yahoo maps API | |
---|---|---|
Accepts longitude and latitude only, no geocoding | Provides geocoding | |
Maps can be generated on, or for, any site | Maps will be generated only on a Yahoo site | |
JavaScript | XML (based on geoRSS 2.0) API | |
Ajax support | No Ajax support | |
Zoom in, Zoom out, move by mouse | Zoom in, Zoom out, move by clicking links | |
Can use for commercial purposes, but should be freely available to end user | Can use for commercial purposes, but should obtain written permission | |
Can add plain text or HTML or XML with XSL or show blowup of the map in a display window | Can add custom link, and/or custom image, or can display HTML in an IFRAME inside display window | |
Usage is not restricted up to a reasonable upper bound | No usage restriction | |
Currently in beta | Not beta, stable release |
Developing a Map Application
To develop a Google map application, you need to go through the following five steps:
- Find a geocoder tool which gives the long-and-lat from an address.
- Identify the state or city or street you want to display by default (i.e., the starting point) and the default zoom level.
- Identify the locations you want to mark on the map.
- Decide what icon to represent the location on the map.
- Decide what information to display when the icon is clicked.
In order to demonstrate taking these five steps, we're going to work through the app I built, which maps the public parks in Irving, Texas.
1. Finding a Geocoder Tool
There are several free geocoder tools which provide the long-and-lat of an address. In order to integrate one of these services into a web app, you can use the Perl module Geo::Coder::US. As its name implies, it only works for US addresses. But it's free, and you can use Perl and JavaScript together in a web app. It's up to you to decide whether you need static data or dynamic data. But to keep things simple, we're going to use static data here. In fact, let's use geocoder.us/, which is based on Geo::Coder::US.
2. Identifying the Default Area
It's pretty easy to identify the default area, since it depends on what your mapping app is meant to accomplish. In our example, the default area that needs to be displayed is Irving, Texas. So we need to figure out the long-and-lat for Irving. Geocoder tools may not provide longitude and latitude based only on the city and state. You may need to provide a valid street address in the city; however, some tools will provide long-and-lat for a ZIP code. Once you get the right coordinates, you can zoom the map to display the whole city.
So we'll use the address 1698 Rochelle Blvd, Irving, TX, 75039, which has coordinates Longitude: -96.926791, Latitude: 32.863917. Now it's almost time to start using the Maps API; but first we have to get access.
Getting an API Key
Unfortunately the key is limited to the website's particular directory that you tell
Google
about, and you can only register the key to valid website addresses. Even though this
is a
JavaScript API, you cannot simply develop and test an app based on it without a web
server.
To get a key you need to provide the URL, and that key only works with requests from
that
URL. It will not even allow the requests from another directory of the same site.
If you
tell Google you want a key for http://www.gmap.example/mysite
, your key is
valid for http://www.gmap.example/mysite/
,
http://www.gmap.example/mysite/mypage.html
, and
http://www.gmap.example/mysite/page?arg=foo
. But it's invalid for
http://gmap.example/mysite/mypage.html
,
http://differenthost.gmap.example/mysite
, and
http://www.gmap.example/mysite/mysubdir/page.html
.
Visit Google's sign-up page and get a key for your website. You need to have a Google account to get the key. The whole API is available through one JavaScript file (which may be using other JavaScript files). Include the following file in a SCRIPT tag with the key you generated (assume the key is "abc123"):
<script src="http://maps.google.com/maps? file=api&v=1&key=abc123" type="text/javascript"></script>
Creating Display Points on the Map
The GMap
class represents the map, and it has the methods to center and to
zoom the map. Also it lets you add the move and zoom controls on the map. With this
class
you can default the map to show a particular area -- in our case, the city of Irving.
This
map can be embedded in a div
element and you can control the size of the map by
setting the height and width of that div
element:
<div id="map" style="width: 800px; height: 500px;"></div>
Create the point with the long-and-lat of the Irving address we got from the geocoder:
var point = new GPoint(-96.926791,32.863917);
Next we create the instance of GMap
by passing the div
element we
just made:
var map = new GMap(document.getElementById("map"));
We want the move and zoom controls, so let's add them too, but we have some choices.
We can
add the large pan/zoom control used on Google Maps (GLargeMapControl
), the
smaller pan/zoom control used on Google Local (GSmallMapControl
), or the small
zoom control, with no panning controls, used in the small map blowup windows
(GSmallZoomControl
).
Let's add one of these controls to our map:
map.addControl(new GLargeMapControl());
We also want to add the control which allows users to toggle between map and satellite view:
map.addControl(new GMapTypeControl());
Finally we set the default or initial starting point of the map, as well as zoom to
the
right level of magnification, which you can discover through trial-and-error. We use
the
point
we created earlier.
Map.centerAndZoom(point, 6);
What have we done so far? We've made an 800 X 500 map, which will display Irving, Texas. (See Listing 1 for source code so far.)
3. Identifying the Overlay Locations
Once you have the map in place with a default area, it's time to identify the locations to display. If your app needs dynamic locations, then you need to write code to get them on the fly: take user input, convert to long-and-lat with a geocoder service, and so on. For example, HousingMaps gets the locations of properties for sale or rent from Craigslist on the fly, and converts and displays them. In our example, we want to identify city parks in Irving, Texas. The best place to find information about those parks is from the City of Irving's website. Once you have the park addresses in hand, you have to use that handy geocoder tool to get their longs-and-lats, as we already did earlier.
Let's get the long-and-lat for the first park, the Austin Recreation Center, which is at 825 East Union Bower Road, Irving, Texas 75061. The Geocoder returns -96.936574/32.822129.
Let's make another GPoint
:
var point = new GPoint(parseFloat(-96.936574),parseFloat(32.822129));
Overlays are objects on the map that are tied to longitude and latitude coordinates,
so
they move when you drag/zoom the map around, and when you toggle between Map and Satellite
modes. To overlay the location on the map you need to create a GMarker
with
longitude and latitude information. The marker is a type of map overlay that shows
an icon
at a single point on the map. The constructor takes the point at which it should be
displayed and an optional instance of GIcon
.
Let's create the marker with this point:
var marker = new GMarker(point);
Add the marker to the map in order to display this location on the map.
map.addOverlay(marker);
Repeat the process for all parks manually, or you can load the parks information from
an
XML file and use the GXmlHttp
class (which creates cross-browser
XMLHttpRequest
instances) to load on startup.
Let's say you have all parks information in parks.xml and it looks like this:
<parks> <park><point lng="-96.936574" lat="32.822129"/></park> <park><point lng="-96.9330679" lat="32.871815"/></park> </parks>
You can create an XMLHttpRequest
via the GXmlHttp.create()
method:
var request = GXmlHttp.create();
You can load parks.xml
:
request.open("GET", "parks.xml", true); request.onreadystatechange = function() { if (request.readyState == 4) { var xmlDoc = request.responseXML;
Now let's extract the point elements out of the XML document:
var points = xmlDoc.documentElement.getElementsByTagName("point");
We can loop through the points to get the long-and-lat of the each point and create
a
GPoint
:
for (var i = 0; i < points.length; i++) { var point = new GPoint(parseFloat(points[i].getgetAttribute("lng")), parseFloat(points[i].getAttribute("lat"))); }
Now we create the marker and add it to the map inside the loop.
var marker = new GMarker(point); map.addOverlay(marker);
(For complete source code for this step, see Listing2.)
4. Display Custom Icons for Map Overlays
Based on the requirements, you can decide what icon to show at each location. In our
example, you can show a different icon for each park, or a different icon for parks
that
have pool facilities, or you can display the default icon for all locations. To display
the
default icon, there is nothing to do. The statement var marker = new
GMarker(point)
will create the marker with a default icon. If you decide to use the
default icon for all locations, you can skip this step and directly go to the next
step.
Again, add icon information in the same XML file and you can loop through the icon
information to add different icons for different parks.
Let's rewrite our
parks.xml
file with icon information:
<parks> <park> <point lng="-96.936574" lat="32.822129"/> <icon image="green.png" class="local"/> </park> <park> <point lng="-96.9330679" lat="32.871815"/> <icon image="yellow.png" class="local"/> </park> </parks>
Add the logic of parsing the icon information to the same section where you parsed the point information:
var iconImage = xmlDoc.documentElement.getElementsByTagName("icon");
Create the base icon with GIcon
class and add the size, shadow, and shadow
size:
var baseIcon = new GIcon(); baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png"; baseIcon.iconSize = new GSize(20, 34); baseIcon.shadowSize = new GSize(37, 34);
Now create the Icon with base icon:
var icon = new GIcon(baseIcon);
Set the custom image you wanted to display to this icon:
Icon.image = iconImage[i].getAttribute("src");
In the previous step we created the marker with a default icon. Now we have a customized icon, so pass the icon to the marker constructor, along with a point:
var marker = new GMarker(point, icon); Map.addOverlay(marker);
For complete source code for this step, see Listing 3.
5. Displaying Icon Information on Click
The last step is to display the content once a user clicks on the marker. In our example, if a user clicks on the marker, we'll show the park name, address, photo, and URL for more info.
The API provides several options for displaying content in an info window:
openInfoWindow(htmlElem)
: opens an info window with the given HTML content over this marker.htmlElem
should be an HTML DOM element.openInfoWindowHtml(htmlStr)
: takes an HTML string rather than an HTML DOM elementopenInfoWindowXslt(xmlElem, xsltUri)
: takes an XML element and the URI of an XSLT document to produce the content of the info window. The first time a URI is given, it is retrieved withGXmlHttp
and subsequently cached.
The API also provides support for different events: click
, which is triggered
when the user clicks on this marker; infowindowopen
, which is triggered when
the info window is opened above this marker; and infowindowclose
, which is
triggered when the info window above this marker is closed.
You can add dynamic elements to your application using event listeners. An object
exports
several named events, and your application can listen to those events using the static
methods GEvent.addListener
or GEvent.bind
. The former takes a
marker as its first argument, the event as the second argument, and a function as
the third
argument. In the function you can call one of the marker's info window methods.
This will display a blowup of the map over this marker. You can specify the zoom level, otherwise it uses a default zoom level of 1.
GEvent.addListener(marker, "click", function() { marker.showMapBlowup(); });
These add plain text or, alternately, HTML:
GEvent.addListener(marker, "click", function() { marker.openInfoWindow("Park"); });
GEvent.addListener(marker, "click", function() { marker.openInfoWindowHtml("<b>Park</b>"); });
Let's add the final information about each park to our parks file:
<parks> <park><point lng="-96.936574" lat="32.822129"/> <icon image="green.png" class="local" /> <info> <FullName>Austin Recreation Center</FullName> <url>http://www.ci.irving.tx.us/parks_and_recreation/ facilities/austin_recreation_center/index.asp</url> <address>825 East Union Bower Road</address> <address2>Irving, Texas 75061</address2> <phone>(972)721-2659</phone> <img>http://www.ci.irving.tx.us/parks_and_recreation/ facilities/austin_recreation_center/images/image7.gif</img> </info> </park> <park><point lng="-96.9330679" lat="32.871815"/> <icon image="yellow.png" class="local" /> <info> <FullName>Birds Fort Trail Park</FullName> <url>http://www.ci.irving.tx.us/parks_and_recreation/ facilities/birds_fort_trail/index.asp</url> <address>5756 N. O'Connor Blvd</address> <address2>Irving, Texas 75039</address2> <phone>(972)721-2659</phone> <img>http://www.ci.irving.tx.us/parks_and_recreation/ facilities/birds_fort_trail/images/index2.jpg</img> </info> </park> </parks>
Listing 4 contains the source of the XSLT we use to display the park information taken from the XML above.
Again, we parse the XML file for info elements inside the loop and add the event listener with an info element and parks.xsl file.
var info = xmlDoc.documentElement.getElementsByTagName("info"); GEvent.addListener(marker, "click", function() { marker.openInfoWindowXslt(info[i], "parks.xsl"); });
(See Listing 5 for complete source code for this step.)
Now you've seen all steps involved in the process of developing a mapping application with Google Maps API. Happy map hacking!