We have been developing an ASP.NET application that works with the Google Maps API to assist in logistics and planning for a small shipping company in our area. One of the features is for the company to input a list of customers and the system will package up all the customer addresses as a series of waypoints, send it off to Google, and get back the direction information for the route.
One problem that we have been having is that many of the addresses in our database are not great and will often times are not able to be processed by Google Maps. When we do this we receive back an error code, but I would like to be able to determine which waypoint was the one to fail (that way the client can then go and fix the address in the database).
EDIT Here is a chunk of the code that handles the initialization and current error handling:
function initialize() {
if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map"));
map.addControl(new GSmallMapControl());
gdir = new GDirections(map);
GEvent.addListener(gdir, "load", onGDirectionsLoad);
GEvent.addListener(gdir, "error", handleErrors);
if (document.getElementById("<%=hiddenWayPoints.ClientID %>").getAttribute("value") != '')
{
setDirections();
}
}
}
function setDirections() {
var waypoints = new Array();
var str = document.getElementById("<%=hiddenWayPoints.ClientID%>").getAttribute("value");
waypoints = document.getElementById("<%=hiddenWayPoints.ClientID %>").getAttribute("value").split(":");
gdir.loadFromWaypoints(waypoints, {getSteps:true});
}
function handleErrors(){
if (gdir.getStatus().code == G_GEO_UNKNOWN_ADDRESS)
alert("No corresponding geographic location could be found for one of the specified addresses. This may be due to the fact that the address is relatively new, or it may be incorrect.\nError code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_SERVER_ERROR)
alert("A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.\n Error code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_MISSING_QUERY)
alert("The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_BAD_KEY)
alert("The given key is either invalid or does not match the domain for which it was given. \n Error code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_BAD_REQUEST)
alert("A directions request could not be successfully parsed.\n Error code: " + gdir.getStatus().code);
else alert("An unknown error occurred.");
}
The problem is that the LoadFromWayPoints() method in the GDirections object doesn't seem to return status based on each individual waypoint, but from just the entire list (if one fails it all fails).
The only real solution I can think of (without performing checks in other areas of the system) is to send each waypoint off to Google Maps in a separate request and check it's validity that way prior to sending off the entire waypoints list for the GDirections object, but that seems incredibly inefficient (especially when dealing with a larger set of customer locations).
If you are using the GClientGeocoder object to do your requests, then you will get back an appropriate response code for each getLocations call:'
function logAddress (response)
{
if (!response || response.Status.code != 200)
{
// log the response.name and the response.Status.code
return;
}
// otherwise everything was fine
}
for (var i = 0; i < addresses.length; i++)
{
var geocoder = new GClientGeocoder ();
geocoder.getLocations (addresses[i], logAddress);
}
Their are various response codes, but I am guessing you want to inform the user when you get a 602 - Unknown Address.
EDIT:
Yep, you will only get a single error callback for loadFromWaypoints for the entire directions request. What you are doing is more than just a simple geocoding request, you are actually generating directions and rendering overlays to a map for a sequence of addresses values. I suggest a couple of solutions:
You could do a getLocation request before your loadFromWaypoints request (as you suggested) and then use the latitude,longitude data returned for each address as the parameter for your loadFromWayPoints. This splits the geocoding processing out of the loadFromWayPoints request, but adds the extra round trip for each geocoding lookup. This is really something you only want to do once, when the user first enters the address (see next).
When the user enters the address information for the first time you can do a GClientGeocoder getLocations lookup at the time and get the latitude,longitude to store in your database along with the address. That way, if the user enters an address that can't be geocoded, then you can ask them to re-enter the address, or perhaps let them select the location on a google map (and get the lat,lng from that). This doesn't solve the problems with address data you have now, but perhaps you can write some code to run through you existing data (offline) and flag the addresses in the db that are not able to be geocoded.
Related
I have a script that archives old classrooms, until the end of 2021 it was working fine.
In the lasts months I got an error (the script works ok, but terminate with error) and today I was investigating it, the script runs only once per month.
The error is due to a supposed change in .nextPageToken function.
var parametri = {"courseStates": "ARCHIVED"};
var page = Classroom.Courses.list(parametri);
var listaClassi = page.courses;
var xyz = page.nextPageToken;
if (page.nextPageToken !== '') {
parametri.pageToken = page.nextPageToken;
page = Classroom.Courses.list(parametri);
listaClassi = listaClassi.concat(page.courses);
};
var xyz has been added to better understand what was happening.
So, in this case the list does not have pagination, is only one page. var xyz returns "undefined", and the "if" statement results "true", this makes that variable listaClassi got appended the same content a second time. That generate the error and the abnormal end of the script.
I found an issue reported here https://issuetracker.google.com/issues/225941023?pli=1 that may be related with my problem.
Now I could change .nextPageToken with .getNextPageToken but I found no docs on the second function and many issues reporting that is not working, can anyone help me?
When using the nextPageToken value obtained to the response make sure to enter it as a separate parameter with a slightly different name. You will obtain nextPageToken in the response, the pageToken parameter needs to be entered in the request. It does look like you are doing it right, the way you add the parameter is a bit odd, yet it should be functional.
To discard problems with the Classroom API (that we can certainly take a look at) try with this simple code example in a new Google Apps Script project, remember you will need to add an Advanced service, information about advanced services can be found in this documentation article https://developers.google.com/apps-script/guides/services/advanced. Use listFiles as the main method in your Apps Script project.
function listFiles() {
var totalClasses = 0;
nextPageToken = "";
console.log("Found the following classes:")
do {
var response = loadPage(nextPageToken);
var classes = response.courses;
for (let x in classes){
console.log("Class ID: " + classes[x].id + " named: '" + classes[x].name + "'.");
}
totalClasses += classes.length;
} while (nextPageToken = response.nextPageToken)
console.log("There are " + totalClasses + " classes.")
}
function loadPage(token = ""){
return Classroom.Courses.list({
fields: 'nextPageToken,courses(id,name)',
pageSize: 10,
pageToken: token
});
}
When we first make the API call with Apps Script we don't specify a pageToken, since it is the first run we don't have one. All calls to the List method may return a nextPageToken value if the returned page contains an incomplete response.
while (nextPageToken = response.nextPageToken)
In my code at the line above once response.nextPageToken is empty (not in the response) inside the condition block JavaScript will return false, breaking the loop and allowing the code to finish execution.
To have your incident reviewed by a Google Workspace technician you can also submit a form to open a ticket with the Google Workspace API Support team at https://support.google.com/a/contact/wsdev.
I’m trying to access Maps from a script bound to a Google form. The problem i’m having is that when debugging the script, it accesses Maps so often that i’m Running into quota limits. I have a Maps API key but do not have a client ID so can’t get Maps.setAuthenication(clientID,Key) to work. I’m doing this for a Scout Troop so don’t want to have to pay to access maps.
Can anyone help?
I was subsequently asked to post my code, and so here it is:
function setLocation(){
var whereString;
var theDuration;
var theDistance;
var theRoute;
var theDirections;
var theTravelString;
// this Sets the Where: Tab on the form
whereString = 'Where: ' + gLocation;
theItemArray = gSignupForm.getItems();
theItemArray[kWhereItem].setTitle(whereString);
//this gets the directions to the location
Maps.setAuthentication('','ABCDEFGHIJKLMNOP');
//Obviously Im'm not going to post the true key
theDirections = Maps.newDirectionFinder()
.setOrigin('7101 Shadeland Ave, Indianapolis, IN 46256')
.setDestination(gLocation)
.setMode(Maps.DirectionFinder.Mode.DRIVING)
.getDirections();
theRoute = theDirections.routes[0];
theDuration = theRoute.legs[0].duration.text;
theDistance = theRoute.legs[0].distance.text;
theTravelString = Utilities.formatString('Travel Considerations: The estimated travel distance is %u miles. ',theDistance);
theTravelString += 'The estimated travel time is ' + theDuration;
theItemArray[kTravelItem].setTitle(theTravelString);
}
One solution to limit the use of low-quota services is to avoid calling those services when the desired information has not changed.
For example, through the use of CacheService, you can dramatically reduce your calls to the Maps API, debugging session or not:
var cache = CacheService.getScriptCache();
function setLocation() {
// Try to find the route for this location if it's still available
var storedRoute = cache.get(gLocation);
if (!storedRoute) {
// No route for this value of the key gLocation was found. Query as normal.
...
theRoute = theDirections.route[0];
// Cache this route for future uses, for the maximum of 6hr).
cache.put(gLocation, JSON.stringify(theRoute), 21600);
} else {
// We have this exact stored route! Convert it from the stored string.
theRoute = JSON.parse(storedRoute);
}
theDuration = ...
...
Your "gLocation" variable may be directly usable as a cache key. If not, you'll need to make it useable by encoding it. The max length key is 250 characters. This single-parameter caching assumes your directions all have a fixed endpoint, as shown in your example code. If both endpoints vary, you'll have to construct a cache key based on both values.
Related question: Maps direction Quota limits
I'm trying to call the Google Maps API to get the travel time between two points (including traffic). Here's what I've got so far:
function test(){
//Info: https://developers.google.com/maps/documentation/directions/intro
var baseUrl = "https://maps.googleapis.com/maps/api/directions/json?";
var origin = "Redmond+WA";
var destination = "Salem+OR";
var departureTime = "now";
var trafficModel = "pessimistic";
var url = baseUrl + "origin=" + origin + "&destination=" + destination + "&departure_time=" + departureTime + "&traffic_model=" + trafficModel;
var response = UrlFetchApp.fetch(url);
Logger.log("Google Maps API: " + JSON.parse(response).routes[0].legs[0].duration.text);
Logger.log("Full response from API: \n" + response);
}
Unfortunately, I always get a result of 3 hours 43 mins, no matter when I run the code (and despite the fact that I have defined departure_time and traffic_model. Any suggestions?
(On a side note, the documentation says that I need to pass in an API key as a required parameter. Obviously - per my code above - I haven't done this yet. But it didn't prevent me from getting a response. Could this be preventing me from using the traffic_model parameters?)
What field do you see: duration or duration_in_traffic?
According to the documentation
duration_in_traffic indicates the total duration of this leg. This value is an estimate of the time in traffic based on current and historical traffic conditions. See the traffic_model request parameter for the options you can use to request that the returned value is optimistic, pessimistic, or a best-guess estimate. The duration in traffic is returned only if all of the following are true:
The request includes a valid API key, or a valid Google Maps APIs Premium Plan client ID and signature.
The request does not include stopover waypoints. If the request includes waypoints, they must be prefixed with via: to avoid stopovers.
The request is specifically for driving directions—the mode parameter is set to driving.
The request includes a departure_time parameter.
Traffic conditions are available for the requested route.
https://developers.google.com/maps/documentation/directions/intro#DirectionsResponseElements
I understand you cannot see duration_in_traffic field, because you don't apply an API key.
Hope it helps!
As far as I understand, in order to track our quota usage, we need to provide our API key to the Google App Service on the service we are planning to use.
In my case I have a spreadsheet with Origin and Destination and a Custom function to calculate the distance between.
I ran into the problem of meeting the quota from invoking .getDirections():
Error: Service invoked too many times for one day: route. (line **).
Sample of the code:
function getDirections_(origin, destination) {
var directionFinder = Maps.newDirectionFinder();
directionFinder.setOrigin(origin);
directionFinder.setDestination(destination);
var directions = directionFinder.getDirections();
return directions;
}
So I read that if I assign the API Key to my project I should be able to see the usage and how close to the free quota I am.
In the script editor, I did enable all of the APIs under Resources menu/ Advanced Google Services. Then I went to the Google Developers Console and there
I did not see any record of how many times my custom function called the Google Maps API or any API usage.
Logically I think that in my script I need to set my google API Key so my scripts start to call the API under my user name and count the number of time I used certain API. I guess right now I am using the Google Maps API as anonymous and since the whole company is assigned with the same IP, so we exhaust the permitted numbers to call this function.
Bottom line please reply if you know a way to connect my simple Spreadsheet function to the Public API access Key I have.
Thank you,
Paul
I also have been eager to find this answer for a long time and am happy to say that I've found it. It looks like Google might have just made this available around Oct 14, 2015 based on the date this page was updated.
You can leverage the UrlFetchApp to add your API key. The link I posted above should help with obtaining that key.
function directionsAPI(origin, destination) {
var Your_API_KEY = "Put Your API Key Here";
var serviceUrl = "https://maps.googleapis.com/maps/api/directions/json?origin="+origin+"&destination="+destination+
"&mode="+Maps.DirectionFinder.Mode.DRIVING+"&alternatives="+Boolean(1)+"&key="+Your_API_KEY;
var options={
muteHttpExceptions:true,
contentType: "application/json",
};
var response=UrlFetchApp.fetch(serviceUrl, options);
if(response.getResponseCode() == 200) {
var directions = JSON.parse(response.getContentText());
if (directions !== null){
return directions;
}
}
return false;
}
So walking through the code... first put in your API key. Then choose your parameters in the var serviceUrl. I've thrown in additional parameters (mode and alternatives) to show how you can add them. If you don't want them, remove them.
With UrlFetch you can add options. I've used muteHttpExceptions so that the fetch will not throw an exception if the response code indicates failure. That way we can choose a return type for the function instead of it throwing an exception. I'm using JSON for the content type so we can use the same format to send and retrieve the request. A response code of 200 means success, so directions will then parse and act like the object that getDirections() would return. The function will return false if the UrlFetch was not successful (a different response code) or if the object is null.
You will be able to see the queries in real time in your developer console when you look in the Google Maps Directions API. Be sure that billing is enabled, and you will be charged once you exceed the quotas.
1.) I added an API key from my console dashboard. Remember to select the correct project you are working on. https://console.developers.google.com/apis/credentials?project=
2.) In my Project (Scripts Editor) I setAuthentication to Maps using the API key and the Client ID from the console. I have included the script below:
function getDrivingDirections(startLoc, wayPoint, endLoc){
var key = "Your_API_Key";
var clientID = "Your_Client_ID";
Maps.setAuthentication(clientID, key);
var directions = Maps.newDirectionFinder()
.setOrigin(startLoc)
.addWaypoint(wayPoint)
.setDestination(endLoc)
.setMode(Maps.DirectionFinder.Mode.DRIVING)
.getDirections();
} return directions;
https://developers.google.com/apps-script/reference/maps/maps#setAuthentication(String,String)
As of 7/13/2017, I was able to get the API to function by enabling the Sheets API in both the "Advanced Google Services" menu (images 1 and 2), and in the Google Developer Console. If you're logged into Google Sheets with the same email address, no fetch function should be necessary.
[In the Resources menu, select Advanced Google Services.][1]
[In Advanced Google Services, make sure the Google Sheets API is turned on.][2]
[1]:
[2]:
Thank goodness for JP Carlin! Thank you for your answer above. JP's answer also explains his code. Just to share, without a code explanation (just go look above for JP Carlin's explanation), below is my version. You will see that I also have the departure_time parameter so that I will get distance and driving-minutes for a specific date-time. I also added a call to Log errors (to view under "View/Logs"):
Note: Google support told me that using your API-key for Google Maps (e.g. with "Directions API") with Google Sheets is not supported. The code below works, but is an unsupported work-around. As of 11/4/2018, Google has an internal ticket request to add support for Google Maps APIs within Google Sheets, but no timeline for adding that feature.
/********************************************************************************
* directionsAPI: get distance and time taking traffic into account, from Google Maps API
********************************************************************************/
function directionsAPI(origin, destination, customDate) {
var Your_API_KEY = "<put your APK key here between the quotes>";
var serviceUrl = "https://maps.googleapis.com/maps/api/directions/json?origin="+origin+"&destination="+destination+"&departure_time="+customDate.getTime()+"&mode="+Maps.DirectionFinder.Mode.DRIVING+"&key="+Your_API_KEY;
var options={
muteHttpExceptions:true,
contentType: "application/json",
};
var response=UrlFetchApp.fetch(serviceUrl, options);
if(response.getResponseCode() == 200) {
var directions = JSON.parse(response.getContentText());
if (directions !== null){
return directions;
}
}
Logger.log("Error: " + response.getResponseCode() + " From: " + origin + ", To: " + destination + ", customDate: " + customDate + ", customDate.getTime(): " + customDate.getTime() );
return false;
}
I want my application using Google local search and google maps to give my users the ability to choose from a number of locations when there are a number of possible answers to their query. A good example would be "Overton, UK" - there are lots of places with this name in the country, and the google maps website gives several possible "did you mean results".
The API, however, doesn't give you this information. Both localsearch and GClientGeocoder return one result.
Is there a way to get the API to return a list of possible results?
Have you tried using geocode from google.maps.Geocoder() (version 3 of the Maps API)? I'm pretty sure that it returns an array of results:
var geocoder = new google.maps.Geocoder();
if(geocoder) {
geocoder.geocode({address: address}, function(results, status) {
if(status == google.maps.GeocoderStatus.OK) {
//do stuff with results, which is an array of address results
}
else {
//error handling
}
});
}
else {
//error handling
}
More information at Maps API V3 Services. The page goes into detail about the structure of the address results object.