Earlier this week Google Maps added Mapplets functionality. The feature lets third-party developers offer their GMaps mashups as toggleable layers that can be added to the GMaps interface.
In addition to my duties here at EchoDitto, I also do some work for DCist as a hobby — in fact, I'm pretty sure that my work on this Google Map of the DC subway system helped land me my job. That app has languished, though, functional but limited and still stuck in version 1 of the GMaps API.
So I took a stab at porting it over to the Mapplet format. It went pretty well — you can see the results by clicking here. There were a number of API changes to contend with, though.
First, there was the transition to the GMap2 object, which most of you have probably already done. The Mapplet interface makes initialization much easier — you can count on the map object to already be instantiated — but other parts were confusing. Most notably, Google appears to have finally un-switched the order of the latitude and longitude parameters in their function calls. That's a welcome change, but figuring out how to undo (or double down on) the kludgy solution I was using made things pretty complicated. The subway map was displaying in Antarctica for a little while.
The actual Mapplet is implemented as an XML file based on the Google Gadget spec. You can find an example here, in the Mapplet documentation. It's a pretty straightforward format to work with, although documentation on some of the Google Gadget metadata tags can be a little unclear.
There are two big changes from v2 of the GMaps API that developers should be aware of. First, functions that query the map's state now must be made asynchronously. There's no more GMap2.getSize(), so code like this won't work:
var MAP_WIDTH = map.getSize().width;
Instead you must append "Async" to the method name and provide a callback function, like so:
var MAP_WIDTH = 0;
map.getSizeAsync(function(size){
MAP_WIDTH = size.width;
});
You can find more information about these asynchronous methods here. One unforeseen consequence can be that, depending on the speed of the asynchronous operation and your Javascript interpreter, some values may or may not be available when they're needed, opening the possibility of race conditions. A simple way to solve this problem is to use timeouts to test the variable's availability:
var MAP_WIDTH = 0;
map.getSizeAsync(function(size){
MAP_WIDTH = size.width;
});
function DoStuffOnceWidthIsAvailable()
{
if(MAP_WIDTH>0)
DoTheAforementionedStuff(MAP_WIDTH);
else
setTimeout(DoStuffOnceWidthIsAvailable,100);
}
DoStuffOnceWidthIsAvailable();
The second problem that presented itself is related to Google proxying AJAX queries. My subway map uses an XML file to define the location of stations and subway lines. The trickiest part of my code relates to drawing subway lines that run together, like the orange and blue lines: because lines in Google Maps are specified in terms of latitude and longitude, it's necessary to convert the pixel offset to degrees, and to do so in a direction perpendicular to the slope of the line. It's all pretty simple geometry, but part of the operation involves taking a square root, which loses information about whether the slope of the line is positive or negative. It's possible to figure this out with some more math, but I wanted to optimize my code as much as possible. So instead I just specified an offset constant for each line segment in the XML file — if the square root operation for that segment happens to return the wrong result (which it will either do or not do reliably), I just modify the constant in the XML file and the script multiplies the output appropriately.
It's a little hacky, but it's fast. However, it also means that getting the lines aligned properly involves a one-time commitment to fiddling with the XML, saving it, then checking it by reloading the map. Most of the work was already done, but I needed to account for the Yellow Line's recent extension to Ft. Totten. The fiddling wasn't going so well — the changes weren't reliably showing up on the map, and I couldn't figure out what was going on.
The answer, of course, was that Google was caching the file. Because the Mapplet will be displayed on a web page hosted at a google.com address, your browser can only issue AJAX queries to Google. They then have to go fetch the remote file — in this case, the station XML file. Google understandably caches the file to improve performance, and that was confusing the issue.
But invalidating the cache was easy: just add a querystring. Each time I made a change to the XML, I incremented a parameter:
var station_xml_url = "http://www.dcist.com/map/stations.xml?q=1"; var station_xml_url = "http://www.dcist.com/map/stations.xml?q=2"; var station_xml_url = "http://www.dcist.com/map/stations.xml?q=3"; ...
And so on. Google helpfully caches by the complete URL, so you'll get a new XML file each time.
That's about it. It was more work that I expected, but creating the Mapplet was still relatively painless. Have a look through the documentation and get cracking — I think the Mapplet concept promises to move mashups past novelty status and into everyday usefulness.
Post new comment