-1

So i have heard that there is no real clean way to batch requests referenced here to the GooglePlacesAPI; understood.

But there has to be a work around.

const retrievePlaces = (google, map, request) => {
  var places = [];
  var newPlaces = []
  var service = new google.maps.places.PlacesService(map);

  return new Promise(function(resolve, reject) {
    service.nearbySearch(request, function(results, status){

    if( status == "OK" ){
      for (var i = 0; i < results.length; i++) {
          var place = results[i];
          places.push(place);
        }
        resolve(places);
      }
    });
  });
}

I use the above function to retrieve my places first (this works fine). Then I use:

const retrieveDetails = ( google, map, places ) => {

   var gmap = {
     map: map,
     google: google
   };

   var placeIds = places.map(function(place){
     return { placeId: place.place_id }
   });

   var promiseArray = placeIds.map( place => getPlaceDetailsPromise( place, gmap )
                                        .then(  res => ({res}) ) 
                                        .catch( err => ({err}) ) );

   Promise.all(promiseArray)
     .then(results => {
       console.log(results);
   });

 }

and:

const getPlaceDetailsPromise = (obj, gmap) => new Promise((resolve, reject) => {
   var service = new gmap.google.maps.places.PlacesService(gmap.map);

   service.getDetails(obj, (place, status) => {
     if (status === google.maps.places.PlacesServiceStatus.OK) {
       console.log(" Status OK", place);
       resolve(place);   
     } else {
       console.log("Not OK");
     }
  });
});

to attempt to retrieve the details for all the places from the PlaceDetailsAPI. What sucks is that it actually works to a degree, but it always returns only 9 responses and no more. furthermore they are out of order.

Does anyone have any insight on how it might be possible to retrieve the details for each place?

Community
  • 1
  • 1
Avant Baker
  • 23
  • 1
  • 8

3 Answers3

2

Maps JavaScript API client side services have a per session limits. This is mentioned in the following section of the documentation:

https://developers.google.com/maps/documentation/javascript/geocoding#UsageLimits

Rate limit applied per user session, regardless of how many users share the same project.

The per-session rate limit prevents the use of client-side services for batch requests, such as batch geocoding. For batch requests, use the Google Maps Geocoding API web service.

Unfortunately, documentation doesn't include this notice in the places library part of the documentation, but it works the same.

As far as I know, initially you have a bucket of 10 requests. Once the bucket is empty request is denied. The bucket is refilled at the rate 1 request per second. So, you have to throttle your places details requests in order to stay within allowed per session limits. Alternatively, you can try to implement batch places requests on server side where you will have 50 queries per second (QPS) limit.

Community
  • 1
  • 1
xomena
  • 31,125
  • 6
  • 88
  • 117
0

Thanks @xomena, and for anybody else that may stumble upon this question there is a way to do it and it involves what @xomena mentioned before. It is a little dirty and probably extremely inefficient but does what i need it to do.

I ended up doing a little ajax/curl inception to solve my issue and to get around the CORS issues i was having as well. I made a server side proxy that takes the formatted google api request uri ( sent via ajax from one of my functions ) and returns the details object.

After that everything worked like a charm. Updated Code is as follows.

const retrievePlaces = (google, map, request) => {
  var places = [],
      newPlaces = [],
      service = new google.maps.places.PlacesService(map);

  return new Promise(function(resolve, reject) {
    service.nearbySearch(request, function(results, status){
      if( status == "OK" ){
        for (var i = 0; i < results.length; i++) {
          var place = results[i];
          places.push(place);
        }
        resolve(places);
      }
     });
  });
}

const retrieveDetails = (google, map, places, api) => {

  var gmap = { map: map, google: google, api_key: api },
      placeDetails = [],
      placeIds = places.map(getPlaceIds),
      promiseArray = placeIds.map(getPlaceDetails);

  function getPlaceIds(place){
    return { placeId: place.place_id }
  }

  function getPlaceDetails(place){
    getPlaceDetailsPromise( place, gmap )
      .then(  res => ({res}) ) 
      .catch( err => ({err}) );
  }

  return Promise.all(promiseArray);

}

const getPlaceDetailsPromise = ( obj, gmap ) => new Promise((resolve, reject) => {

  var url = `https://maps.googleapis.com/maps/api/place/details/json?placeid=${obj.placeId}&key=${gmap.api_key}`,
      qsObj = Qs.parse({ url: url }),
      qsString = Qs.stringify(qsObj),
      headerConfig = { 
        headers: { 
          "Content-Type": "application/x-www-form-urlencoded" 
        } 
      };

  function resolveDetails(res){
    var result = res.data;
    resolve($.parseJSON(result));
  }

  axios.post('/a/random/path', qsString, headerConfig ).then(resolveDetails);

});

And then the proxy in php:

<?php 
 /* proxy.php */
 $url = $_POST["url"];

 $ch = curl_init();
 curl_setopt($ch, CURLOPT_URL, $url);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 $result = curl_exec ($ch);
 curl_close ($ch);

 $result = json_encode($result);

 echo $result;

 die();
?>

Of course this won't work if you plug and played it into your code but the idea is there. If anybody finds a better way I'd love to see how you did it, it would be good shit to know in the future.

Avant Baker
  • 23
  • 1
  • 8
0

Google Maps apparently has placed throttle limits on their Maps API because they expect it to be called on the client side as part of user interactive experience (and the limits are there probably to avoid DOS denial of service attacks). I was able to write a program that automates the calling of the Google Maps Places API, see below. Unfortunately, my program is only able to call the API in batches of 240 (apparently the Google Maps Places API has more throttle limits than their documents 10calls/sec and 100calls/100sec).

The obvious solution is to loop through the dataset making a Google Places API call for each element in the dataset, however I found that this doesn’t work. The Google Places API stores each API call and waits for the entire loop to finish before executing the calls, and this causes the program to exceed the 10calls/sec limitation.
My program was designed to run synchronously, but it needed to run asynchronously.

My solution is to only send datasets of 10 elements (places) to the google API and then have my program sleep for 11 seconds. This will get us past 10calls/sec and 100calls/100 seconds throttle limits.

function callMeWithDataset(dataset) {
//dataset is an array of json objects with values of strings containing address
// EX: [“address”: 123 helloworld st, ca”, “address”: “456 hacking way, ca”, etc..] containing 10 elements.
for(var i=0; i<dataset.length;i++) {

const request = {
query: dataset[i].address,
fields: [‘place_id’],
}

//function
callGoogleService = function (index, request) {
  let googleService = new google.maps.places.PlacesService(map);
  googleService.findPlaceFromQuery( request, function (results, status) {
     //in callback function
     serviceCallCounter++;
     var placeIdValue;
     if(status == ‘OK’) 
       placeIdValue = results[0].place_id;
     else 
       placeIdValue = “null”;

      //add the place id to the array of json objects at the corresponding //location in the array (see function call for “index”
      dataset[index][“placeID”] = placeIdValue;

    //check to see if this call is the last call for the dataset
      if (serviceCallCounter == dataset.length) {
         //Insert Code to save your data set HERE
      
    
         //Sleep for 11 seconds using a sleep helper function. easy to build/find
         sleep(11);

        //Call callMeWithDataset(dataset) with a new dataset of 10 elements
        callFunctionToGetNewDataset();
       }
     });
    }
   callGoogleService( i, request);
  }
}

This solution works for 24 datasets of 10 elements. On the 25th dataset, I get the ‘OVER_QUERY_LIMIT’ error. Though it is not ideal to only get 240 api calls, it sure beats the 10 calls I was getting when I first started.