I'd like to find the shortest driving distance for multiple origins to multiple destinations. Say I have 5 customers and 10 stores, I'd like to find the shortest distance for each customer to a store.
My problem now is the 10 query per second limit on the google direction service. For each customer it takes less than 1 second to finish querying the API, so I will reach the query limit for each customer.
I tried to implement a delay between each customer, but the callback function from the google direction service is not blocked...
// A function to calculate the route between our current position and some desired end point.
function calcRoute(end, callback) {
var request = {
origin: currentPosition,
destination: end,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
callback(response);
} else {
size--;
}
});
}
// Stores a routing result from the API in our global array for routes.
function storeResult(data) {
routeResults.push(data);
if (routeResults.length === size) {
findShortest();
}
}
// Goes through all routes stored and finds which one is the shortest. It then
// sets the shortest route on the map for the user to see.
function findShortest() {
var i = routeResults.length;
var shortestIndex = 0;
var shortestLength = routeResults[0].routes[0].legs[0].distance.value;
while (i--) {
if (routeResults[i].routes[0].legs[0].distance.value < shortestLength) {
shortestIndex = i;
shortestLength = routeResults[i].routes[0].legs[0].distance.value;
}
}
directionsDisplay.setDirections(routeResults[shortestIndex]);
}
Is there a way to block the callback after each iteration? Or is there another way to do this?
The below code should do the job for you.
// A function to calculate the route between our current position and some desired end point.
function calcRoute(end, callback) {
var request = {
origin: currentPosition,
destination: end,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function (response, status) {
if (status == google.maps.DirectionsStatus.OK) {
callback(response);
}
//Handle the limit of 10 queries per sec
else if (status === google.maps.DirectionsStatus.OVER_QUERY_LIMIT) {
setTimeout(function () {
calcRoute(end, callback);
}, 1100);
}
else {
// a result could not found due to any one of the following errors:
//UNKNOWN_ERROR or REQUEST_DENIED or INVALID_REQUEST or MAX_WAYPOINTS_EXCEEDED
size--;
}
});
}
// Stores a routing result from the API in our global array for routes.
function storeResult(data) {
routeResults.push(data);
if (routeResults.length === size) {
findShortest();
}
}
// Goes through all routes stored and finds which one is the shortest. It then
// sets the shortest route on the map for the user to see.
function findShortest() {
var i = routeResults.length;
var shortestIndex = 0;
var shortestLength = routeResults[0].routes[0].legs[0].distance.value;
while (i--) {
if (routeResults[i].routes[0].legs[0].distance.value < shortestLength) {
shortestIndex = i;
shortestLength = routeResults[i].routes[0].legs[0].distance.value;
}
}
directionsDisplay.setDirections(routeResults[shortestIndex]);
}
Related
I have a process in my code where I need to get a list of technician drive times. I use the Google Maps API to get the driving time between the origin and destination. As most of you know, the API requires to have a timeout of roughly 1 second or more to work without generating errors. I have created a recursive function to retrieve the list of times that I need using a setTimeout within the method, like so:
function GetTechDriveTimes(info, destAddress) {
let techs = this.state.Techs
.filter(tech => tech.Address != "" && !tech.Notes.includes('Not'))
.map(tech => {
let techObj = {
TechName: tech.FirstName + " " + tech.LastName,
TechAddress: tech.Address + " " + tech.City + " " + tech.State + " " + tech.Zip,
KioskID: info.ID.toUpperCase(),
DriveTime: "",
};
return techObj
});
let temp = [...techs]; // create copy of techs array
const directionsService = new google.maps.DirectionsService();
recursion();
let count = 0;
function recursion() {
const techAddress = temp.shift(); // saves first element and removes it from array
directionsService.route({
origin: techAddress.TechAddress,
destination: destAddress,
travelMode: 'DRIVING'
}, function (res, status) {
if (status == 'OK') {
let time = res.routes[0].legs[0].duration.text;
techs[count].DriveTime = time;
} else {
console.log(status);
}
if (temp.length) { // if length of array still exists
count++;
setTimeout(recursion, 1000);
} else {
console.log('DONE');
}
});
}
return techs;
}
After this method is complete, it will return an array with the techs and their respective drive times to that destination. The problem here is that using setTimeout obviously doesn't stop execution of the rest of my code, so returning the array of technicians will just return the array with empty drive times.
After timeout is complete I want it to return the array within the method it was called like this:
function OtherMethod() {
// there is code above this to generate info and destAddress
let arr = GetTechDriveTimes(info, destAddress);
// other code to be executed after GetTechDriveTimes()
}
I've looked online for something like this, and it looks like I would need to use a Promise to accomplish this, but the difference from what I found online is that they weren't using it inside of a recursive method. If anyone has any ideas, that would help me a lot. Thanks!
You could use promises, but you can also create a callback with the "other code to be executed after GetTechDriveTimes" and send it to the function:
function OtherMethod() {
// there is code above this to generate info and destAddress
// instead of arr = GetTechDriveTimes, let arr be the parameter of the callback
GetTechDriveTimes(info, destAddress, function(arr) {
// other code to be executed after GetTechDriveTimes()
});
}
function GetTechDriveTimes(info, destAddress, callback) {
...
if (temp.length) { // if length of array still exists
...
} else {
console.log('DONE');
callback(techs); // send the result as the parameter
}
...
I've created a small ReactJS frontend app to create a table of origins and destinations, calculate the distances between them and outputting those distances in the table.
Everything should work fine, except that I'm getting a Cors policy blockade every time I try to generate the table. This is the function I've made for the Google Distance Matrix API:
export const getKm = (origins, destinations) => {
var distance = require('google-distance-matrix');
distance.key('HERE_IS_MY_API_KEY');
distance.units('metric');
distance.matrix(origins, destinations, function (err, distances) {
if (distances.rows[0].elements.status == 'OK') {
console.log(distances)
return distances.rows.elements.distance.value;
} else {
console.log(err);
}
})
}
These are the errors I'm getting from the console:
errors
I've tried to google the problem, but I'm not able to find anything useful.
Do you guys maybe know in which direction I have to look for an answer?
Your error shows that you are calling the Distance Matrix API via webservice request.This webservice request that looks like this: (https://maps.googleapis.com/maps/api/distancematrix/json? . . .) should be used in the server side however you are using it in client side that's why you are running to a CORS error. Instead of using this webservice request, you can use the Maps JavaScript API's Distance Matrix Service for client side usage of Distance Matrix.
Here is a snippet on how I use Distance Matrix service in my reactjs code:
var orig = this.state.input;
var dest = this.state.destination;
service.getDistanceMatrix({
origins: [orig],
destinations: [dest],
travelMode: 'DRIVING',
unitSystem: google.maps.UnitSystem.METRIC,
avoidHighways: false,
avoidTolls: false
}, (response, status) => {
if (status !== 'OK') {
alert('Error was: ' + status);
} else {
var origins = response.originAddresses;
var destinations = response.destinationAddresses;
//Loop through the elements row to get the value of duration and distance
for (var i = 0; i < origins.length; i++) {
var results = response.rows[i].elements;
for (var j = 0; j < results.length; j++) {
var element = results[j];
var distanceString = element.distance.text;
var durationString = element.duration.text;
this.setState ({
distance: parseInt(distanceString, 10)
});
console.log(this.state.distance);
this.setState ({
duration: parseInt(durationString, 10)
});
console.log(this.state.duration);
}
}
}
});
Hope this helps!
So what I am trying to do is implement Google Maps with a search box. Currently, the search box retrieves 20 of the top places based on the search result.
What I noticed is there is no pagination with this. Such as if I type in “pizza”, it will give me the top 20 results in the current location bounds. But when I use the google.maps.places.PlacesService(map) textSearch function to obtain a list of pizza searches, I get more than 20 results with pagination.
Is there a way to get pagination on your searchBox or use the textSearch function to provide pagination after you used searchBox?
What I have here is a snippet:
var canvas = element.find('div')[0];
var mapOptions = {
zoom: 5,
minZoom: 2,
};
var searchBox = new google.maps.places.SearchBox('pizza');
var placeService = new google.maps.places.PlacesService(map);
google.maps.event.addListener(searchBox, 'places_changed', locationChange);
function locationChange() {
var places = searchBox.getPlaces();
//Determine if there are additional places not provided by searchbox. Using text
var request = {
query: input.value,
};
var textSearchPlaces = placeService.textSearch(request, callback);
function callback(results, status, pagination) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
var hi = places;
for (var i = 0; i < results.length; i++) {
var place = results[i];
}
}
}
if (places.length === 0) {
return;
}
};
As you can see this is not the most ideal solution to looking using a pagination as I would have to filter through the textSearchPlaces to see if those places are not duplicates of the searchBox results.
I also thought about just removing the searchBox and just going with the textSearch function but I realized I wouldn’t have the autoComplete function that provides suggestions that searchBox provides.
Most likely you are getting OVER_QUERY_LIMIT.
if (status == google.maps.places.PlacesServiceStatus.OK) {
Try queuing the details requests for after you have all the list completed, adding the same timeout logic to check for google.maps.places.PlacesServiceStatus.OVER_QUERY_LIMIT on this line.
if (pagination.hasNextPage) {
//add all places to the list
setTimeout('pagination.nextPage()',2000);
} else {
createMarkers(placesList);
}
You should use google.maps.places.PlacesServiceStatus.OVER_QUERY_LIMIT when using Places.
I am building an app that shows the local weather to the user. So far, I am able to grab the user's longitude and latitude coordinates using geolocation, but when I attempt to turn that into a location (e.g. New York, NY) via the function whose results get stored to geocodeLatLng, geocodeLatLng is undefined. I suspect that it has something to do with when the functions are called; does anyone know why?
window.onload = function() {
var startPos;
var geoOptions = {
maximumAge: 5 * 60 * 1000,
timeout: 10 * 1000
}
var geoSuccess = function(position) {
startPos = position;
document.getElementById('startLat').innerHTML = startPos.coords.latitude;
document.getElementById('startLon').innerHTML = startPos.coords.longitude;
};
var geoError = function(position) {
console.log('Error occurred. Error code: ' + error.code);
// error.code can be:
// 0: unknown error
// 1: permission denied
// 2: position unavailable (error response from location provider)
// 3: timed out
};
navigator.geolocation.getCurrentPosition(geoSuccess);
document.getElementById('location').innerHTML = geocodeLatLng;
var geocodeLatLng = function(startPos) {
var latlng = {lat: document.getElementById('startLat').innerHTML, lng: document.getElementById('startLon').innerHTML};
console.log(startPos);
function address(results, status) {
if (results[6]) {
return results[6].formatted_address;
} else {
console.log('No results found');
}
}
}
};
document.getElementById('location').innerHTML = geocodeLatLng;
You're not actually calling the function, you're assigning the function itself to innerHTML. Call it by adding () at the end. You're also missing the startPos parameter.
document.getElementById('location').innerHTML = geocodeLatLng();
Really though, you're going to want to make an asynchronous call to look up the location name. How you do that depends more on how you're doing the lookup, which isn't specified here.
We're seeing an issue where the PlacesService's textSearch is not returning any results for the "Strood Station Car Park, Rochester", whereas the Autocomplete is. I've created this JS Fiddle that shows the demonstrates this behavior: http://jsfiddle.net/anero/b5jh56gy/6/
Is there a way to have a consistent result set for both API calls?
var client = new google.maps.places.PlacesService($("#google-attributions")[0]);
var query = "Strood Station Car Park, Rochester";
var textSearchRequest = {
query: query
};
client.textSearch(textSearchRequest, function (response, status) {
if (status === google.maps.GeocoderStatus.OK && response.length > 0) {
alert('Found place!');
} else {
alert('Did not find place!');
}
});