A fast and simple async API for Google Mapplets
Update 2007-06-22: Version 2 now supports portable code that runs as both a mapplet and a Maps API app. Read about the update.
If you’re building a Google Mapplet that responds to map movement and resizing, you will soon find yourself writing code like this recent gem of mine:
What is going on here? I have a
search() function that takes the current map size, bounds and center, runs a search and displays pins on the map. In a normal Google Maps application I could have simply coded:
But a Google Mapplet lives in a strange and different world. To isolate mapplet code from the Google domain, Google runs the mapplet in an IFRAME loaded from the gmodules.com domain. Cross-domain browser security prevents your code from communicating directly with the Google Maps frame loaded from maps.google.com.
The mapplet API uses the iframe fragment hack to allow limited communication between the mapplet and the Google map. This has two consequences:
The communication is asynchronous. This doesn’t affect the API for functions that simply set map state—they operate on a “fire and forget” basis. But functions that return information can’t do it directly. You have to provide a callback function that receives the information when it is ready.
The communication is slow. Everything is serialized through the fragment identifier (hash) of a hidden IFRAME. The map page and the mapplet frame each have interval timers running to watch for changes to this hash. A single
getSomethingAsync()function call requires all these steps:
- Mapplet frame sets the hash to represent the function call.
- Map page timer wakes up, makes the actual Maps API call, and sets the hash to represent the return value.
- Mapplet frame timer wakes up, gets the value from the hash, and calls the callback function.
My code listed above makes three of these round trips to the maps page one after the other, because the callback for each function triggers the next step in the series. That’s a lot of timeouts—enough to cause a noticeable delay.
What if we could somehow combine all three information requests into a single round trip? That should speed things up quite a bit. Imagine a different Mapplet async API where you provide a list of Maps API functions and get back all of their responses in a single callback with multiple arguments. My three nested function calls and callbacks could be reduced to:
(The sharp-eyed reader will note that the
search() function could be used directly as the callback because it takes the same arguments:
But we’ll stick with the longer form for this discussion, because it makes it clear what the function arguments are.)
While we’re at it, we can provide a way to retrieve information for more than one object in a single call:
And for functions such as
map.fromContainerPixelToLatLngAsync() which take an additional argument, we can allow an optional arguments array after any function name:
Compare that with the equivalent nested functions using the existing API, which would take about three times longer to run:
Good news: We don’t have to wait for Google to implement this zippy
GAsync API or something like it. Although the public API only exposes individual
xyzAsync() functions, the underlying iframe fragment dispatcher can queue up multiple function calls and return values into a single round trip.
The Google Maps team was kind enough to provide me with a nifty
makeBarrier() function that allows us to queue up a number of async calls and get a single callback when all their values are ready. Using this function, my first example can be written as:
As you can see, it’s up to us to count the functions and keep track of the values, but having done that, we can get all three values in a single round trip through the API. It’s literally three times faster than the nested API calls.
Armed with that information, could we code
GAsync() as a layer on top of the existing mapplet async APIs? Indeed we can!
You can try out the code right now and see the speed difference with my test mapplet. Go to the Google Maps Developer Preview page, log into your Google account, and click the Add Content link under the Mapplets tab (or click the Browse Content button if that is what is there).
The next page will show a number of existing mapplets. Click the tiny Add by URL link next to the search button at the top of the page, and paste this URL into the URL box that opens up. You can also click this link to see the mapplet source code:
(When you paste the link, make sure the http:// isn’t duplicated because of the text already in the box.)
Click the Add button and click OK on the confirmation dialog. Then click Back to Google Maps at the top left corner of the page, and you should see a new entry titled A fast simple mapplet async API. Click it to load the mapplet.
An info window should open in the map, displaying several items of information about the map, and the time it required to collect the information using the
GAsync() API. Then try the Slow Async API radio button to see the performance using nested async calls.
GAsync code used in the test mapplet is:
and the corresponding nested async code is:
Finally, here is the
GAsync source code. First, a compact version suitable for pasting into your own mapplet (or download async.js):
And a heavily commented version that explains how it works:
Of course, there are still cases where you will have to run one async call after another one. If you need one piece of information as input to a subsequent call, nested async functions are the way to do it. Even then, it may be possible to combine some other async calls into a single
GAsync call, wherever they don’t depend on each other’s results. You’ll shave about a quarter second off your mapplet’s response time for every call you combine using
Enjoy your simpler and faster mapplet code!
Thanks to Ben A. of Google for mapplet design and coding tips.