views:

599

answers:

2

I'm trying to plot lines between two or more addresses in Google Maps. The idea is that addresses are in an array (in the order of the route taken), then the array is looped over and geocoded via Google Maps, so I then have an array of co-ordinates:

function showRoute(routes) {
    var points = [];
    var geocoder = new GClientGeocoder();
    for (var i = 0; i < routes.length; i++) {
        geocoder.getLatLng(routes[i], function(point) {
            points.push(point);
            drawRoute(points, routes);
        });
    }
}

The drawRoute() function will only run through if the array passed to it is the same as the length of the original routes array.

Most of the time this works fine. However, it will break if there's a lag in getting a response from the geocoder for any value, as it'll result in them being out of order.

My first attempt around this problem was as follows:

function showRoute(routes) {
    var points = [];
    var geocoder = new GClientGeocoder();
    for (var i = 0; i < routes.length; i++) {
        geocoder.getLatLng(routes[i], function(point) {
            points[i] = point;
            drawRoute(points, routes);
        });
    }
}

But as the function runs asynchronously, it means that the value of i inside the geocode callback is always going to be the length of the routes array, so it'll just keep overwriting the same value.

Anyone got any ideas?

+2  A: 

You want to do each geocode one at a time, and have the callback invoke the next geocode. This will guarantee the ordering.

E.g.:

var map = null;
var geocoder = null;
var next = 0;
var points = [];

var addresses = [
  "1521 1st Ave, Seattle, WA",
  "2222 2nd Ave, Seattle, WA",
  "14 Mercer St, Seattle, WA"
];

function initialize() {
  if (GBrowserIsCompatible()) {
    map = new GMap2(document.getElementById("map_canvas"));
    map.setCenter(new GLatLng(47.61630, -122.34546), 13);
    map.setUIToDefault();
    geocoder = new GClientGeocoder();
    geocodeAll();
  }
}

function geocodeAll() {
  if (next < addresses.length) {
    // Do the next one.
    geocoder.getLatLng(addresses[next], callBack);
  } else {
    doneCb();  // All done.
  }
  next += 1;
}

function callBack(point) {
  points.push(point);
  geocodeAll();
}

// Called when all done.
function doneCb() {
  // Add a polyline connecting the dots.
  var poly = new GPolyline(points);
  map.addOverlay(poly);

  // Add some markers over the points.
  for (var i = 0; i < points.length; ++i) {
    map.addOverlay(new GMarker(points[i]));
  }
}
Alex
This was my first reaction as well (in fact I have used this approach in the past), but I think you can avoid doing the requests sequentially.
Cannonade
Thanks for the idea; I was trying to avoid going sequentially though as it's slower.
David M
+3  A: 

Ok. So I had to do a search on SO and re-read how closures work before I answered this question (seems like I always have to do that when ever I think about closures).

The value of the index variable is set when the outer function returns (that is showRoute in this case). So I figured, all you need to do is wrap up your getLatLng in another function that takes the index as a parameter:

function processPoint (geocoder, routes, i, points)
{
    geocoder.getLatLng(routes[i], function(point) {
        points[i] = point;
        drawRoute (points, routes);
        });
}

So your loop in showRoute becomes:

var geocoder = new GClientGeocoder();
for (var i = 0; i < wp.length; i++) 
     processPoint (geocoder, routes, i, points);

This way the getLatLng callback has access to the appropriate index value. I did a quick example which demonstrates this working. You can find the source for this here.

Cannonade
Thankyou! This is exactly what I was trying to do.
David M
Good stuff :). Thanks for the great question. It prompted me to look into this properly.
Cannonade