2

I have a Google Sheet where we are fetching the driving distance between two Lat/Lng via the Maps Service. The function below works, but the matrix is 4,500 cells, so I'm getting the "Hit Limit" error.

How can I supply my paid account's API key here?

Custom Function

function drivingMeters(origin, destination) {
  if (origin=='' || destination==''){return ''}
  var directions = Maps.newDirectionFinder()
  .setOrigin(origin)
  .setDestination(destination)
  .getDirections();
  return directions.routes[0].legs[0].distance.value ;
}

Example use:

A1: =drivingMeters($E10,G$9)

Where E10 = 42.771328,-91.902281
  and G9  = 42.490390,-91.1626620
tehhowch
  • 9,645
  • 4
  • 24
  • 42
John Cappelletti
  • 79,615
  • 7
  • 44
  • 66

1 Answers1

4

Per documentation, you should initialize the Maps service with your authentication details prior to calling other methods:

Your client ID and signing key can be obtained from the Google Enterprise Support Portal. Set these values to null to go back to using the default quota allowances.

I recommend storing these values in PropertiesService and using CacheService, to provide fast access. Using this approach, rather than writing them in the body of your script project, means they will not be inadvertently copied by other editors, pushed to a shared code repository, or visible to other developers if your script is published as a library.

Furthermore, I recommend rewriting your custom function to accept array inputs and return the appropriate array output - this will help speed up its execution. Google provides an example of this on the custom function page: https://developers.google.com/apps-script/guides/sheets/functions#optimization

Example with use of props/cache: example props

function authenticateMaps_() {
  // Try to get values from cache:
  const cache = CacheService.getScriptCache();
  var props = cache.getAll(['mapsClientId', 'mapsSigningKey']);
  // If it wasn't there, read it from PropertiesService.
  if (!props || !props.mapsClientId || !props.mapsSigningKey) {
    const allProps = PropertiesService.getScriptProperties().getProperties();
    props = {
      'mapsClientId': allProps.mapsClientId,
      'mapsSigningKey': allProps.mapsSigningKey
    };
    // Cache these values for faster access (max 6hrs)
    cache.putAll(props, 21600);
  }
  // Apply these keys to the Maps Service. If they don't exist, this is the
  // same as being a default user (i.e. no paid quota).
  Maps.setAuthentication(props.mapsClientId, props.mapsSigningKey);
}
function deauthMaps_() {
  Maps.setAuthentication(null, null);
}

// Your called custom function. First tries without authentication,
// and then if an error occurs, assumes it was a quota limit error
// and retries. Other errors do exist (like no directions, etc)...
function DRIVINGMETERS(origin, dest) {
  if (!origin || !destination)
    return;
  try {
    return drivingMeters_(origin, dest);
  } catch (e) {
    console.error({
      message: "Error when computing directions: " + e.message,
      error: e
    });
    // One of the possible errors is a quota limit, so authenticate and retry:
    // (Business code should handle other errors instead of simply assuming this :) )
    authenticateMaps_();
    var result = drivingMeters_(origin, dest);
    deauthMaps_();
    return result;
  }
}

// Your implementation function.
function drivingMeters_(origin, dest) {
  var directions = Maps.newDirectionFinder()
  ...
}
tehhowch
  • 9,645
  • 4
  • 24
  • 42
  • Thank you for the detailed response! – John Cappelletti Jun 05 '18 at 15:58
  • @JohnCappelletti added a touch more to it O:) – tehhowch Jun 05 '18 at 16:05
  • Never understood why I can't create a bounty from day 1 – John Cappelletti Jun 08 '18 at 12:33
  • Great response but this only seems applicable Google Enterprise clients as the api keys or oauth credentials do not work if apis and billing are enabled in the cloud console. For me it appears the only way is to use URL fetch app where you can specify an api key. – russellkt Aug 21 '18 at 19:42
  • 1
    Is putting the keys in Cache(that too ScriptCache) safe? I'd recommend against it for security reasons. I recommend storing it in "User"Properties only. Security loop holes will be huge, if the sheet is shared. Custom functions probably can't access Userproperties. But the workaround's security implications should be mentioned in the answer, IMO. – TheMaster Oct 15 '18 at 09:59
  • @i'-'i my understanding of the OPs situation was that the spreadsheet was shared with persons within the same organisation. In this case they likely should all use the same billing project, and rather than require every editor to manually enter these credentials into their user properties (increasing the knowledge that these creds even exist), having one developer enter these into the properties service for a script and other editors be able to copy them if both know these creds exist (not guaranteed) and they intend to copy them, seems like a smaller surface. I'm open to your edits though. – tehhowch Oct 15 '18 at 11:16
  • @tehhowch Even domain users do share the sheet outside the domain. I don't think custom functions are suitable for this problem. In light of your explanation however, I think my previous comment is enough, rather than an edit. – TheMaster Oct 15 '18 at 11:44