1

EDIT: I need help combining functions into 1 and adding a trigger.

In my spreadsheet I have rows 4-100 for customer service calls that are filtered from a "ServiceData" worksheet by either choosing a "Service Month" or "Service Day" (ie. "7/11" shows only 5 rows where "July" would show 65 rows) . Each row item has corresponding Place IDs for origin/destination in column K and L with an order # (as in 1st, 2nd, 3rd... service call of the day) in column J .

TravelTime spreadsheet

I'm using the following custom function travelTime() in cells M4:M100 to calculate driving duration and distance between 2 place IDs:

function travelTime(origin,destination) {
  var API_KEY = PropertiesService.getScriptProperties().getProperty('keyMaps');
  var baseUrl = "https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=";
  var queryUrl = baseUrl + "place_id:" + origin + "&destinations=" + 
"place_id:" + destination + "&mode=driving" + "&key=" + API_KEY;
  var response = UrlFetchApp.fetch(queryUrl);
  var json = response.getContentText();
  var time = JSON.parse(json);

  return [[ time.rows[0].elements[0].duration.text,
            time.rows[0].elements[0].distance.text ]] ;

A major issue is that many unnecessary service calls are being made to the API when I'm making edits to the "ServiceData" spreadsheet (ie. service date changes when a particular day is over-scheduled) and not needing the travel time updated until I'm done working through a schedule . After researching quite a bit there seems to be several options I could be using; caching, looping, arrays, and putting everything into a script then attach to a button to only run when ready. Considering I'm a newbie, putting all these options together are definitely beyond my skill level and could really use some help.

EDIT with new functions:
So after more researching I have been able to put together the following functions that when each are run independently work great. Now the problem I'm having is putting these all together in particular adjusting the original travelTime()into newTravelTime(). I have made an attempt towards the right direction below but can't figure out how to get the API call in there .

function newTravelTime() {//<--**having issues how to write this function
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sourceSheet = ss.getSheetByName("MonthlySA");
  var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
  var sourceV = sourceR.getValues();

  var array = [];
  for (var i = 0; i < sourceV.length; i++) {
    if (sourceV[i][2] == "") {
      var origin = sourceV[i][0];//ori place IDs for API query
      var destination = sourceV[i][1];//des place IDs API api query
    }
    array.push([sourceV[i][2]]);
  }
  sourceSheet.getRange(4, 13, array.length, 1).setValues(array);

I'd like to create a final getTravelTime() with all the functions and add an OnEdit trigger when either "Service Month" or "Service Day" changes in cells B1 or B2 to run them. If there is any advice with my functions themselves I would really appreciate some help, I am very new with this and trying.

///checks if origin/destination are already in the cacheSheet then return travel time to sourceSheet
function getCachedTravelTime() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sourceSheet = ss.getSheetByName("MonthlySA");
  var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
  var sourceV = sourceR.getValues();
  var cacheSheet = ss.getSheetByName("TravelTimeCache");
  var cacheR = cacheSheet.getRange(2, 1, cacheSheet.getLastRow()-1, 4); 
  var cacheV = cacheR.getValues();

  var array = [];
  for (var i = 0; i < sourceV.length; i++) {
    for (var j = 0; j < cacheV.length; j++) {
      //if origin/destination columns from sourceSheet match columns on cacheSheet
      if (sourceV[i][0]+sourceV[i][1] == cacheV[j][0]+cacheV[j][1]) {
        sourceV[i][2] = cacheV[j][2]; //column with travel duration
        sourceV[i][3] = cacheV[j][3]; //column with travel distance
      }
    }
    array.push([sourceV[i][2], sourceV[i][3]]);
  }
  sourceSheet.getRange(4, 13, array.length, 2).setValues(array);
}

///if origin or destination are blank, label as 'missing value'
function missingOD() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sourceSheet = ss.getSheetByName("MonthlySA");
  var sourceID = sourceSheet.getRange(4, 3, sourceSheet.getLastRow()-3, 12);
  var sourceV = sourceID.getValues();

  var array = [];
  for (var i = 0; i < sourceV.length; i++) {
    // if ID has a value
    if (sourceV[i][0] != "") {
      // if origin or destination is blank
      if (sourceV[i][8] == "" || sourceV[i][9] == "") {
        sourceV[i][10] = 'missing value';
      }
    }
    array.push([sourceV[i][10]]);
  }
  sourceSheet.getRange(4, 13, array.length, 1).setValues(array);
}

///if cache not found - get the new travelTime for that origin/destination on sourceSheet...
function newTravelTime() {//<--

}

///...and store the new travelTime() in cacheSheet
function storeTravelTime() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sourceSheet = ss.getSheetByName("MonthlySA");
  var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
  var sourceV = sourceR.getValues();
  var cacheSheet = ss.getSheetByName("TravelTimeCache");
  var cacheR = cacheSheet.getRange(2, 1, cacheSheet.getLastRow()-1, 4); 
  var cacheV = cacheR.getValues();

  var array = [];
  for (var i = 0; i < sourceV.length; i++) {
    var duplicate = false;
    for (var j = 0; j < cacheV.length; j++) {
      if (sourceV[i][0]+sourceV[i][1] == cacheV[j][0]+cacheV[j][1]) {
        duplicate = true; 
      }
    }
    if(!duplicate){ //if origin/destination columns from sourceSheet are NOT matched on cacheSheet
    array.push([sourceV[i][0], sourceV[i][1], sourceV[i][2], sourceV[i][3]]);//columns with new data
    }
  }
  //add new data to last row of cacheSheet
  cacheSheet.getRange(cacheSheet.getLastRow()+1, 1, array.length, 4).setValues(array);
}
ladyhbomb
  • 31
  • 4
  • 1
    Guys, this user seems to be a new user, can we please be little nice and give scope for improvement ? Instead of no-explanation-down-vote – Umair Mohammad Aug 05 '19 at 20:59
  • Ty Umair, and for uping it back! lol "seems?" Such a nice way to put it, lol. Definitely no way to treat the new girl on the block. – ladyhbomb Aug 05 '19 at 23:18

1 Answers1

1

One of the easiest solution thats coming to mind is Caching. Instead of making API calls everytime check if we have already made that call previously.

Something like this

function getTravelTime(origin, destination) {
  var travelTime = getTravelTimeFromPreviousCall(origin, destination);
  if (travelTime != null) {
    return travelTime;
  } else {
    var travelTime = fetchTravelTime(origin, destination);
    storeTravelTime(origin, destination, travelTime);
    return travelTime;
  }
}

function fetchTravelTime(origin, destination) {
  var API_KEY = PropertiesService.getScriptProperties().getProperty('keyMaps');
  var baseUrl = "https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=";
  var queryUrl = baseUrl + "place_id:" + origin + "&destinations=" + "place_id:" + destination + "&mode=driving" + "&key=" + API_KEY;

  var response = UrlFetchApp.fetch(queryUrl);
  var json = response.getContentText();
  var time = JSON.parse(json);

  return time.rows[0].elements[0].duration.text;
}

For this we can define our cache something like :

A sheet with column -

  1. origin
  2. destination
  3. travel time

And we need to define following functions:

getTravelTimeFromPreviousCall(origin, destination) : In this we need to check cache and return travel time for that origin & destination, if not found then return null

storeTravelTime(origin, destination, time) : This will only store travel time for future use in cache sheet

You can try something like this :

function getTravelTimeFromPreviousCall(origin, destination) {
    var cacheSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(CACHE_SHEET_NAME);
    var cacheData = sheet.getDataRange().getValues();

    for (var i=0; i<cacheData.length; i++) {
        if (cacheData[i][ORIGIN_COLUMN_INDEX]==origin && cacheData[i][DESTINATION_COLUMN_INDEX]==destination) {
            return cacheData[i][TRAVEL_TIME_COLUMN_INDEX];
        }
    }

    return null;
}

function storeTravelTime(origin, destination, travelTime) {  
    var cacheSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(CACHE_SHEET_NAME);
    sheet.appendRow([origin, destination, travelTime]);
}

Please fix loop variable, array indexes & constants, as per your sheet.

Umair Mohammad
  • 4,489
  • 2
  • 20
  • 34
  • 1
    Thank you for helping with a solution but I'm definitely not understanding a few things. Can you explain how cache is defined in your answer because I don't understand how it should be working for me? I assumed I would need to change formulas in column `R` to `getTravelTime()` because it called `travelTime()` within the code, but that resulted in undefined error. Then I tried `travelTime()` with viewing just a "Service Day" and back viewing full "Service Month", both resulted in new calls. Either I don't understand how cache should be working or have entered something incorrectly. – ladyhbomb Aug 05 '19 at 19:04
  • All I get returned is "ReferenceError: "foundInPreviousCall" is not defined." – ladyhbomb Aug 05 '19 at 20:19
  • Have you defined the 3 extra functions ? – Umair Mohammad Aug 05 '19 at 20:56
  • oh... clearly I have not. I did mention newbie. Ok, working on that now. Unfortunately everything takes me 20x longer than you pro coders. Stay tuned... – ladyhbomb Aug 05 '19 at 23:10
  • I have edited my post with the functions I have worked out but could use some help with putting them all together and if you have any advice with my script writing abilities. – ladyhbomb Aug 17 '19 at 20:26
  • Hey @ladyhbomb I'm no way close to a coder also :sigh: ~pro~ (strikeout). I have updated my answer with utility functions, let me know if any more help. – Umair Mohammad Aug 19 '19 at 15:05
  • Ty for more advice but several lines conflict with... [link](https://developers.google.com/apps-script/guides/sheets/functions?hl=fr-FR#using_apps_script_services) . "A custom function can not modify the structure of the spreadsheet" so calling `appendRow()` or `openById` are not allowed. Which is part of the reason I started abandoning the idea of writing a custom function that invokes it from a cell in the spreadsheet and just creating an Apps Script function with an OnEdit trigger for the whole range. – ladyhbomb Aug 20 '19 at 17:12
  • But I have been trying to make changes with your ideas for using the custom function still. The 'openById' was easy enough to swap with `getActiveSpreadsheet` but the `appendRow` has me puzzled still and everything is working now up to the `storeTravelTime` function. So far I've gotten... – ladyhbomb Aug 20 '19 at 17:20
  • I made changes to your answer that will stop the Error but still haven't figured out `storeTravelTime` – ladyhbomb Aug 20 '19 at 20:45