Using Google Maps API to estimate traffic - google-maps

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!

Related

UrlFetchApp function truncates my api json call

This is the code I am running to grab assignments from the Canvas LMS API.
var response = UrlFetchApp.fetch(canvas_link + "api/v1/courses?access_token="+api_key+"&enrollment_state=active");
var courses = JSON.parse(response.getContentText());
var assignments = UrlFetchApp.fetch(canvas_link + "api/v1/courses/" + courses[3].id + "/assignments?access_token="+api_key)
var assignmentsArray = JSON.parse(assignments)
It grabs the cources using an api key then requests assignments for those courses using the course id. Everytime I get the error that it Truncated my output. The assignmentsArray always returns a length of 10 when I know that there is more assignments in the course. And it will return less than 10 if there is less than 10 assignments.
I have tried using the UrlFetchApp.getRequest() function to get the request and put that into my browser it works and returns json for all the assignments.
I have tried adding headers and other options the the options for the UrlFetchApp.fetch(url, options) to see if they did anyting, but it didn't work.

Accessing Maps from Google Apps script bound to form

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

Set Maps API key in Script Editor

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;
}

Google geocoding returns zero results, Unity3D

I am trying to connect to google maps api and get lant/long of place, but no matter what I'm trying to get, I receive ZERO_RESULTS every time. For example if I type
http://maps.googleapis.com/maps/api/geocode/json?address=Moscow+Tverskaya+18 into browser, it gives me correct result, but if I'm trying to send the exact same string via WWW class from unity I get zero results.
IEnumerator GetGoogleCoords() {
var url = "http://maps.googleapis.com/maps/api/geocode/json?";
var qs = "";
// qs += "address=" + savedAddress;
qs += "address=Moscow +Tverskaya+18";
var req = new WWW(url + "?" + qs);
Debug.Log(url + qs);
yield return req;
Debug.Log(req.text);
}
I tried every request and in every order
You have an extra "?" in your url as #Engerlost said.
This post is about how to prevent doing similar mistakes again.
Best programming practice would be to build the full url not in WWW constructor but in a separate line.
var url = "http://maps.googleapis.com/maps/api/geocode/json?";
var qs = "address=Moscow +Tverskaya+18";
var fullUrl = url + "?" + qs
var request = new WWW(fullUrl);
That is, one line should contain only one job. Which makes it much easier to manage the code. In your case, it gets much easier to debug and see there is an error building the full url. Now you are able to easily add a Debug.Log if you suspicious about final url which goes into WWW as parameter.
Debug.Log("Request url: " + fullUrl);
And you would easily see the resulting url contains two "?" characters.

Google Maps API - Finding Waypoint That Caused Error

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.