43

I'm hitting an issue that is WELL discussed in these forums, but none of the recommendations seem to be working for me so I'm looking for some full javascript that works when saved as an html file.

The issue is I keep hitting the OVER_QUERY_LIMIT error when trying to geocode > 11 locations on a Google Map using the V3 APIs called by Javascript. I understand that there is a limit to the rate at which you can call the geocoder (as well as the daily limit on total volume), so I need to introduce a pause in between each result in the array.

Any help very much appreciated.

Here is my code:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
var geocoder;
var map;
var wait = false;


  function initialize() {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(51.32, 0.5);



var myOptions = {
  zoom: 8,
  center: latlng,
  mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
codeAddress('KT16 8LA' + ', UK');
codeAddress('LS8 2LQ' + ', UK');
codeAddress('NE13 8AF' + ', UK');
codeAddress('KT12 2BE' + ', UK');
codeAddress('W1W 8AN' + ', UK');
codeAddress('EC3N 2LS' + ', UK');
codeAddress('BS9 3BH' + ', UK');
codeAddress('KA10 6LZ' + ', UK');
codeAddress('EC1V 9BW' + ', UK');
codeAddress('WD18 8YN' + ', UK');
codeAddress('HA3 6DQ' + ', UK');
codeAddress('W1U 3PL' + ', UK');
codeAddress('W1T 7QL' + ', UK');
codeAddress('W1S 1TD' + ', UK');
codeAddress('SW1X 8NX' + ', UK');
codeAddress('LE2 8ET' + ', UK');
codeAddress('BA3 4BH' + ', UK');
codeAddress('AL3 8JP' + ', UK');
codeAddress('DE55 4QJ' + ', UK');
codeAddress('W6 0QT' + ', UK');
codeAddress('LA1 1PP' + ', UK');
codeAddress('SW16 4DH' + ', UK');
codeAddress('WC2N 6DF' + ', UK');
codeAddress('RM6 6LS' + ', UK');
codeAddress('S25 3QZ' + ', UK');
codeAddress('WC2H 7LR' + ', UK');
codeAddress('BH24 1DW' + ', UK');
codeAddress('EC2N 6AR' + ', UK');
codeAddress('W1U 2FA' + ', UK');
codeAddress('B60 3DX' + ', UK');    
}

  function codeAddress(vPostCode) {
if (geocoder) {
  geocoder.geocode( { 'address': "'" + vPostCode + "'"}, function(results, status) {
    if (status == google.maps.GeocoderStatus.OK) {
      map.setCenter(results[0].geometry.location);
      var marker = new google.maps.Marker({
          map: map, 
          position: results[0].geometry.location
      });
    } else {
      alert("Geocode was not successful for the following reason: " + status);
    }
  });
}
}

</script>
<body style="margin:0px; padding:0px;" onload="initialize()">
<div id="map_canvas" style="width:100%; height:90%"></div>
</body>

EDIT: This is what I've tried to do to get it to pause/wait in the relevant section, but it doesn't do anything:

function codeAddress(vPostCode) {
    if (geocoder) {
    while (wait) { /* Just wait. */ };
      geocoder.geocode( { 'address': "'" + vPostCode + "'"}, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
          map.setCenter(results[0].geometry.location);
          var marker = new google.maps.Marker({
              map: map, 
              position: results[0].geometry.location
          });
        /* When geocoding "fails", see if it was because of over quota error: */
        } else if (status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) { 
        wait = true;
        setTimeout("wait = true", 2000);
        //alert("OQL: " + status);
        } else {
          alert("Geocode was not successful for the following reason: " + status);
        }
      });
    }
  }
2

6 Answers 6

45

Nothing like these two lines appears in Mike Williams' tutorial:

    wait = true;
    setTimeout("wait = true", 2000);

Here's a Version 3 port:

http://acleach.me.uk/gmaps/v3/plotaddresses.htm

The relevant bit of code is

  // ====== Geocoding ======
  function getAddress(search, next) {
    geo.geocode({address:search}, function (results,status)
      { 
        // If that was successful
        if (status == google.maps.GeocoderStatus.OK) {
          // Lets assume that the first marker is the one we want
          var p = results[0].geometry.location;
          var lat=p.lat();
          var lng=p.lng();
          // Output the data
            var msg = 'address="' + search + '" lat=' +lat+ ' lng=' +lng+ '(delay='+delay+'ms)<br>';
            document.getElementById("messages").innerHTML += msg;
          // Create a marker
          createMarker(search,lat,lng);
        }
        // ====== Decode the error status ======
        else {
          // === if we were sending the requests to fast, try this one again and increase the delay
          if (status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
            nextAddress--;
            delay++;
          } else {
            var reason="Code "+status;
            var msg = 'address="' + search + '" error=' +reason+ '(delay='+delay+'ms)<br>';
            document.getElementById("messages").innerHTML += msg;
          }   
        }
        next();
      }
    );
  }
17
  • Also works great for routing, when batching just recurse into that batch call to Google after increasing delay.
    – Brent
    Commented May 28, 2014 at 16:42
  • what is this.. @Andrew Leach would you kindly tell me what are these delay and next address..Could you please solve this Fiddle
    – Prabs
    Commented May 1, 2015 at 11:16
  • delay is delay in milliseconds; nextAddress is the address counter: if an address fails, unset the increment and increase the delay in order that it can be tried again. The full implementation is on my site at the link given: you have nothing like this code in your Fiddle. Commented May 1, 2015 at 11:23
  • Is it possible to set the center of the map after the first result?
    – user43251
    Commented May 18, 2015 at 13:08
  • 1
    @PaulLeclerc My example still works for me, although the geocoder has changed so fewer addresses are actually found. If you are using a shared IP address, the query limit is shared between all users on that IP address. That means that users of mobile networks can fall foul of the limit before they start. Commented Jan 5, 2017 at 10:48
9

The general answer to this question is:

Don't geocode known locations every time you load your page. Geocode them off-line and use the resulting coordinates to display the markers on your page.

The limits exist for a reason.

If you can't geocode the locations off-line, see this page (Part 17 Geocoding multiple addresses) from Mike Williams' v2 tutorial which describes an approach, port that to the v3 API.

5
  • Thanks for the response. I can't geocode them off-line, but the page you've referenced doesn't give any example I can apply. All I really need is a delay/pause step that will work. Commented Aug 3, 2012 at 10:11
  • Did you see this: view-source:econym.org.uk/gmap/example_geomulti.htm, it is written for the v2 API, but the concept should apply to the v3 API.
    – geocodezip
    Commented Aug 3, 2012 at 10:26
  • Have tried that concept but without success. It may well work in V2 and V3 but I can't get it to apply successfully to the above Javascript. Commented Aug 3, 2012 at 11:21
  • You could post a link to your map (or a jsfiddle) where you tried to get it to work and failed, maybe someone can see where you are going wrong.
    – geocodezip
    Commented Aug 3, 2012 at 11:40
  • Yep, based on the above code it would work for the first 80-ish page views and then break using the free usage. 3300 page views if your paying for it. Commented May 7, 2014 at 13:35
4

Here I have loaded 2200 markers. It takes around 1 min to add 2200 locations. https://jsfiddle.net/suchg/qm1pqunz/11/

//function to get random element from an array
    (function($) {
        $.rand = function(arg) {
            if ($.isArray(arg)) {
                return arg[$.rand(arg.length)];
            } else if (typeof arg === "number") {
                return Math.floor(Math.random() * arg);
            } else {
                return 4;  // chosen by fair dice roll
            }
        };
    })(jQuery);

//start code on document ready
$(document).ready(function () {
    var map;
    var elevator;
    var myOptions = {
        zoom: 0,
        center: new google.maps.LatLng(35.392738, -100.019531), 
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    map = new google.maps.Map($('#map_canvas')[0], myOptions);

    //get place from inputfile.js
    var placesObject = place;
    errorArray = [];

  //will fire 20 ajax request at a time and other will keep in queue
    var queuCounter = 0, setLimit = 20; 

  //keep count of added markers and update at top
  totalAddedMarkers = 0;

  //make an array of geocode keys to avoid the overlimit error
    var geoCodKeys = [
                    'AIzaSyCF82XXUtT0vzMTcEPpTXvKQPr1keMNr_4',
                    'AIzaSyAYPw6oFHktAMhQqp34PptnkDEdmXwC3s0',
                    'AIzaSyAwd0OLvubYtKkEWwMe4Fe0DQpauX0pzlk',
                    'AIzaSyDF3F09RkYcibDuTFaINrWFBOG7ilCsVL0',
                    'AIzaSyC1dyD2kzPmZPmM4-oGYnIH_0x--0hVSY8'                   
                ];

  //funciton to add marker
    var addMarkers = function(address, queKey){
        var key = jQuery.rand(geoCodKeys);
        var url = 'https://maps.googleapis.com/maps/api/geocode/json?key='+key+'&address='+address+'&sensor=false';

        var qyName = '';
        if( queKey ) {
            qyName = queKey;
        } else {
            qyName = 'MyQueue'+queuCounter;
        }

        $.ajaxq (qyName, {
            url: url,
            dataType: 'json'
        }).done(function( data ) {
                    var address = getParameterByName('address', this.url);
                    var index = errorArray.indexOf(address);
                    try{
                        var p = data.results[0].geometry.location;
                        var latlng = new google.maps.LatLng(p.lat, p.lng);
                        new google.maps.Marker({
                            position: latlng,
                            map: map
                        });
                        totalAddedMarkers ++;

            //update adde marker count
                        $("#totalAddedMarker").text(totalAddedMarkers);
                        if (index > -1) {
                            errorArray.splice(index, 1);
                        }
                    }catch(e){
                        if(data.status = 'ZERO_RESULTS')
                            return false;

            //on error call add marker function for same address
            //and keep in Error ajax queue
                        addMarkers( address, 'Errror' );
                        if (index == -1) {
                            errorArray.push( address );
                        }
                    }
        });

    //mentain ajax queue set
        queuCounter++;
        if( queuCounter == setLimit ){
            queuCounter = 0;
        }
    }

  //function get url parameter from url string
    getParameterByName = function ( name,href )
    {
      name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
      var regexS = "[\\?&]"+name+"=([^&#]*)";
      var regex = new RegExp( regexS );
      var results = regex.exec( href );
      if( results == null )
        return "";
      else
        return decodeURIComponent(results[1].replace(/\+/g, " "));
    }

  //call add marker function for each address mention in inputfile.js
    for (var x = 0; x < placesObject.length; x++) {
        var address = placesObject[x]['City'] + ', ' + placesObject[x]['State'];
        addMarkers(address);
    }
});
2
  • 2
    The ToS has certain restrictions you should be aware of: "No use beyond transaction limits and usage policies. If your Maps API Implementation generates a high volume of transactions, Google reserves the right to set transaction limits. Google also reserves the right to set other usage policies in the Documentation from time to time. If you want to engage in use outside these transaction limits or usage policies, you can purchase more usage capacity through the Maps API Standard pricing plan, or you can contact the Google Maps sales team for licensing options to address your needs."
    – xomena
    Commented Nov 7, 2016 at 11:08
  • @sachingavas I am getting name.replace is not a function error message
    – QWERTY
    Commented Dec 16, 2017 at 12:22
3

Using "setInterval" & "clearInterval" fixes the problem:

function drawMarkers(map, markers) {
    var _this = this,
        geocoder = new google.maps.Geocoder(),
        geocode_filetrs;

    _this.key = 0;

    _this.interval = setInterval(function() {
        _this.markerData = markers[_this.key];

        geocoder.geocode({ address: _this.markerData.address }, yourCallback(_this.markerData));

        _this.key++;

        if ( ! markers[_this.key]) {
            clearInterval(_this.interval);
        }

    }, 300);
}
1

this post was made a while ago, but it provides an answer that did not solve the problem regarding reaching the limit of requests in an iteration for me, so I publish this, to help who else has not served.

My environment happened in Ionic 3.

Instead of making a "pause" in the iteration, I ocurred the idea of ​​iterating with a timer, this timer has the particularity of executing the code that would go in the iteration, but will run every so often until it is reached the maximum count of the "Array" in which we want to iterate.

In other words, we will consult the Google API in a certain time so that it does not exceed the limit allowed in milliseconds.

// Code to start the timer
    this.count= 0;
    let loading = this.loadingCtrl.create({
      content: 'Buscando los mejores servicios...'
    });
    loading.present();
    this.interval = setInterval(() => this.getDistancias(loading), 40);
// Function that runs the timer, that is, query Google API
  getDistancias(loading){
    if(this.count>= this.datos.length){
      clearInterval(this.interval);
    } else {
      var sucursal = this.datos[this.count];
      this.calcularDistancia(this.posicion, new LatLng(parseFloat(sucursal.position.latitude),parseFloat(sucursal.position.longitude)),sucursal.codigo).then(distancia => {
    }).catch(error => {
      console.log('error');
      console.log(error);
    });
    }
    this.count += 1;
  }
  calcularDistancia(miPosicion, markerPosicion, codigo){
    return new Promise(async (resolve,reject) => {
      var service = new google.maps.DistanceMatrixService;
      var distance;
      var duration;
      service.getDistanceMatrix({
        origins: [miPosicion, 'salida'],
        destinations: [markerPosicion, 'llegada'],
        travelMode: 'DRIVING',
        unitSystem: google.maps.UnitSystem.METRIC,
        avoidHighways: false,
        avoidTolls: false
      }, function(response, status){
        if (status == 'OK') {
          var originList = response.originAddresses;
          var destinationList = response.destinationAddresses;
          try{
            if(response != null && response != undefined){
              distance = response.rows[0].elements[0].distance.value;
              duration = response.rows[0].elements[0].duration.text;
              resolve(distance);
            }
          }catch(error){
            console.log("ERROR GOOGLE");
            console.log(status);
          }
        }
      });
    });
  }

I hope this helps!

I'm sorry for my English, I hope it's not an inconvenience, I had to use the Google translator.

Regards, Leandro.

0

You are using setTimeout wrong way. The (one of) function signature is setTimeout(callback, delay). So you can easily specify what code should be run after what delay.

var codeAddress = (function() {
    var index = 0;
    var delay = 100;

    function GeocodeCallback(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            map.setCenter(results[0].geometry.location);
            new google.maps.Marker({ map: map, position: results[0].geometry.location, animation: google.maps.Animation.DROP });
            console.log(results);
        }
        else alert("Geocode was not successful for the following reason: " + status);
    };

    return function(vPostCode) {
        if (geocoder) setTimeout(geocoder.geocode.bind(geocoder, { 'address': "'" + vPostCode + "'"}, GeocodeCallback), index*delay);
        index++;
    };
})();

This way, every codeAddress() call will result in geocoder.geocode() being called 100ms later after previous call.

I also added animation to marker so you will have a nice animation effect with markers being added to map one after another. I'm not sure what is the current google limit, so you may need to increase the value of delay variable.

Also, if you are each time geocoding the same addresses, you should instead save the results of geocode to your db and next time just use those (so you will save some traffic and your application will be a little bit quicker)

4
  • How can I implement this code into my code? jsfiddle.net/395rdhd8 I tried a lot of things, but without any luck. STATUS OVER_QUERY_LIMIT is showing up before my STATUS.OK in my console. But why? Thanks.
    – Appel
    Commented Mar 3, 2017 at 15:12
  • your usage of setTimeout is wrong, setTimeout is not putting javascript in sleep for X seconds, it is used to run specified function X seconds later
    – Buksy
    Commented Mar 5, 2017 at 22:09
  • How could I fix it? Javascript is not my strongest skill. Thanks!
    – Appel
    Commented Mar 6, 2017 at 7:49
  • One of the options is to create recursive function which would call itself if request wasnt successfull, something like this: jsfiddle.net/395rdhd8/1
    – Buksy
    Commented Mar 7, 2017 at 12:36

Not the answer you're looking for? Browse other questions tagged or ask your own question.