web-dev-qa-db-fra.com

Comment géocoder 20 adresses sans recevoir de réponse OVER_QUERY_LIMIT?

À l'aide du Google Geocoder v3, si j'essaie de géocoder 20 adresses, j'obtiens un OVER_QUERY_LIMIT, à moins que je ne les chronomètre à environ 1 seconde d'intervalle, mais il faut 20 secondes avant que tous mes marqueurs soient placés.

Y a-t-il un autre moyen de le faire, autre que de stocker les coordonnées à l'avance?

88

Non, il n'y a pas vraiment d'autre moyen: si vous avez plusieurs emplacements et souhaitez les afficher sur une carte, la meilleure solution consiste à:

  • récupère la latitude + longitude, à l'aide du géocodeur, lorsqu'un emplacement est créé
  • les stocker dans votre base de données, à côté de l'adresse
  • et utilisez ces latitude + longitude stockées lorsque vous souhaitez afficher la carte.

Ceci, bien sûr, étant donné que vous avez beaucoup moins de création/modification d'emplacements que vous avez des consultations d'emplacements.


Oui, cela signifie que vous devrez effectuer un peu plus de travail lors de la sauvegarde des emplacements - mais cela signifie également:

  • Vous pourrez rechercher par coordonnées géographiques
    • c'est-à-dire "je veux une liste de points proches de l'endroit où je suis maintenant"
  • L'affichage de la carte sera beaucoup plus rapide
    • Même avec plus de 20 emplacements dessus
  • Oh, et aussi (last but not least): cela fonctionnera ;-)
    • Vous aurez moins de chances d'atteindre la limite d'appels X du géocodeur en N secondes.
    • Et vous aurez moins de chances d'atteindre la limite de Y appels de géocodeur par jour.
84
Pascal MARTIN

En réalité, vous n'avez pas à attendre une seconde complète pour chaque demande. J'ai constaté que si j'attendais 200 millisecondes entre chaque demande, je suis en mesure d'éviter la réponse OVER_QUERY_LIMIT et l'expérience utilisateur est passable. Avec cette solution, vous pouvez charger 20 éléments en 4 secondes.

$(items).each(function(i, item){

  setTimeout(function(){

    geoLocate("my address", function(myLatlng){
      ...
    });

  }, 200 * i);

}
20
gabeodess

Malheureusement, il s’agit d’une restriction du service Google Maps.

Je travaille actuellement sur une application utilisant la fonctionnalité de géocodage et j'enregistre chaque adresse unique utilisateur par utilisateur. Je génère les informations d'adresse (ville, rue, état, etc.) en fonction des informations renvoyées par Google Maps, puis enregistre ces informations dans la base de données. Cela vous évite d'avoir à recoder des objets et vous donne des adresses bien formatées.

Cela s'explique également par le fait qu'il existe une limite quotidienne du nombre d'adresses pouvant être géocodées à partir d'une adresse IP donnée. Vous ne voulez pas que votre demande échoue pour une personne pour cette raison.

6
Zachary Wright

Je suis confronté au même problème en essayant de géocoder 140 adresses.

Ma solution de contournement consistait à ajouter sleep (100000) pour chaque boucle de la demande de géocodage suivante. Si le statut de la demande est OVER_QUERY_LIMIT, la durée d'utilisation est augmentée de 50000 et la demande est répétée, etc.

Et bien sûr, toutes les données reçues (lat/long) sont stockées dans un fichier XML pour ne pas exécuter de requête à chaque chargement de la page.

2
gray

MODIFIER:

Oublié de dire que cette solution est en js pur, la seule chose dont vous avez besoin est un navigateur qui supporte les promesses https: // developer. mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Promise


Pour ceux qui ont encore besoin d'accomplir de telles tâches, j'ai écrit ma propre solution combinant promesses et délais d'attente.

Code:

/*
    class: Geolocalizer
        - Handles location triangulation and calculations.
        -- Returns various prototypes to fetch position from strings or coords or dragons or whatever.
*/

var Geolocalizer = function () {
    this.queue          = [];     // queue handler..
    this.resolved       = [];
    this.geolocalizer = new google.maps.Geocoder();  
};

Geolocalizer.prototype = {
    /*
        @fn: Localize
        @scope: resolve single or multiple queued requests.
        @params: <array> needles
        @returns: <deferred> object
    */
    Localize: function ( needles ) {
        var that = this;
        // Enqueue the needles.
        for ( var i = 0; i < needles.length; i++ ) {
            this.queue.Push(needles[i]);
        }
        // return a promise and resolve it after every element have been fetched (either with success or failure), then reset the queue.
        return new Promise (
            function (resolve, reject) {
                that.resolveQueueElements().then(function(resolved){
                  resolve(resolved);
                  that.queue    = [];
                  that.resolved = [];
                });
            }
        );
    },

    /*
        @fn: resolveQueueElements
        @scope: resolve queue elements.
        @returns: <deferred> object (promise)
    */

    resolveQueueElements: function (callback) {
        var that = this;
        return new Promise(
            function(resolve, reject) {
                // Loop the queue and resolve each element.
                // Prevent QUERY_LIMIT by delaying actions by one second.
                (function loopWithDelay(such, queue, i){
                    console.log("Attempting the resolution of " +queue[i-1]);
                    setTimeout(function(){
                        such.find(queue[i-1], function(res){
                           such.resolved.Push(res); 
                        });
                        if (--i) {
                            loopWithDelay(such,queue,i);
                        }
                    }, 1000);
                })(that, that.queue, that.queue.length);

                // Check every second if the queue has been cleared.
                var it = setInterval(function(){
                    if (that.queue.length == that.resolved.length) {
                        resolve(that.resolved);
                        clearInterval(it);
                    }
                }, 1000);
            }
        );
    },

    /*
        @fn: find
        @scope: resolve an address from string
        @params: <string> s, <fn> Callback
    */
    find: function (s, callback) {
        this.geolocalizer.geocode({
            "address": s
        }, function(res, status){
           if (status == google.maps.GeocoderStatus.OK) {
               var r = {
                   originalString:  s,
                   lat: res[0].geometry.location.lat(),
                   lng: res[0].geometry.location.lng()
               };
               callback(r);
           }
            else {
                callback(undefined);
                console.log(status);
                console.log("could not locate " + s);
            }
        });
    }
};

Veuillez noter qu'il ne s'agit que d'une partie d'une plus grande bibliothèque que j'ai écrite pour gérer les éléments de Google Maps. Par conséquent, les commentaires peuvent être déroutants.

L'utilisation est assez simple, mais l'approche est légèrement différente: au lieu de boucler et de résoudre une adresse à la fois, vous devrez passer un tableau d'adresses à la classe et celle-ci gérera la recherche par elle-même, renvoyant une promesse qui , une fois résolu, retourne un tableau contenant toutes les adresses résolues (et non résolues).

Exemple:

var myAmazingGeo = new Geolocalizer();
var locations = ["Italy","California","Dragons are thugs...","China","Georgia"];
myAmazingGeo.Localize(locations).then(function(res){ 
   console.log(res); 
});

Sortie de la console:

Attempting the resolution of Georgia
Attempting the resolution of China
Attempting the resolution of Dragons are thugs...
Attempting the resolution of California
ZERO_RESULTS
could not locate Dragons are thugs...
Attempting the resolution of Italy

Objet retourné:

enter image description here

Toute la magie se passe ici:

(function loopWithDelay(such, queue, i){
                    console.log("Attempting the resolution of " +queue[i-1]);
                    setTimeout(function(){
                        such.find(queue[i-1], function(res){
                           such.resolved.Push(res); 
                        });
                        if (--i) {
                            loopWithDelay(such,queue,i);
                    }
                }, 750);
            })(that, that.queue, that.queue.length);

Fondamentalement, chaque élément est bouclé avec un délai de 750 millisecondes, donc une adresse est contrôlée toutes les 750 millisecondes.

J'ai effectué quelques tests supplémentaires et j'ai découvert que même après 700 millisecondes, l'erreur QUERY_LIMIT m'arrivait parfois, alors qu'avec 750, je n'ai rencontré aucun problème.

Dans tous les cas, n'hésitez pas à modifier les 750 ci-dessus si vous sentez que vous êtes en sécurité en gérant un délai inférieur.

J'espère que cela aidera quelqu'un dans un proche avenir;)

1
briosheje

Je viens de tester Google Geocoder et j'ai le même problème que vous. J'ai remarqué que je n'obtiens le statut OVER_QUERY_LIMIT qu'une fois toutes les 12 demandes. Donc, j'attends 1 seconde (c'est le délai d'attente minimum). Cela ralentit l'application mais attend moins d'une seconde à chaque demande

info = getInfos(getLatLng(code)); //In here I call Google API
record(code, info);
generated++; 
if(generated%interval == 0) {
holdOn(delay); // Every x requests, I sleep for 1 second
}

Avec la méthode de base holdOn:

private void holdOn(long delay) {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException ex) {
            // ignore
        }
    }

J'espère que ça aide

0
Hugues