Results tagged “api”

Four months ago Google released a new version of their Maps API, version 3, which promised substantial speed improvements and support for mobile browser among other benefits. It's a complete rewrite and its interface is significantly different from the current API. And yet it may still be possible to write modules that work with both APIs as this post shows.

As it turned out, the differences between the APIs are not as significant as I originally expected; although I only used a small chunk of the API, the result still provides a fully functional and potentially useful example.

I am going to use dynamic loading of the API to allow easy switching between the two versions. I am also going to set up a namespace to encapsulate the code generates the canvas class I'm using that inherits from different classes depending on the version of API being used. Namespacing your Javascript post provides a good starting point for those who may need get familiar with this approach.

Let's look at the differences between the APIs and how they can be resolved. The most impatient can scroll to the end of the post to see the result.

1. Map initialization:

if (version == 2) {
  map = new google.maps.Map2(el);
  map.addControl(new google.maps.SmallZoomControl3D());
  map.setCenter(new google.maps.LatLng(lat, lng), zoom);
}
else {
  map = new google.maps.Map(el, {
    zoom: zoom,
    center: new google.maps.LatLng(lat, lng),
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    mapTypeControl: false,
    scaleControl: false,
    navigationControlOptions: {style: google.maps.NavigationControlStyle.SMALL}
  });
}

Both versions use same namespace (google.maps), but they differ in how maps are created and initialized: v2 is using Map2() and setCenter() methods, whereas v3 is using one Map() call that accepts an object with all map setting as an additional parameter.

2. Adding overlay to the map:

(version == 2 ? map.addOverlay(canvasLayer) : canvasLayer.setMap(map));

v2 is using map.addOverlay() and v3 is using setMap() method of the object being added.

3. Overlay class to inherit from:

CanvasTileLayerOverlay.prototype = 
  (version == 2 ? new google.maps.Overlay() : new google.maps.OverlayView());

v2 has google.maps.Overlay class (same as GOverlay) and v3 provides OverlayView that exposes (almost) the same functionality. I set the prototype depending on which version is being used (which is passed as a parameter). It would be convenient to be able to retrieve the version number in some way from the API, but I couldn't find any way to do that.

4. Classes/method to use to get the map container and the current projection:

var div = (version  2 ? map : this.getProjection()).fromLatLngToDivPixel(center);
var c = (version  2 ? map.getContainer() : map.getDiv());

getContainer() method got replaced with getDiv(), but they both return a DOM object that contains the map. To convert LatLng coordinates to (div) pixel coordinates v2 provided the fromLatLngToDivPixel() method on the map object, but in v3, this method is in the projection object that can be retrieved from the overlay itself (this in this example refers to the overlay object itself).

5. Getting the pane to attach the element to:

(version == 2 ? this._map.getPane(google.maps.MAP_MAP_PANE) : this.getPanes().mapPane)
  .appendChild(this._container);

Notice that G_MAP_MAP_PANE got changed to google.maps.MAP_MAP_PANE in v2 because of the namespace change, but v3 is using the getPanes() method and specific pane types. They both return the DOM element to attach to.

6. Methods that get triggered by various events:

initialize became onAdd, remove became onRemove, and redraw is simply draw in v3. Also notice that parameters for some methods have changed too: for example, initialize gets the map object as a parameter in v2, but onAdd doesn't have any parameters in v3 (as documented).

7. Events and event methods:

The event model had several changes in the version overhaul: 1) the namespace (GEvent/google.maps.Event in v2 and google.maps.event in v3), 2) provided methods (for example, bind is not curently offered, but can be implemented as addListener(map, event, function(){return object.method.apply(object, arguments)}), and 3) events themselves; some events got added and some others have changed their behavior.

The most notable difference is probably that some functionality is not yet available in the new version (and some may never be, like GDownloadUrl that provides functions widely available elsewhere).

Here is the complete example that allows you to see it all in action and easily switch between the two APIs. It also remembers the map position and returns to the same position when the API is switched. Its functionality is rather simple -- it displays a grid that is aligned with map tiles -- but its logic is not quite that simple as it calculates tile positions and manipulates DOM elements to show tiles that are currently visible.

This code has been seen working in FF 3.5, IE 7 (with ExCanvas) and iPhone 3G Safari.

Google Maps provides two ways to load its API. One is static, the simplest one that looks like this:

<script  type="text/javascript"
  src="http://maps.google.commaps?file=api&amp;v=2&amp;sensor=false&amp;key=ABCDEF..."></script>
<script type="text/javascript">
  var map = new GMap2(document.getElementById("map"));
  map.setCenter(new GLatLng(33.52392, -111.90884), 12);
  var myOverlay = new MyOverlay();
  map.addOverlay(myOverlay);
  ...
</script>
<body onunload="GUnload()">

The second one, dynamic, requires a bit more work:

<script type="text/javascript" 
  src="http://www.google.com/jsapi?key=ABCDEF..."></script>
<script type="text/javascript">
  google.load("maps", 2, {other_params:"sensor=false"});
  google.setOnLoadCallback(function() {
    var map = new google.maps.Map2(document.getElementById("map"));
    map.setCenter(new google.maps.LatLng(33.52392, -111.90884), 12);
    var myOverlay = new MyOverlay();
    map.addOverlay(myOverlay);
    ...
  });
</script>
...
<body>

Notice that there are several changes:

1. the source of the API script changed to http://www.google.com/jsapi?key=ABCDEF...; the version and sensor parameters are not present
2. the module to load, the version, and other parameters are specified in google.load call: google.load("maps", 2, {other_params:"sensor=false"});
3. the callback function that is called when the module is loaded is specified with google.setOnLoadCallback() call
4. onunload is no longer needed

The interface is documented here and instructions are easy to follow. In addition to asynchronous loading, the new method also provides a new namespace, google.maps, to be used instead of the old G* namespace. For example, GMap2 is replaced with google.maps.Map2, GOverlay with google.maps.Overlay and G_MAP_MAP_PANE with google.maps.MAP_MAP_PANE.

One of the pitfalls that I encountered was that comparing to static loading, dynamic loading is done asynchronously and all the code that needs to run after the API is loaded has to be run from the callback function specified in setOnLoadCallback().

What I initially attempted to do didn't work:

google.load("maps", 2, {other_params:"sensor=false"});
function myOverlay () {}
myOverlay.prototype = new google.maps.Overlay();
myOverlay.prototype.initialize .....
.. all other code to add to myOverlay
google.setOnLoadCallback(function() {
  var map = new google.maps.Map2(document.getElementById("map"));
  map.setCenter(new google.maps.LatLng(33.52392, -111.90884), 12);
  var myOverlay = new MyOverlay();
  map.addOverlay(myOverlay);
 ...
});

Notice that new google.maps.Overlay() code is executed right after google.load() call, but likely before the API code is actually loaded and you get something like "google.maps.Overlay is not a constructor".

Simply moving the code into the callback still doesn't work:

google.load("maps", 2, {other_params:"sensor=false"});
google.setOnLoadCallback(function() {
  var map = new google.maps.Map2(document.getElementById("map"));
  map.setCenter(new google.maps.LatLng(33.52392, -111.90884), 12);
  var myOverlay = new MyOverlay();
  map.addOverlay(myOverlay);
  ...
  function myOverlay () {}
  myOverlay.prototype = new google.maps.Overlay();
  myOverlay.prototype.initialize .....
  .. all other code to add to myOverlay
});

This still gives an error, albeit a different one this time: "a.initialize is not a function". This happens because the prototype initialization code -- myOverlay.prototype = new google.maps.Overlay(); -- happens after the class is being used.

Moving map.addOverlay(myOverlay); after the prototype is initialized makes things work. Here is a complete example that shows how this all works together. It is based on google's own example that displays a rectangle overlay; I changed the loading method to use AJAX and updated all G* methods to use the new namespace.

For the last several weeks I've been experimenting with building interactive maps using the Google Maps API and decided to document some of the results and challenges I've had. There are already several excellent repositories of Google Maps tutorials and examples, the most notable and comprehensive being Mike Williams's maps tutorial and Esa Ilmari's list of examples, so I'll try to focus on those things that I haven't seen covered elsewhere.

These are the topics I plan to cover:

  • Dynamic loading of the API
  • Using multiple versions of the API
  • Canvas processing
  • How to turn a web page into a mobile app
  • Map animation using processing.js
  • Internet Explorer support for canvas drawing
  • Map tiling algorithm
  • Using canvas.toDataURL for data caching
  • DownloadURL implementation with partial processing
  • Drawing a heat map

About

I am Paul Kulchenko.
I live in Kirkland, WA with my wife and three kids.
I work for Six Apart as a software developer.
I study machine learning and artificial intelligence.
I write books and open-source software.

Recommended

Close