Skip navigation

Working with the Google Maps Client-Side API

Use Google's open-source API objects to add custom-mapping functions to your website

As just about all technical professionals know, Google is a leading company in the search engine business. But that's not all that Google does. Google also heavily invests in open-source development projects such as Google Maps. The Google Maps client-side API is freely available to the public for various uses. Google's primary purpose in enabling developers to embed its mapping functions in their websites is to encourage use of Google's product over that of a competitor like Yahoo. Another reason is to allow you to build custom mapping features into your website. For instance, if your company's website provided a weather service like weather.com, the Google map could serve as the map and, by using the Google API, the map could have clouds or other objects overlaying it.

Instead of making the mapping service available through web services, Google exposes a reference to a service that downloads scripts to the client to enable this mapping functionality. Although Google defines components that use the browser's capabilities, whenever I can I use the ASP.NET AJAX architecture instead, to show that Google can fit within what Microsoft has done in its client-side library. Let's take a look at the GMap2 object and walk through examples of how to add various custom mapping functions zoom and pan controls, location, markers, panoramic view, directions, and more to your website.

The GMap2 Object

The core component in the Google Maps client-side API is the GMap2 object. GMap2 is a constructor, which takes a reference to an HTML element that defines the region to place the map. Typically, this is a DIV element that defines map's width and height. The method that Figure 1 shows initializes a GMap2 object; the method is defined in a helper class that will perform common Google mapping tasks. The initializeMap method is responsible for setting up the map on load, setting the center of the map using coordinates, and establishing a proper zoom level.

initializeMap: function(mapElement, coords, zoom) {
this._map = new GMap2(mapElement);
this._map.setCenter(coords, zoom);
this._map.setUIToDefault();
}


Figure 1: Core setup: Initializing a GMap2 object

Maps work with coordinates (latitude and longitude); along with the zoom level, the Google Maps API uses these parameters to serve up the map image to the target DIV. As the user changes the parameters (e.g., zoom in or out), the Google Maps API serves up an image that's generated on Google's servers and linked from the client. The map gets or sets coordinates using the getCenter or setCenter method (this setting also takes the zoom level); consequently, the map can also get or set the zoom using the getZoom or setZoom method.

These coordinates are actually objects of type GLatLng. This object represents the latitude and longitude of a location on the map. This method has a lat() method and a lng() method to extract the latitude and longitude values for later use. Take note that a good amount of the objects work with this GLatLng class. In the rest of the article, where you see the variable coords or point, most likely this is a GLatLng instance.

Have you noticed in Google Maps the zoom and pan controls available within the map? These controls inherit from GMapTypeControl. The controls can be instantiated and added to the map by using the GMap2.addControl() method; however, the easiest way to add the default controls is to call the setUIToDefault() method. Doing so adds the default controls within the UI. For more information about the various types of controls that can be added, consult the Google Maps API documentation at http://code.google.com/apis/maps/documentation/controls.html.




Changing a Location on a Map

So what if you want to change the location on the map? The map can use the setCenter method to change this location, or it can use the panTo method to provide a pan effect of the map moving to the new location, if the new location is within a relatively short distance. Take a look at two methods used to change the location in Figure 2.

changeMapLocation: function(coords) {
this._map.panTo(coords);
},
changeMapLocationByAddress: function(address) {
var geo = new GClientGeocoder();
geo.getLatLng(address, function(point) {
if (point)
this.changeMapLocation(point);
});
}


Figure 2: Changing a map's location

Notice the second method using a new object? Google Maps also works with street addresses, as you'd know if you've visited maps.google.com. An address has to be translated into valid coordinates, and Google provides a geocoding service that performs this translation. The encoder translates the address, and if there is a valid point, the location change occurs. An invalid address may be a reason why the point returns a null value. I won't go in-depth into geocoding; see the Google Maps documentation at http://code.google.com/apis/maps/documentation/services.html and "Got Maps?", December 2008.

Markers, Events, and Windows

Google also includes markers points on the map added to illustrate a point of interest. Points of interest are identified with the GMarker object. This object uses a latitude and longitude to specify its location. A marker may also specify a variety of configuration options, such as the icon to use, some marker-moving effects, and more. To create a marker, call the GMarker's constructor and supply a valid latitude and longitude by using the GLatLng object. Add this marker to the map using the addOverlay method, as Figure 3 shows.


addMarker: function(coords) {
var marker = new GMarker(coords,
{ draggable: true, bouncy: false });
this._map.addOverlay(marker); //_map is GMap2
Array.add(this._markers, marker); //store locally
return marker;
},


Figure 3: Adding a marker to a map

The marker specifies two UI settings: draggable, specifying whether the marker can be dragged and dropped; and bouncy, specifying whether the marker bounces when it's dropped. I'm not quite sure why Google invested the time to create a bouncing marker, but nevertheless, it's configurable. Our addMarker method accepts a GLatLng object and passes this to the constructor. This marker may appear on the map, if the latitude and longitude are within the map boundaries.

We've gone to the trouble to create a marker, but what can the marker actually do? After all, it seems pointless just to host a marker that does nothing. Fortunately, the Google Maps API includes some other cool features. One such feature is events. All events flow through the GEvent object; this event object is very easy to attach event handlers to. The biggest challenge with events is to know what the signature is, as every event changes its event-handler signature. The only way to figure this out is to consult the Google Maps API documentation (http://code.google.com/apis/maps/documentation/index.html), or guess.

I have to first explain how events in Google work, as they're similar to ASP.NET AJAX. Google event handlers recode the this keyword to point to a reference of the object having an event handler attached to it. This means that using the this keyword points to an instance of the GMarker object. This will pose some problems, as we'll see shortly.



When we add markers in an addMarker method, it would also be beneficial to centralize the event-handler attachment process. Figure 4 shows an updated version of the addMarker method, with event-handling added.

addMarker: function(coords) {
var marker = new GMarker(coords,
{ draggable: true, bouncy: false });
marker.AspNetComponent = this;
GEvent.addListener(marker, "click", function(latLng) {
this.AspNetComponent.showInfoWindow(latLng,
this.AspNetComponent._createMarkerWindow(this),
true);
});
GEvent.addListener(marker, "dragstart", function() {
this.AspNetComponent.hideInfoWindow();
});
GEvent.addListener(marker, "dragend", function() {
this.AspNetComponent.showInfoWindow(this.getLatLng(),
this.AspNetComponent._createMarkerWindow(this),
true);
});


Figure 4: Handling events

The GMarker class itself supports clicking the marker and dragging the marker around the map. The GEvent object attaches event handlers to the marker's click, dragstart, and dragend events. When the user begins or ends the dragging, the event handler attached to these events fires.

Did you notice all the references to AspNetComponent? This reference handles a very specific problem. Because Google event handlers recode the this pointer to point to the GMarker instance, there's no easy way to access the ASP.NET AJAX utility class that defines the addMarker method. It needs to access this class to call its methods.

The workaround works only too well in JavaScript: Attach another property to the marker referencing the ASP.NET AJAX component. This way, this new property can be referenced directly and have all the methods it needs to call called.

A better way is to attach the AspNetComponent reference to the window object. Since only one instance of the component runs in the browser at a time, the browser's window object can be referenced anywhere in the application, and this AspNetComponent reference can be made directly without the need for the "this" prefix. Note: I considered trying to use the $addHandler object, but because these aren't DOM object events; $addHandler is meant for DOM, not JavaScript, objects.

The second great feature is information windows. Google automatically provides a pop-up balloon notification. This notification is triggered through the openInfoWindow or openInfoWindowHtml method of the GMap2 or GMarker class. There may be other Google objects that inherit this as well. These two methods display a message to the user stating some information about the marker or point on the map at hand. Again, the information window uses a coordinate value to display itself and isn't necessarily tied to a specific marker. In this case, the marker events tie the informational window to the marker's location, using some helper methods, as shown in Figure 5.

hideInfoWindow: function() {
this._map.closeInfoWindow();
},
showInfoWindow: function(coords, content, isHtmlText, options) {
if (isHtmlText)
this._map.openInfoWindowHtml(coords, content, options);
else
this._map.openInfoWindow(coords, document.createTextNode(content), options);
}


Figure 5: Showing and hiding informational windows

In this case, the map triggers an informational window. Please note that the showInfoWindow and hideInfoWindow are helper methods that wrap the underlying Google functionality and that the actual work is performed via the Google methods themselves. At any point, an informational window can be closed using the closeInfoWindow() method; this window doesn't cause any issues if no informational windows are present.

In the included example, the map displays the coordinates of the current point that's clicked or dropped on the map. This is useful to have to find starting coordinates for the map.



Panoramic View

Recently, Google included a new feature into its mapping control: a 360-degree street panoramic view that allows anyone to view the actual details of a specific street. This feature targets more popular areas but is gaining ground in the amount of the United States and the world that it covers. This panoramic view works similarly to the GMap2 control: It targets a DIV element and uses it as its host. Panoramic view uses Flash, so Flash must be installed on the computer. You can configure this view can be configured; there are a couple of necessary settings. Some of these settings are

  • Yaw defines the rotation around the camera to point. The value provided is a floating point value relative from due north.
  • Pitch the degrees up or down from the default pitch of the camera (zero).
  • Zoom the zoom level of the street-view picture.

All this functionality is part of the GStreetviewPanorama class. Take a look at a method to initialize a panoramic view, shown in Figure 6.

initializePanorama: function(panoramaElement, coords) {
this._panElement = panoramaElement;
this._pan = new GStreetviewPanorama(panoramaElement);
panoramaElement.style.display = "none";
//Errors occur because location isn't available (600) or service has issues (500)
GEvent.addListener(this._pan, "error", function(errorCode) {
panoramaElement.style.display = "none";
});
},


Figure 6: Initializing the panoramic view

The GStreetviewPanorama object works similar to the GMap2 class in that it uses a coordinate-based approach and takes an HTML element that it will use as its host in its constructor. The panoramic view also has a few events, the most important of which is the error event. If, for whatever reason, the GLatLng instance used to show a specific street view isn't supported (i.e., no views are available), an error event is fired with an error code of 600, signaling this.

Because some of the views may not be available, the approach I took to displaying the street view is by turning the DIV element that hosts it on and off whenever the map is or isn't available. This is important because the street-view panoramic object doesn't have a success event.

The street view can use its own coordinates, but often it's useful to marry the map and the street view together. To do this, every time a marker is added, clicked, or dropped on the map, the street view attempts to do its thing and display a map. These methods and event handlers all call the changePanoramaLocation method to change the street view to a new coordinate/point of view (POV), as Figure 7 shows. The POV entails using the yaw, pitch, and zoom as described earlier.

changePanoramaLocation: function(coords, pov) {
if (pov == null)
pov = { yaw: 0, pitch: 0, zoom: 1 };
this._panElement.style.display = "block";
this._pan.setLocationAndPOV(coords, pov);
},
addMarker: function(coords) {
.
.
.
this._map.addOverlay(marker);
Array.add(this._markers, marker);
this.changePanoramaLocation(coords);
}


Figure 7: Changing the panoramic view

Notice that the map element is displayed whenever the coordinates are established; if the coordinates happen to be bad, the map element is quickly hidden when the 600 error occurs, as previously shown.

Directions

Directions are essential to the mapping process. In the Google Maps API, the direction process is a cinch. Directions also involve the use of a map and an HTML element to render them in. The directions part of the API, however, is more self-sustaining; it renders each directional statement itself as well as manages the points and route on the map.

As with the maps and steet-view features, the directions feature has an initialize function, which instantiates our GDirections object. Take a look at the following methods to initialize the map and display the driving directions, as Figure 8 shows.

initializeDirections: function(directionsElement) {
if (this._map == null)
throw Error.invalidOperation("The initializeMap() method must be called before directions can be established");
this._dir = new GDirections(this._map, directionsElement);
},
showDirections: function(from, to, options) {
var query = String.format("from: {0} to: {1}", from, to);
this._dir.load(query, options);
}


Figure 8: Initializing the map and showing directions

Displaying driving directions is pretty easy; it's done using a query structure format. To display driving directions, simply create a string specifying the from: and to: locations. Some additional options are available, such as excluding highways in the driving directions, if that's possible.




Local Search

The local search feature is an add-on that's available by including another Google script in our application. LocalSearch is a control that can be displayed within the Google map. It consists of a text box and button that searches the currently available location for anything of a specific venue: restaurants, barber shops, malls, and so on.

It's really easy to add the LocalSearch control to the script. To do so, you need to download an extra script from www.google.com/uds/solutions/localsearch/gmlocalsearch.js. This URL makes available a LocalSearch class within the google.maps namespace. You can add it to a Google map by using the line of code in Figure 9 shows.

this._map.addControl(new google.maps.LocalSearch(), new
GControlPosition(G_ANCHOR_BOTTOM_RIGHT, new GSize(10, 20)));


Figure 9: Adding the LocalSearch control

Once this control is within the map, it works like the driving direction; it's self-sustaining in that it renders its own interface and the result set. There is one other external dependency however; for the control to look "pretty," it requires importing two Cascading Style Sheets (CSS) files that contain the styles for the control.

Putting It All Together

You may not have realized it, but the script within this article is embedded in a helper component called GoogleMapUtility. This utility class simply wraps all our common mapping logic into one common place. The shell of this component, reiterating the methods we created throughout the article, is outlined in Figure 10.

Type.registerNamespace("AjaxSamples");
AjaxSamples.GoogleMapUtility = function() { }
AjaxSamples.GoogleMapUtility.prototype = {
get_map: function() { },
get_mapZoom: function() { },
set_mapZoom: function(value) { },
get_panorama: function() { },
addMarker: function(coords) { },
changeMapLocation: function(coords) { },
changeMapLocationByAddress: function(address) { },
changePanoramaLocation: function(coords, pov) { },
clearMarkers: function() { },
_createMarkerWindow: function(marker) { },
dispose: function() { },
hideInfoWindow: function() { },
initializeDirections: function(directionsElement) { },
initializeMap: function(mapElement, coords, zoom) { },
initializePanorama: function(panoramaElement, coords) { },
removeMarker: function(marker) { },
showDirections: function(from, to, options) { },
showInfoWindow: function(coords, content, isHtmlText, options) {}
}
AjaxSamples.GoogleMapUtility.registerClass(
"AjaxSamples.GoogleMapUtility", Sys.Component);


Figure 10: Helper class shell

The sample ASP.NET page (code shown in Figure 11) implements most, if not all, of these functions in a test application. At page-loading time, all the initialization methods get called in a specific order. A location of 51.231,-0.609 is used as the center of the map, along with a zoom at level 8.

<script type="text/javascript"
language="javascript">
var googleMapUtility = null;
Sys.Application.add_unload(pageUnload);
//Called from address lookup button, on click
function getaddress_click(sender, e) {
if (!e) e = window.event;
var address = $get("<%= address.ClientID %>").value;
googleMapUtility.changeMapLocationByAddress(address);
}
//Called from directions looup button, on click
function getdirections_click(sender, e) {
if (!e) e = window.event;
var fromLocation = $get("<%= fromlocation.ClientID %>").value;
var toLocation = $get("<%= tolocation.ClientID %>").value;
googleMapUtility.showDirections(fromLocation, toLocation);
}
function pageLoad() {
googleMapUtility = new AjaxSamples.GoogleMapUtility();
googleMapUtility.initializeMap($get("googlemapview"),
new GLatLng(51.231, -0.609), 8);
googleMapUtility.initializePanorama($get("googlepanoramicview"));
googleMapUtility.initializeDirections($get("googledirectionview"));
}
function pageUnload() {googleMapUtility.dispose();
}
</script>


Figure 11: Helper component in use

I set up the form with supporting components: text boxes for an address search, and a to/from address, along with buttons to trigger an address/direction search. Both buttons trigger an asynchronous call using the Google Maps API; this application does not post back to the server.

Although there are only two buttons triggering specific functionality within the map, the map supports a wide array of functions, too: It supports clicking and dragging within the map. So not only can the user interact with these extra form elements for the test application, the user can also interact with other features set up within our helper component.

I've set up all the Google scripts in an instance of a ScriptManagerProxy; although our helper utility should have dynamically loaded the script files, I used the ScriptManagerProxy as a way to show how easily Google code can fit within the ASP.NET architecture. The primary purpose of the ScriptManagerProxy classes is to push any scripts to the ASP.NET page's client code at rendering time, at a specific point in the lifecycle. Thus, this component is a good fit.

Because all the code is embedded within the helper class, the code is easily reusable within other pages of the application, though this sort of functionality is commonly placed in one section of the application anyway. The final helper utility script looks something like that in Figure 11. To see the entire ASP.NET page example, check out the sample code, also available for download with this article at aspnetpro.com, which also has the form and its setup.

You can see that the code required for the helper component is shorter than it would be without the helper component. Additionally, you can see that it doesn't take much coding effort to set up Google Maps for your application.

Happy Mapping

Google Maps is a client-side scripting API enabling the embedding of map images into your application. Its self-contained script components generate the map, a panoramic view, and even directions all through the use of Google's classes listed in its API documentation. We've discussed some of the components here and set up a sample web application.

Google mapping is coordinate-based, in that it uses latitude and longitude to identify the center of the map, points (markers) of interest, and the street view. Google maps are also interactive, allowing the user to pan and zoom in on the map, click and drag the map or its markers, switch between the various types of map (e.g. satellite view), and display information to the user in the form of a pop-up window. If you decide it's worth your while to delve into the Google Maps client-side API, you'll want to refer to the complete API reference at http://code.google.com/apis/maps/documentation/reference.html.

Brian Mains ([email protected]) is a Microsoft MVP and consultant with Computer Aid, where he works with nonprofit and state government organizations.


Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish