Google geocoding multiple addresses in a loop with javascript, how do I know when everything is done?
Asked Answered
C

4

14

I've got a form that asks for a list of locations (not a lot, normally only 3 or 4 but that number is dynamic). When the form is submitted, I have to parse the data, use Google geocoding to get the locations, and then draw a line connecting the points in order. I have the parsing working, but I'm stuck on the geocoding part, mostly because of the asynchronous nature of it. Assume my address strings are stored in the array 'addresses', this is how far I've gotten:

function someFunction(addresses) {
  var coords = [];
  for(var i = 0; i < addresses.length; i++) {
    currAddress = addresses[i];
    var geocoder = new google.maps.Geocoder();
    if (geocoder) {
      geocoder.geocode({'address':currAddress}, function (results, status)
        if (status == google.maps.GeocoderStatus.OK) {
          coords.push(results[0].geometry.location);
        } 
        else {
          throw('No results found: ' + status);
        }
      });
    }
  }
  // Initially I tried to work with the data here, but it wasn't all present yet.
}

Drawing the line is easy enough, I've done that before when users provided geographic lat/lng coordinates. My problem is, because the coordinates are only added in the callback, how do I know when it's done? I can't just dump that into a function and put in the callback because I need to wait until all the coordinates have been processed.

I also read about someone who had issues with results not coming back in order but I didn't understand the provided response. If someone has an answer that can help me with my specific issue and ensure the results come back in order, I would greatly appreciate.

NB: I hand-bombed that code, so there may be typos. My actual code thus far "works", I just don't know who to move from what I have to doing something once all addresses are processed. Also, this is currently being developed as an internal application for testing. Once testing is finished, it will comply fully with Google's TOS. This means I don't have a page I can link to. The entire application is also over 2,000 lines of code and contains some proprietary company information at this moment which will eventually be phased out, so pasting the entire thing or sending it out isn't feasible. I hope that doesn't pose too big a problem.

Cephalization answered 29/1, 2012 at 10:13 Comment(2)
Can you provide a short example of how to do that? I come from a synchronous background and all this callback/asynchronous stuff makes my head hurt if it isn't explained with a big box of Crayola crayons.Cephalization
@InspiredJW: no, I don't agree. You would process one item after another which most likely will take quite a bit longer than invoking them in parallel (limited by the XHR channels).Neither
M
14
function someFunction(addresses, callback) {
    var coords = [];
    for(var i = 0; i < addresses.length; i++) {
        currAddress = addresses[i];
        var geocoder = new google.maps.Geocoder();
        if (geocoder) {
            geocoder.geocode({'address':currAddress}, function (results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    coords.push(results[0].geometry.location);
                    if(coords.length == addresses.length) {
                        if( typeof callback == 'function' ) {
                            callback();
                        }
                    }
                } 
                else {
                    throw('No results found: ' + status);
                }
            });
        }
     }
  }
}

//Usage
someFunction(addresses, function() {
    // Do something after getting done with Geocoding of multiple addresses
});

Use of Callback function is amazing

Mana answered 29/1, 2012 at 16:53 Comment(3)
Note that one problem with this is if a problem occurs with geocoding one or more of the addresses, the callback will never call (because it won't ever be of the same size as the addresses array). Still, you can easily tweak the code to accommodate it, using (say) another local array/counter.Brno
@Brno using async module can be a good way to handle this kind of problem.Mana
Geocoder is subject to a rate limit of 50 requests per second. Depending on the number of addresses, and time taken to loop, this might fail.Samothrace
N
8

You could check if all calls have been finished by comparing the number of results to the number of addresses:

function someFunction(addresses) {
    var currAddress, coords = [];
    for (var i = 0; i < addresses.length; i++) {
        currAddress = addresses[i];
        var geocoder = new google.maps.Geocoder();
        if (geocoder) {
            geocoder.geocode({'address':currAddress}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    coords.push(results[0].geometry.location);

                    // Check if all calls have been processed
                    if (coords.length == addresses.length) {
                        someOtherFunction(coords);
                    }
                }
                ...
            });
        }
    }
}

function someOtherFunction(coords) {
    // Geocoding has been done for all addresses
    ...
}
Neither answered 29/1, 2012 at 11:14 Comment(0)
Q
1

If you are utilizing libraries such as jQuery, you could take advantage of Deferred Object to perform a chainable requests via geocoder.geocode function.

Example

function initMap() {
   
    var geocoder = new google.maps.Geocoder();

    var addreses = [
    { "lat": 60.173890, "lng": 24.941025 },
    { "lat": 60.461608, "lng": 22.266598 },
    { "lat": 61.498714, "lng": 23.760940 }
    ];

    var deferreds = getGeocodeAddressDeferred(geocoder,addreses);
    $.when.apply($, deferreds).done(function (locations) {

        //print results
        $.each(arguments, function (i, data) {
            $("div#result").append(data + "<br/>");
        });
    });
}
function getGeocodeAddressDeferred(geocoder, addreses) {
    var deferreds = [];
    
    $.each(addreses, function (i,address) {
        deferreds.push(geocodeAddress(geocoder, address));
    });

    return deferreds;
}

function geocodeAddress(geocoder, latLng) {
    var deferred = $.Deferred();
    geocoder.geocode({ 'location': latLng }, function (results, status) {
        if (status === google.maps.GeocoderStatus.OK) {
            deferred.resolve(results[0].formatted_address);
        } 
    });
    return deferred.promise();
}
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    <script src="https://maps.googleapis.com/maps/api/js?callback=initMap"
            async defer></script>
<div id="result"/>
Quicksand answered 13/9, 2015 at 21:17 Comment(0)
E
-1

This is quite old but there's a problem of closure in the given answers so final result has same lat,long for all addresses.

It's better to loop through all addresses using addresses.forEach so that each geocoded address is contained and this should work fine provided target browsers are modern. Otherwise, you have to define an outside function to tackle the javascript infamous loop issue. Here is forEach solution for an array of addresses:

            var getLatLng = function(addresses, callback) {
            var coords = [];

            addresses.forEach(function(address) {

                var geocoder = new google.maps.Geocoder();
                geocoder.geocode({'address': address}, function (results, status) {

                    if (status == google.maps.GeocoderStatus.OK) {
                        var lat = results[0].geometry.location.lat();
                        var lng = results[0].geometry.location.lng();
                        coords.push([lat, lng]);

                        // all addresses have been processed
                        if (coords.length === addresses.length)
                            callback(coords);
                    }
                });
            });
        }

        getLatLng(allAddresses, function (results) {
            console.log("received all addresses:", results);
        });
Effeminacy answered 3/1, 2016 at 7:30 Comment(1)
A valid concern, but none of the provided answers seems to actually use the running variable in the callback as you claim, so your comment isn't really correct. It is also not an answer to the question.Neither

© 2022 - 2024 — McMap. All rights reserved.