This tutorial compares the APIs of Yahoo! and Google maps by implementing a simple side-by-side map overlay rendered on a single HTML page. In this tutorial we will describe the details of using each API in JavaScript. Along the way we’ll compare a few of the differences between the two APIs. This material is suitable to be incorporated as part of a computer science lab covering the AJAX interfaces to the Yahoo and Google Maps APIs.
Both the Yahoo! and Google Maps APIs are easy to work with. The packages are similar. Both started out being based on mapping technology provided by Navteq. Today Google uses Tom Tom’s Tele Atlas data, while Yahoo has continued to use Navteq. But both companies source geographic data from a variety of other partners as well. The Google Maps API seems to support a few more features, especially in navigation and map types. In addition, the Google maps generally offer greater detail and more up-to-date content, especially when rendering international regions.
In order to compare both packages side-by-side we initially wanted to run each Maps API together on a single page. Unfortunately, we could not load both APIs together due to namespace collisions. A workaround was to load each package into its own iframe.
With this approach we were able to develop a web page that shows an initial default geographic location, the Golden Gate Bridge, San Francisco, side-by-side in each of the Maps APIs. Each map is rendered with a sample of its package’s standard navigation controls. On each map, a marker is placed at the center when the map is initially rendered. On a MouseOver event, this marker will display a pop-up picture of the target location, which in our case is the Golden Gate Bridge. The pop-up image will also provide a link to its associated Wikipedia entry.
The user can specify a new address for each map and is also be able to input different latitude and longitude coordinates to render a new map location. In addition, a fresh marker is placed anywhere on the map that the user clicks.

Figure 1: Comparing Maps APIs Side-by-Side — Screenshot
Try this example
Starting out with Yahoo! and Google Maps APIs
An introduction to the AJAX Maps API for Yahoo! can be found at Yahoo! Maps Web Services - AJAX API Getting Started Guide. An introduction to the Maps API for Google can be found at Google Maps API.
You must obtain your own Yahoo! app ID and Google key in order to run the application presented in this tutorial on your own server.
Overview of Our Application
Our application resides in an HTML page that contains two iframe elements. The JavaScript application in each iframe can call functions in the other iframe. This feature is used to synchronize the two maps. Cross-frame communication is limited to using simple numeric and string values as arguments to the method calls; complex parameters such as objects cannot be supported.

Figure 2: Comparing Maps APIs Side-by-Side — Overview
Parent Page
IFRAME layout
The parent page that defines the iframe layout for our application, as depicted in Figure 1, is shown in the following listing:
Listing 1: Parent page with iframe layout
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Side-by-Side Yahoo! and Google Maps</title>
</head>
<body>
<div style="width: 844px; background-color: #ccc;">
<br />
<h2 style='text-align: center; color: white;'>
Side-by-Side Yahoo! and Google Map APIs
</h2>
<br />
</div>
<div style="width: 844px;">
<iframe src="yahoo_map_frame.html" name="yahoo"
style="border: none; width: 420px; height: 800px; position: absolute;">
</iframe>
<iframe src="google_map_frame.html" name="google"
style="border: none; width: 420px; height: 800px; float: right;">
</iframe>
</div>
</body>
</html>
Map Application Walkthrough
In this section we will explain various details about how our application is constructed and how it uses each Maps API package. Full listings for each API solution are presented at the end of the tutorial.
Loading the Maps API
Each iframe loads its own Maps API package. The following <script> tag loads the Google Maps API by supplying the required key and sensor flag values. The sensor flag notifies Google about whether a sensor is being used to determine the user’s location.
Listing 2: Loading the Google Maps API package
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="http://maps.google.com/maps?file=api&v=2&key=[YOUR GOOGLE MAPS API KEY]&sensor=[Are you using a location sensor? true or false]"></script>
<script>
...
The Yahoo! Maps API is loaded in a similar manner but doesn't require the sensor notification parameter.
Listing 3: Loading the Yahoo! Maps API package
...
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="http://api.maps.yahoo.com/ajaxymap?v=3.8&appid=[YOUR YAHOO! MAPS API KEY]"></script>
...
Setting Up Globals
After loading the Google or Yahoo! Maps API, each map application sets up a small number of global variables including defaultGeoTargets, $EL, and myMap.
The global variable defaultGeoTargets holds information about the features we want to highlight on the map. It is an array of hash literals containing address, geolocation and html values for use by the popup information windows associated with map markers.
Three well known landmarks will be featured on our map, the Golden Gate Bridge in San Francisco, the Hallepoort Gate in Brussels and the ancient Ile de la Cite of Paris. The literal array (of hash literals) assigned to defaultGeoTargets can easily be extended to add more landmarks. Alternatively, other data sources such as JSON could be used to populate the defaultGeoTargets array.
Listing 4: defaultGeoTargets global function literal
...
<script>
var defaultGeoTargets = [
{
address: 'Golden Gate Bridge, San Francisco',
geobox: { N:37.833, E:-122.474, S:37.805, W:-122.482 },
html: "<a href='http://en.wikipedia.org/wiki/Golden_Gate_Bridge' target='_blank'><img src='/images/sf_bridge_small.jpg' width='160px' height='120px'></a>"
},
{
address: 'Hallepoort, Brussels',
geobox: { N:50.834, E:4.345, S:50.833, W:4.344 },
html: "<a href='http://en.wikipedia.org/wiki/Halle_Gate' target='_blank'><img src='/images/hallepoort_1612.jpg' width='120px' height='160px'></a><span style='position: absolute;'><p> Hallepoort<br /> in 1612.</p></span>"
},
{
address: 'Ile de la Cite, Paris',
geobox: { N:48.86, E:2.349, S:48.85, W:2.341 },
html: "<a href='http://en.wikipedia.org/wiki/Ile_de_la_Cite' target='_blank'><img src='/images/ile_de_la_cite.jpg' width='200px' height='150px'></a>"
}
];
...
The second global variable $EL is simply a convenience function for easily accessing the client JavaScript function document.getElementById().
Listing 5: $EL convenience function
...
var $EL = function(id) { return document.getElementById(id); };
...
Our Application Encapsulated as a JavaScript Function Literal
The third and last of our global variables myMap holds our principal data structure for interfacing with the specific Maps API we have selected, Google or Yahoo!. It uses the technique of JavaScript function closure to isolate its namespace, retain state, and to export public methods to its caller. The public methods are contained in an associative array object returned and assigned to myMap. The public methods exported include .initMap, .showMap, .reMap, .remoteRemap and .changeAddress. All other methods and attributes within the closure context are effectively private. Note that the variable myMap is assigned the result of executing an anonymous function literal immediately after it has been read in by the browser (notice the trailing parentheses after the function literal definition). The function literal then sets up the closure context and returns the associative array of public function literals.
Listing 6: How JavaScript builds the myMap function closure and returns a hash of public functions
...
var myMap = function() {
// function closure environment
// private attributes
// private interface adapters
// private application functions
// return {
// hash of public functions
// }(); <--- parentheses cause immediate execution of function literal
}
...
Private Attributes
The function literal assigned to myMap starts out by defining several private variables including map and targetFeatures. These private variables are retained as attributes of the function closure context. In our application, they are initialized by calling the public function .initMap from the onLoad event attribute of the HTML body element. The private variable map will hold the basic map structure allocated by the selected underlying Maps API. The variable targetFeatures will be assigned a singleton associative array that contains the proximity function, .isNearby which we will examine in detail in the section Private Application Functions below. The variable DONT_PAN is used as a self-documenting constant for requesting that the panAndMark function temporarily turn off map panning. We will look at the function panAndMark in a moment.
Listing 7: The private attributes of myMap's function closure
...
var myMap = function() {
var map;
var targetFeatures;
var DONT_PAN = true;
...
Private Interface Adapters
The next section of myMap defines wrappers around the underlying Maps APIs for Yahoo and Google. These functions are adapted to each API.
The following table shows the relationship between our private interface adapters and the corresponding functions in the Yahoo! and Google packages.
Table 1: Wrappers for selected Maps API calls
| Private Interface Adapter |
Yahoo! Maps API |
Google Maps API |
| allocMap |
new YMap |
new GMap2 |
| applyUIControls |
addTypeControl, addZoomLong, addPanControl |
setUIToDefault, addControl |
| browserIsCompatible |
return YMAPPID |
GBrowserIsCompatible |
| captureEvent |
YEvent.Capture |
GEvent.addListener |
| createMarker |
new YMarker |
new GMarker |
| eraseOverlays |
removeMarkersAll |
clearOverlays |
| getZoomLevel |
getZoomLevel |
getZoom |
| makePointFromFields |
new YGeoPoint |
new GLatLng |
| mouseClickEventType |
EventsList.MouseClick |
“click” |
| mouseOverEventType |
EventsList.MouseOver |
“mouseover” |
| onClick callback |
function(_e, _c) |
function(overlay, point) |
| onEndMapDraw callback |
getCenterLatLon |
— |
| openInfoWindow |
openSmartWindow |
openInfoWindowHtml |
| pLat |
p.Lat |
p.y |
| pLon |
p.Lon |
p.x |
| panToPt |
panToLatLon |
panTo |
| zoomAndCenter |
drawZoomAndCenter |
getLatLng, panTo |
For our application, it is convenient to set default zoom level values in our interface adapter zoomAndCenter. This enables a common calling signature using a minimum number of arguments. It also fixes a problematic default zoom level in the Google Maps API. For example, our zoomAndCenter interface adapter can be called with 1 argument (the address to be resolved) from either a Yahoo! or a Google map application. In the case of the Yahoo! map application, we default a missing zoom level parameter to level 5 (city scale); for Google, we default the parameter to 13 (their zoom level for city). In contrast, a missing zoom level parameter to the underlying Maps API is defaulted to 5 for Yahoo! but is defaulted to 0 for Google. Of course, this native Google default is not very useful.
The interface adapter section of myMap for each Maps API is listed below, first for Google and then for Yahoo!.
Listing 8: Interface Adapters for Google Maps API
...
/* Begin Google Interface Adapters */
var allocMap = function(el) { return new GMap2(el); }
var applyUIControls = function() {
map.setUIToDefault();
map.addControl(new GOverviewMapControl());
}
// Google provides an interface to query whether the current browser is supported.
// A similar (but possibly unreliable) test can be simulated using the Yahoo! API.
// See below.
var browserIsCompatible = function() {
return GBrowserIsCompatible();
}
var captureEvent = function(_obj, _event, _action) { GEvent.addListener(_obj, _event, _action); }
var createMarker = function(p) { return new GMarker(p); }
var eraseOverlays = function() { map.clearOverlays(); }
var getZoomLevel = function() { map.getZoom(); }
var makePointFromFields = function() { return new GLatLng($EL("lat").value, $EL("lon").value); }
var mouseClickEventType = "click";
var mouseOverEventType = "mouseover";
var onClick = function(overlay, point) {
if (point) {
panAndMark(point);
showPoint(point);
}
}
var openInfoWindow = function(mrkr, html) { mrkr.openInfoWindowHtml(html); }
var pLat = function(p) { return p.y; }
var pLon = function(p) { return p.x; }
var panToPt = function(p) { map.panTo(p); }
// In Google, zooming and centering using address lookup must be implemented
// with an asynchronous callback attached to the .getLatLng method.
// While in Yahoo, the callback occurs when the endMapDraw event is fired.
var zoomAndCenter = function(address, z) {
(new GClientGeocoder).getLatLng(
address,
function(point) {
if (!point) {
alert(address + " not found");
} else {
map.setCenter(point, z || 13); // default to city view
panAndMark(point, DONT_PAN)
showPoint(point);
// alert("Current Google zoom level: "+getZoomLevel());
}
}
);
}
/* End Google Interface Adapters */
...
Listing 9: Interface Adapters for Yahoo! Maps API
...
/* Begin Yahoo! Interface Adapters */
var runOnceOnEndMapDraw = 1;
var allocMap = function(el) { return new YMap(el); }
var applyUIControls = function() {
map.addTypeControl();
map.addZoomLong();
map.addPanControl();
}
// Google provides an interface to query whether the current browser is supported.
// A similar (but possibly unreliable) test can be simulated using the Yahoo! API.
var browserIsCompatible = function() {
return YMAPPID;
}
var captureEvent = function(_obj, _event, _action) { YEvent.Capture(_obj, _event, _action); }
var createMarker = function(p) { return new YMarker(p); }
var createPoint = function(lat, lon) { return new YGeoPoint(lat, lon); }
var endMapDrawEventType = EventsList.endMapDraw;
var eraseOverlays = function() { map.removeMarkersAll(); }
// var getZoomLevel = function() { map.getZoomLevel(); }
var makePointFromFields = function() { return new YGeoPoint($EL("lat").value, $EL("lon").value); }
var mouseClickEventType = EventsList.MouseClick;
var mouseOverEventType = EventsList.MouseOver;
var onClick = function(_e, _c) {
var point = createPoint( _c.Lat, _c.Lon );
panAndMark(point);
showPoint(point);
}
// In Yahoo!, the endMapDraw event is fired after address lookup
// and map centering have completed.
var onEndMapDraw = function() {
if (runOnceOnEndMapDraw > 0) {
runOnceOnEndMapDraw = 0;
var point = map.getCenterLatLon();
panAndMark(point, DONT_PAN)
showPoint(point);
}
}
var openInfoWindow = function(mrkr, html) { mrkr.openSmartWindow(html); }
var pLat = function(p) { return p.Lat; }
var pLon = function(p) { return p.Lon; }
var panToPt = function(p) { map.panToLatLon(p); }
var zoomAndCenter = function(address, z) {
map.drawZoomAndCenter(address, z || 5); // default to city view
// alert("Current Yahoo zoom level: "+getZoomLevel());
}
/* End Yahoo! Interface Adapters */
...
Private Application Functions
The next section of our application implements more private functions that we use internally. With minor exceptions, these are the same for both the Yahoo! and Google maps APIs. The previously defined wrapper functions help maintain this close similarity.
Let’s look at each private function in turn.
showPoint
showPoint sets the values of the fields for Lattitude and Longitude on the HTML page (see Figure 1). Note that the attribute names within a point object are different between Yahoo! and Google. Therefore we use the wrapping functions pLat and pLon to preserve the same high level interface across Yahoo! and Google.
Listing 10: showPoint private function
...
var showPoint = function(p) {
$EL("lat").value = pLat(p);
$EL("lon").value = pLon(p);
}
...
setFields
setFields sets the visible input fields for Lattitude and Longitude to new values.
Listing 11: setFields private function
...
var setFields = function(lat, lon) {
$EL("lat").value = lat;
$EL("lon").value = lon;
}
...
panAndMark
panAndMark clears the map of any previous markers and subsequently positions the map to the point indicated by the first parameter p. Then a new marker is created at that point. If this marker is near the bounding box of one of the target features (e.g., Golden Gate Bridge, Hallepoort or Ile de la Cite), an information window is attached to the marker, using our wrapper function openInfoWindow, and will be displayed when the marker gets a MouseOver event. The second argument to panAndMark, dont_pan, controls whether to pan to the given point during the function execution. Avoiding a repeat pan operation is useful when the underlying API has already centered on the desired point.
Listing 12: panAndMark private function
...
var panAndMark = function(p, dont_pan) {
eraseOverlays();
if (!dont_pan) panToPt(p);
var marker = createMarker(p);
var html = targetFeatures.isNearby(p);
if (html) {
captureEvent(marker, mouseOverEventType, function() {
openInfoWindow(marker, html);
});
}
map.addOverlay(marker);
}
...
setTargetFeatures
The next private function is a bit more complex. When executed, the function literal which is assigned to setTargetFeatures creates a closure for its argument targets and exports a function literal .isNearby to its caller. Because of closure context, the exported function .isNearby continues to have access to targets when invoked later to test the proximity between a marker and a target. In the case of our example, the target feature list is contained in the global literal array defaultGeoTargets. The feature list, however, could just as easily have been obtained from an alternative source like JSON. Here, the advantage of applying closure to the argument targets is that the function does not need to know anything about, and is thereby decoupled from, the structure of the caller and the global environment.
When called, the exported function .isNearby simply iterates through the target list and checks to see if the given point p is contained within the bounding box of each target in turn. If so, the HTML string associated with that target is returned to the caller. In our example, this HTML will be rendered in the marker pop up information window when the marker is activated by a MouseOver event.
As a side effect, when the function literal is called from .initMap, it also sets the value of the address field in the application’s HTML body to the first element of the first target. This becomes the address which will be rendered as the initial location of the map.
Listing 13: setTargetFeatures private function
...
var setTargetFeatures = function(targets) {
if (targets) {
$EL("address").value = targets[0].address; // first element of first target is default address
}
return {
isNearby: function(p) {
var found = null;
for (var i=0; i < targets.length; i++) {
b = targets[i].geobox;
if (pLon(p) < b.E && pLon(p) > b.W && pLat(p) < b.N && pLat(p) > b.S) {
var target = targets[i];
found = targets[i].html;
break;
}
}
return found;
}
}
}
...
Public Application Functions
The next section of our application embodies the public interface of our application. When the JavaScript interpreter reads the function literal on the right hand side of the assignment statement for myMap, it executes the function literal and assigns the returned object to myMap. The returned object is an associative array containing the public functions of our maps application.
Listing 14: Hash of public functions (template)
...
return {
//
// public functions expressed as a literal hash object
//
initMap: ...
showMap: ...
reMap: ...
remoteMap: ...
changeAddress: ...
}
...
The public functions are similar across both APIs. Each public function is described in the following sections.
initMap Public Function
The public function .initMap first checks to see if the current browser is compatible with the selected Maps API. In the Google Maps API, this function is explicitly supported. In the Yahoo package, there does not appear to be a way to programmatically tell if the current browser is supported, so we have created a stub which simply echos the Yahoo! Map ID that was provided in the request to download the JavaScript maps library.
If the browser is found to be compatible, the targetFeatures object is created which defines a method to determine if a marker on the map lies within the bounding box of one of our target features. This check is invoked whenever a MouseOver event is raised for a marker on the map.
Then the following steps are executed: (1) the base map object is allocated by the selected Maps API, (2) listeners are set up for map events, and (3) a selection of standard UI controls (map navigation and zoom elements) are applied. Notice that an additional map event is needed for the Yahoo interface to capture the completion of map drawing which then allows placement of an initial marker (see below for more details).
Finally, the map along with an initial location and marker is displayed using a call to our own public .showMap function. Because .showMap must be invoked on an object, we use the current object reference this as the receiving object.
The Google based initMap function is:
Listing 15: initMap public function for Google Maps API
...
initMap: function() {
if (!browserIsCompatible()) return;
targetFeatures = setTargetFeatures(defaultGeoTargets);
map = allocMap($EL('map_canvas'));
captureEvent(map, mouseClickEventType, onClick);
applyUIControls();
this.showMap();
},
...
The Yahoo! based initMap function is:
Listing 16: initMap public function for Yahoo! Maps API
...
initMap: function() {
if (!browserIsCompatible()) return;
targetFeatures = setTargetFeatures(defaultGeoTargets);
map = allocMap($EL('map_canvas'));
captureEvent(map, mouseClickEventType, onClick);
---> captureEvent(map, endMapDrawEventType, onEndMapDraw); <--- additional event
applyUIControls();
this.showMap();
},
...
showMap Public Function
.showMap performs the address resolution, centering and zooming procedures required to display a map for an initial or a new address. .showMap is exported as a public function so that it can be invoked by the .changeAddress function of the maps application running in the other iframe. In this way the two iframes can be kept in sync if allowed by the sync checkbox. Note that our Yahoo version must set a recursion limiting switch, the runOnceOnEndMapDraw flag, in order to allow the endMapDraw callback to perform centering and marker placement.
Notice that for the Yahoo! API, an additional event, endMapDraw, must be used to draw an initial marker on the map whenever a new address is resolved. This is because, in the Yahoo! API, the center of the map can only be retrieved after the map has finished being drawn. This is an inherently asynchronous process. The Yahoo! API uses drawZoomAndCenter to resolve, center and zoom on an address. Then the callback registered for the endMapDraw event can retrieve the new map’s center and place the initial marker. The Google API uses the geocoding function .getLatLng to first resolve an address and then invoke a callback, given as a parameter to .getLatLng, to center the map and place an initial marker. These procedures are executed in the interface function zoomAndCenter which was shown earlier in the section Private Interface Adapters.
The Yahoo! steps involved in zoomAndCenter (with help from .initMap, onEndMapDraw and .showMap) are the following:
.drawZoomAndCenter: (1) resolve, (2) center and (3) zoom on address
endMapDraw callback: (4) get center & (5) place marker
The Google steps in zoomAndCenter are the following:
.getLatLng: (1) resolve address
getLatLng callback: (2) set center, (3) zoom and (4) mark
The Google based showMap function is:
Listing 17: showMap public function for Google Maps API
...
showMap: function(address) {
if (!map) return;
$EL("address").value = address || $EL('address').value;
zoomAndCenter($EL("address").value);
},
...
The Yahoo! based showMap function is:
Listing 18: showMap public function for Yahoo! Maps API
...
showMap: function(address) {
if (!map) return;
$EL("address").value = address || $EL('address').value;
---> runOnceOnEndMapDraw = 1;
zoomAndCenter($EL("address").value);
},
...
Notice the runOnceOnEndMapDraw flag in the Yahoo function. As already mentioned above, runOnceOnEndMapDraw is used to limit the depth of recursion in the callback attached to the endMapDraw event.
reMap Public Function
.reMap is invoked when the user presses the Set button (see Figure 1). Typically the user has changed the values of the latitude and longitude input fields in order to recenter the map. .reMap performs the following steps: (1) sets the value of the address input text field to the string “CUSTOM”, (2) creates a point object (for the appropriate Maps API using our wrapper function), (3) pans to the coordinates of this point object, and (4) places a marker at the location. If the sync checkbox has been selected, the maps application running in the other iframe will be requested to pan to, mark and display the same coordinates.
The Google based reMap function is:
Listing 19: reMap public function for Google Maps API
...
reMap: function() {
if (!map) return;
$EL("address").value = "CUSTOM";
point = makePointFromFields();
panAndMark(point);
showPoint(point);
if ($EL('sync').checked) parent.yahoo.myMap.remoteRemap(pLat(point), pLon(point));
},
...
And the Yahoo based reMap function is:
Listing 20: reMap public function for Yahoo! Maps API
...
reMap: function() {
if (!map) return;
$EL("address").value = "CUSTOM";
point = makePointFromFields();
panAndMark(point);
showPoint(point);
if ($EL('sync').checked) parent.google.myMap.remoteRemap(pLat(point), pLon(point));
},
...
remoteMap Public Function
remoteMap is invoked by the maps application running in the other iframe when the user presses the Set button of that iframe to re-center the map using the current values of the latitude and longitude fields. It is invoked from the .reMap function of the other iframe. The sync checkbox of the other iframe must be selected for the cross-frame call to be made.
The Google and Yahoo based remoteMap functions are identical:
Listing 21: remoteMap public function for Google & Yahoo! Maps API
...
remoteRemap: function(lat, lon) {
if (!map) return;
$EL("address").value = "CUSTOM";
setFields(lat, lon);
point = makePointFromFields();
panAndMark(point);
showPoint(point);
},
...
changeAddress Public Function
changeAddress is invoked when the user manually changes the value of the address input field in order to resolve a new address and to recenter the map. If the sync checkbox is selected, then the .showMap function of the other iframe is also invoked in order to synchronize the two maps.
The Google based changeAddress function is:
Listing 22: changeAddress public function for Google Maps API
...
changeAddress: function (addr) {
if (!map) return;
this.showMap(addr);
if ($EL('sync').checked) parent.yahoo.myMap.showMap(addr);
}
...
The Yahoo! based changeAddress function is:
Listing 23: changeAddress public function for Yahoo! Maps API
...
changeAddress: function (addr) {
if (!map) return;
this.showMap(addr);
if ($EL('sync').checked) parent.google.myMap.showMap(addr);
}
...
Finishing Up With The HTML Body
Our main JavaScript application is invoked when the onLoad event for the body tag is fired. That is, when all of the resources used by the HTML page have been loaded. When using the Google Maps API, it is recommended that the onUnload attribute be set to trigger a call to GUnload when leaving the page in order to avoid memory leaks.
Here is the Google version:
Listing 24: onLoad and onUnload attributes in body tag for Google Maps API
...
</head>
<body onload="myMap.initMap();" onunload="GUnload();">
...
Here is the Yahoo! version:
Listing 25: onLoad attribute in body tag for Yahoo! Maps API
...
</head>
<body onload="myMap.initMap();">
...
The header for the HTML page within each iframe is adjusted for each Maps API:
Here’s the Google header:
Listing 26: Page header for Google map
...
<h2><font style="color: white; background: #ccc;">Google</font> Maps API</h2>
<div style="width: 400px;">
...
And the Yahoo! header:
Listing 27: Page header for Yahoo map
...
<h2><font style="color: white; background: #ccc;">Yahoo!</font> Maps API</h2>
<div style="width: 400px;">
...
The address input field allows the user to change the current address rendered by the map.
Listing 28: Address input field
...
<p>Address: <input size=40 type="text" id="address" value=""
onChange="myMap.changeAddress(this.value);"></p>
...
The sync checkbox instructs the application to send any address changes to the sibling iframe containing the alternative Maps API.
Here’s the Google version:
Listing 29: Sync checkbox for Google iframe
...
<p><input type="checkbox" id="sync"> Keep address synced with Yahoo! Map</p>
...
Here’s the Yahoo version:
Listing 30: Sync checkbox for Yahoo iframe
...
<p>Keep address synced with Google Map <input type="checkbox" id="sync"></p>
...
The following section builds a dynamic list of target features, taken from global variable defaultGeoTargets. These are links that can be selected by the user (see Figure 1).
Listing 31: Dynamic creation of map selection links
...
<p>Mouseover the marker near
<ul>
<script>
for (var i=0; i < defaultGeoTargets.length; i++) {
document.writeln("<li><a href='javascript:myMap.changeAddress(defaultGeoTargets["+i+"].address)'>"+defaultGeoTargets[i].address+"</a></li>")
}
</script>
</ul>
to show its picture. Click on map for other coordinates.</p>
</div>
...
map_canvas is the HTML element that acts as a container for the map rendered by the selected Maps API. The Maps API uses the explicit dimensions of this container to size the map. In addition, style attributes may be inherited from this container by pop up information windows attached to markers. Inherited styles like font-size can conflict with layout calculations made by the Maps API for the pop up windows since these calculations are made independently of any styles actually specified for the container. See Mike Williams' Fixing the “inherited CSS” problem for more details.
Listing 32: map_canvas map container element
...
<div id="map_canvas" style="width: 400px; height: 300px"></div>
<br />
...
The user can adjust the latitude and longitude of the center of the map by entering in new values in the respective fields below and then pressing the Set button. Synchronization with the Maps API of the other iframe is maintained if the sync checkbox is checked.
Listing 33: Longitude, latitude input fields
...
<table>
<tr><td align="left"><a href="http://en.wikipedia.org/wiki/Latitude">Latitude</a>:</td><td><input size="18" type="text" id="lat" value="" ></td></tr>
<tr><td align="left"><a href="http://en.wikipedia.org/wiki/Longitude">Longitude</a>:</td><td><input size="18" type="text" id="lon" value="" ></td></tr>
<tr><td align="left" colspan="2"><input type="submit" value="Set" onClick="myMap.reMap($EL('address'));"></td></tr>
</table>
</body>
</html>
...
Complete Listings
The following listings present the complete code for each iframe component. The Yahoo code is shown first, followed by the Google code.
Yahoo Iframe Page
(Full Listing)
Listing 34: Yahoo Maps API application
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="http://api.maps.yahoo.com/ajaxymap?v=3.8&appid=[YOUR YAHOO MAPS API KEY]"></script>
<script>
var defaultGeoTargets = [
{
address: 'Golden Gate Bridge, San Francisco',
geobox: { N:37.833, E:-122.474, S:37.805, W:-122.482 },
html: "<a href='http://en.wikipedia.org/wiki/Golden_Gate_Bridge' target='_blank'><img src='/images/sf_bridge_small.jpg' width='160px' height='120px'></a>"
},
{
address: 'Hallepoort, Brussels',
geobox: { N:50.834, E:4.345, S:50.833, W:4.344 },
html: "<p style='width:200px'><a href='http://en.wikipedia.org/wiki/Halle_Gate' target='_blank'><img src='/mapsapis/images/hallepoort_small.jpg' width='120px' height='160px'></a><span style='position: absolute;'><p> Hallepoort<br /> today.</p></span></p>"
},
{
address: 'Ile de la Cite, Paris',
geobox: { N:48.86, E:2.349, S:48.85, W:2.341 },
html: "<a href='http://en.wikipedia.org/wiki/Ile_de_la_Cite' target='_blank'><img src='/images/ile_de_la_cite.jpg' width='200px' height='150px'></a>"
}
];
var $EL = function(id) { return document.getElementById(id); };
var myMap = function() {
var map;
var targetFeatures;
var DONT_PAN = true;
/* Begin Yahoo! Interface Adapters */
var runOnceOnEndMapDraw = 1;
var allocMap = function(el) { return new YMap(el); }
var applyUIControls = function() {
map.addTypeControl();
map.addZoomLong();
map.addPanControl();
}
var browserIsCompatible = function() {
return YMAPPID;
}
var captureEvent = function(_obj, _event, _action) { YEvent.Capture(_obj, _event, _action); }
var createMarker = function(p) { return new YMarker(p); }
var createPoint = function(lat, lon) { return new YGeoPoint(lat, lon); }
var endMapDrawEventType = EventsList.endMapDraw;
var eraseOverlays = function() { map.removeMarkersAll(); }
// var getZoomLevel = function() { map.getZoomLevel(); }
var makePointFromFields = function() { return new YGeoPoint($EL("lat").value, $EL("lon").value); }
var mouseClickEventType = EventsList.MouseClick;
var mouseOverEventType = EventsList.MouseOver;
var onClick = function(_e, _c) {
var point = createPoint( _c.Lat, _c.Lon );
panAndMark(point);
showPoint(point);
}
var onEndMapDraw = function() {
if (runOnceOnEndMapDraw > 0) {
runOnceOnEndMapDraw = 0;
var point = map.getCenterLatLon();
panAndMark(point, DONT_PAN)
showPoint(point);
}
}
var openInfoWindow = function(mrkr, html) { mrkr.openSmartWindow(html); }
var pLat = function(p) { return p.Lat; }
var pLon = function(p) { return p.Lon; }
var panToPt = function(p) { map.panToLatLon(p); }
var zoomAndCenter = function(address, z) {
map.drawZoomAndCenter(address, z || 5); // default to city view
// alert("Current Yahoo zoom level: "+getZoomLevel());
}
/* End Yahoo! Interface Adapters */
var showPoint = function(p) {
$EL("lat").value = pLat(p);
$EL("lon").value = pLon(p);
}
var setFields = function(lat, lon) {
$EL("lat").value = lat;
$EL("lon").value = lon;
}
var panAndMark = function(p, dont_pan) {
eraseOverlays();
if (!dont_pan) panToPt(p);
var marker = createMarker(p);
var html = targetFeatures.isNearby(p);
if (html) {
captureEvent(marker, mouseOverEventType, function() {
openInfoWindow(marker, html);
});
}
map.addOverlay(marker);
}
var setTargetFeatures = function(targets) {
if (targets) {
$EL("address").value = targets[0].address; // first element of first target is default address
}
return {
isNearby: function(p) {
var found = null;
for (var i=0; i < targets.length; i++) {
b = targets[i].geobox;
if (pLon(p) < b.E && pLon(p) > b.W && pLat(p) < b.N && pLat(p) > b.S) {
var target = targets[i];
found = targets[i].html;
break;
}
}
return found;
}
}
}
return {
initMap: function() {
if (!browserIsCompatible()) return;
targetFeatures = setTargetFeatures(defaultGeoTargets);
map = allocMap($EL('map_canvas'));
captureEvent(map, mouseClickEventType, onClick);
captureEvent(map, endMapDrawEventType, onEndMapDraw);
applyUIControls();
this.showMap();
},
showMap: function(address) {
if (!map) return;
$EL("address").value = address || $EL('address').value;
runOnceOnEndMapDraw = 1;
zoomAndCenter($EL("address").value);
},
reMap: function() {
if (!map) return;
$EL("address").value = "CUSTOM";
point = makePointFromFields();
panAndMark(point);
showPoint(point);
if ($EL('sync').checked) parent.google.myMap.remoteRemap(pLat(point), pLon(point));
},
remoteRemap: function(lat, lon) {
if (!map) return;
$EL("address").value = "CUSTOM";
setFields(lat, lon);
point = makePointFromFields();
panAndMark(point);
showPoint(point);
},
changeAddress: function (addr) {
if (!map) return;
this.showMap(addr);
if ($EL('sync').checked) parent.google.myMap.showMap(addr);
}
}
}();
</script>
</head>
<body onload="myMap.initMap();">
<h2><font style="color: white; background: #ccc;">Yahoo!</font> Maps API</h2>
<div style="width: 400px;">
<p>Address: <input size=40 type="text" id="address" value="" onChange="myMap.changeAddress(this.value);"></p>
<p>Keep address synced with Google Map <input type="checkbox" id="sync"></p>
<p>Mouseover the marker near
<ul>
<script>
for (var i=0; i < defaultGeoTargets.length; i++) {
document.writeln("<li><a href='javascript:myMap.changeAddress(defaultGeoTargets["+i+"].address)'>"+defaultGeoTargets[i].address+"</a></li>")
}
</script>
</ul>
to show its picture. Click on map for other coordinates.</p>
</div>
<div id="map_canvas" style="width: 400px; height: 300px"></div>
<br />
<table>
<tr><td align="left"><a href="http://en.wikipedia.org/wiki/Latitude">Latitude</a>:</td><td><input size="18" type="text" id="lat" value="" ></td></tr>
<tr><td align="left"><a href="http://en.wikipedia.org/wiki/Longitude">Longitude</a>:</td><td><input size="18" type="text" id="lon" value="" ></td></tr>
<tr><td align="left" colspan="2"><input type="submit" value="Set" onClick="myMap.reMap();"></td></tr>
</table>
</body>
</html>
Google Iframe Page
(Full Listing)
Listing 35: Google Maps API application
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="http://maps.google.com/maps?file=api&v=2&key=[YOUR GOOGLE MAPS API KEY]&sensor=false"></script>
<script>
var defaultGeoTargets = [
{
address: 'Golden Gate Bridge, San Francisco',
geobox: { N:37.833, E:-122.474, S:37.805, W:-122.482 },
html: "<a href='http://en.wikipedia.org/wiki/Golden_Gate_Bridge' target='_blank'><img src='/images/sf_bridge_small.jpg' width='160px' height='120px'></a>"
},
{
address: 'Hallepoort, Brussels',
geobox: { N:50.834, E:4.345, S:50.833, W:4.344 },
html: "<a href='http://en.wikipedia.org/wiki/Halle_Gate' target='_blank'><img src='/images/hallepoort_1612.jpg' width='120px' height='160px'></a><span style='position: absolute;'><p> Hallepoort<br /> in 1612.</p></span>"
},
{
address: 'Ile de la Cite, Paris',
geobox: { N:48.86, E:2.349, S:48.85, W:2.341 },
html: "<a href='http://en.wikipedia.org/wiki/Ile_de_la_Cite' target='_blank'><img src='/images/ile_de_la_cite.jpg' width='200px' height='150px'></a>"
}
];
var $EL = function(id) { return document.getElementById(id); };
var myMap = function() {
var map;
var targetFeatures;
var DONT_PAN = true;
/* Begin Google Interface Adapters */
var allocMap = function(el) { return new GMap2(el); }
var applyUIControls = function() {
map.setUIToDefault();
map.addControl(new GOverviewMapControl());
}
var browserIsCompatible = function() {
return GBrowserIsCompatible();
}
var captureEvent = function(_obj, _event, _action) { GEvent.addListener(_obj, _event, _action); }
var createMarker = function(p) { return new GMarker(p); }
var eraseOverlays = function() { map.clearOverlays(); }
var getZoomLevel = function() { map.getZoom(); }
var makePointFromFields = function() { return new GLatLng($EL("lat").value, $EL("lon").value); }
var mouseClickEventType = "click";
var mouseOverEventType = "mouseover";
var onClick = function(overlay, point) {
if (point) {
panAndMark(point);
showPoint(point);
}
}
var openInfoWindow = function(mrkr, html) { mrkr.openInfoWindowHtml(html); }
var pLat = function(p) { return p.y; }
var pLon = function(p) { return p.x; }
var panToPt = function(p) { map.panTo(p); }
var zoomAndCenter = function(address, z) {
(new GClientGeocoder).getLatLng(
address,
function(point) {
if (!point) {
alert(address + " not found");
} else {
map.setCenter(point, z || 13); // default to city view
panAndMark(point, DONT_PAN)
showPoint(point);
// alert("Current Google zoom level: "+getZoomLevel());
}
}
);
}
/* End Google Interface Adapters */
var showPoint = function(p) {
$EL("lat").value = pLat(p);
$EL("lon").value = pLon(p);
}
var setFields = function(lat, lon) {
$EL("lat").value = lat;
$EL("lon").value = lon;
}
var panAndMark = function(p, dont_pan) {
eraseOverlays();
if (!dont_pan) panToPt(p);
var marker = createMarker(p);
var html = targetFeatures.isNearby(p);
if (html) {
captureEvent(marker, mouseOverEventType, function() {
openInfoWindow(marker, html);
});
}
map.addOverlay(marker);
}
var setTargetFeatures = function(targets) {
if (targets) {
$EL("address").value = targets[0].address; // first element of first target is default address
}
return {
isNearby: function(p) {
var found = null;
for (var i=0; i < targets.length; i++) {
b = targets[i].geobox;
if (pLon(p) < b.E && pLon(p) > b.W && pLat(p) < b.N && pLat(p) > b.S) {
var target = targets[i];
found = targets[i].html;
break;
}
}
return found;
}
}
}
return {
initMap: function() {
if (!browserIsCompatible()) return;
targetFeatures = setTargetFeatures(defaultGeoTargets);
map = allocMap($EL('map_canvas'));
captureEvent(map, mouseClickEventType, onClick);
applyUIControls();
this.showMap();
},
showMap: function(address) {
if (!map) return;
$EL("address").value = address || $EL('address').value;
zoomAndCenter($EL("address").value);
},
reMap: function() {
if (!map) return;
$EL("address").value = "CUSTOM";
point = makePointFromFields();
panAndMark(point);
showPoint(point);
if ($EL('sync').checked) parent.yahoo.myMap.remoteRemap(pLat(point), pLon(point));
},
remoteRemap: function(lat, lon) {
if (!map) return;
$EL("address").value = "CUSTOM";
setFields(lat, lon);
point = makePointFromFields();
panAndMark(point);
showPoint(point);
},
changeAddress: function (addr) {
if (!map) return;
this.showMap(addr);
if ($EL('sync').checked) parent.yahoo.myMap.showMap(addr);
}
}
}();
</script>
</head>
<body onload="myMap.initMap();" onunload="GUnload();">
<h2><font style="color: white; background: #ccc;">Google</font> Maps API</h2>
<div style="width: 400px;">
<p>Address: <input size=40 type="text" id="address" value="" onChange="myMap.changeAddress(this.value);"></p>
<p><input type="checkbox" id="sync"> Keep address synced with Yahoo! Map</p>
<p>Mouseover the marker near
<ul>
<script>
for (var i=0; i < defaultGeoTargets.length; i++) {
document.writeln("<li><a href='javascript:myMap.changeAddress(defaultGeoTargets["+i+"].address)'>"+defaultGeoTargets[i].address+"</a></li>")
}
</script>
</ul>
to show its picture. Click on map for other coordinates.</p>
</div>
<div id="map_canvas" style="width: 400px; height: 300px"></div>
<br />
<table>
<tr><td align="left"><a href="http://en.wikipedia.org/wiki/Latitude">Latitude</a>:</td><td><input size="18" type="text" id="lat" value="" ></td></tr>
<tr><td align="left"><a href="http://en.wikipedia.org/wiki/Longitude">Longitude</a>:</td><td><input size="18" type="text" id="lon" value="" ></td></tr>
<tr><td align="left" colspan="2"><input type="submit" value="Set" onClick="myMap.reMap($EL('address'));"></td></tr>
</table>
</body>
</html>