My supervisee Ed asks how to store and recreate google maps directions. The idea here is that on a site where logged-in members can create and share routes on maps, they should be able to save maps and edit them later, possibly even collaboratively. Specifically we're talking about using Google Maps, API version 3, and the Google Directions functionality.
This took a bit of figuring out. I thought at first it would be as simple as simply identifying and intercepting a JSON-encoded server response whenever the route was specified. In fact this is what was necessary, except for the bit where it's 'simple'.
I've adapted google's own example to illustrate the idea here. Alter the route on the first map, and you'll see it recreated on the second map:
Obviously the example above is set up to recreate the second map from the first on the same page, so if you view source, you'll see the source code for that. The principle is very much the same for recreating the second map from a set of instructions stored in a database. Here's how it can be done:
First set up your source map
var rendererOptions = {
draggable: true
};
var directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions);
var directionsService = new google.maps.DirectionsService();
var map;
var mapCenter = new google.maps.LatLng(50.75, -1.92);
var myOptions = {
zoom: 7,
mapTypeId: google.maps.MapTypeId.SATELLITE,
center: mapCenter
};
map = new google.maps.Map(document.getElementById("your_map_div"), myOptions);
directionsDisplay.setMap(map);
directionsDisplay.setPanel(document.getElementById("your_directions_panel"));
Next we want to detect changes to the route, and this is where we'll try to intercept the information. First of all we need to know what we're intercepting. When new directions are loaded, the DirectionsRenderer fires a 'directions_changed' event. We can capture this and intercept the response from the server. It is a DirectionsResult object, which the documentation notes, while it 'is "JSON-like," it is not strictly JSON, as it directly and indirectly includes LatLng objects.'
This is an issue for the plan to merely store the response - it is a Javascript Object with child objects and methods, rather than just a bit of JSON application/text. We'll need to rebuild those objects if we want to re-use it properly. Matthew Crumley suggests a nice workaround for this at stackoverflow, which involves using Doug Crockford's JSON2 javascript extension. This has two methods which will be useful to us: JSON.stringify() and JSON.parse().
JSON.stringify safely turns JSON structures into strings. It also has the handy capability of letting us specify a callback function to perform on all the fields it finds, so we can intervene in how they are stringified. So we'll first use JSON.stringify() to capture the LatLng objects in the DirectionsResult object and put placeholders in where they occur. This will allow us find them later, and rebuild them.
/* adapted from Matthew Crumley's contribution to stackoverflow:
http://stackoverflow.com/questions/3608545/how-to-serialize-deserialize-javascript-objects */
google.maps.event.addListener(directionsDisplay, 'directions_changed', function() {
/* grab the DirectionsResult object */
currentDirections = directionsDisplay.getDirections();
/* use Doug Crockford's json2 tools:
https://github.com/douglascrockford/JSON-js */
directionsResponseString = JSON.stringify(currentDirections, function(name, value) {
/* capture instances of LatLng objects
if (value instanceof google.maps.LatLng) {
/* replace them with a placeholder containing their values
return 'LatLngPlaceholder(' + value.lat() + ',' + value.lng() + ')';
} else {
return value;
}
});
/* and then here make your call to the server,
storing the contents of 'directionsResponseString'
and whatever other metadata you need to save */
});
Now you'll want to recreate the map with directions when your user returns to edit it. Once again, set up your map as above, and then populate it with your stored 'DirectionsResult'. You'll need to revivify the LatLng objects before you do so, and that's where json2.js's JSON.parse() function comes into play. JSON.parse() turns JSON strings into objects. Like JSON.stringify(), JSON.parse() too allows you to add in a callback function which iterates over each JSON name/value pair, whereupon you can look for your LatLng placeholders and replace them with real LatLng objects:
/* set up map as before
with the variable 'directionsDisplay' refering to your DirectionsRenderer */
/* retrieve your stored 'DirectionsResult' data
- this example assumes you put it in the variable called 'directionsResponseString' */
/* check 'directionsResponseString' exists */
if (directionsResponseString){
/* rebuild the DirectionsResult */
myDirections = JSON.parse(directionsResponseString, function(name, value) {
/* capture LatLng placeholders */
if (/^LatLngPlaceHolder\(/.test(value)) {
/* retrieve the lat/lng co-ordinates */
var match = /LatLngPlaceHolder\(([^,]+),([^,]+)\)/.exec(value);
/* replace the placeholder with a LatLng object */
return new google.maps.LatLng(match[1], match[2]);
} else {
return value;
}
});
/* populate the map with the newly recreated DirectionsResult object
directionsDisplay.setDirections(myDirections);
}
... and you're done. You can of course, make the second map editable, and capture 'directions_changed' events and store them when appropriate, in exactly the same way as with the first map.