GSheets 应用程序脚本中的 Maps.newDirectionFinder().SetArrive 和 SetDepart(Google 地图 API)不会影响经过测试的脚本返回的驾驶时间

问题描述 投票:0回答:2

我有两个实现,我尝试获取任意驾驶路线的持续时间,并使用 Google Sheets 中的 Apps 脚本设置到达或出发时间。我已经用多个出发地、目的地和时间组合对它们进行了测试,但我无法返回因到达或出发时间而异的持续时间。我已经验证直接访问 Google 地图时路线时间确实会有所不同。

这是 Google 电子表格,展示并跟踪所有这一切。

实现 1(时间在脚本中是硬编码的,但我对其进行了更改以进行测试):

function GetDuration(location1, location2, mode) {
   //var arrive= new Date(2022, 07, 04, 18);// 7th of July 06:00 am
   var arrive= new Date(2022, 07, 04, 17);
   //var arrive = new Date(new Date().getTime() + (10 * 60 * 60 * 1000));//arrive in ten hours from now
   //var directions  = Maps.newDirectionFinder().setDepart(arrive)
   var directions  = Maps.newDirectionFinder().setArrive(arrive)
  .setOrigin(location1)
  .setDestination(location2)
  .setMode(Maps.DirectionFinder.Mode[mode])
  .getDirections();
 return directions.routes[0].legs[0].duration.text;
}

和实现2(时间是一个变量

adrive
从GSheet读入):

const GOOGLEMAPS_DURATION = (origin, destination, adrive, mode = "driving") => {
  if (!origin || !destination) {
    throw new Error("No address specified!");
  }
  if (origin.map) {
    return origin.map(DISTANCE);
  }
  const key = ["duration", origin, destination, adrive, mode].join(",");
  const value = getCache(key);
  if (value !== null) return value;
  const { routes: [data] = [] } = Maps.newDirectionFinder()
    .setOrigin(origin)
//    .setDepart(adrive)
    .setArrive(adrive)
    .setDestination(destination)
    .setMode(mode)
    .getDirections();
  if (!data) {
    throw new Error("No route found!");
  }
  const { legs: [{ duration: { text: time } } = {}] = [] } = data;
  setCache(key, time);
  return time;
};

我怎样才能让这些实现之一与出发或到达时间一起工作?

google-maps google-apps-script google-sheets google-maps-api-3
2个回答
3
投票

请在下面找到一个自定义函数,用于从

Maps
服务获取驾驶或步行距离和持续时间以及其他此类数据。该函数检查参数,可以一次性迭代更大范围的值,并使用
CacheService
将结果缓存长达六个小时,以帮助避免超出速率限制。

要查找行驶距离,您只需指定

start_address
end_address

要查找行驶时间,您需要另外指定

units
"hours"
中的
"minutes"
travel_mode
depart_time
。请注意,您需要指定未来开始行程的时间,因为持续时间取决于是否是高峰时段等。

该函数使用 .setDepart() 完成持续时间获取。结果位于

.getDirections()
响应中的 duration_in_traffic 字段中。请注意,该字段仅在出发时间为不是过去而是未来时才可用。

要测试该函数,请将将来的日期时间值放入单元格

D2:D
中,然后将此公式插入单元格
J2
中:

=GoogleMapsDistance(A2:A13, B2:B13, "minutes", "driving", D2:D13)

'use strict';


/**
* Gets the distance or duration between two addresses.
*
* Accepts ranges such as S2:S100 for the start and end addresses.
*
* @param {"Hyde Park, London"} start_address The origin address.
* @param {"Trafalgar Sq, London"} end_address The destination address.
* @param {"miles"} units Optional. One of "kilometers", "miles", "minutes" or "hours". Defaults to "kilometers".
* @param {"walking"} travel_mode Optional. One of "bicycling", "driving", "transit", "walking". Defaults to "driving".
* @param {to_date(value("2029-07-19 14:15:00"))} depart_time Optional. A reference to a datetime cell. The datetime cannot be in the past. Use "now" to refer to the current date and time.
* @return {Number} The distance or duration between start_address and end_address at the moment of depart.
* @license https://www.gnu.org/licenses/gpl-3.0.html
* @customfunction
*/
function GoogleMapsDistance(start_address, end_address, units = 'kilometers', travel_mode = 'driving', depart_time = new Date()) {
  // version 1.2, written by --Hyde, 19 July 2022
  //  - see https://stackoverflow.com/a/73015812/13045193
  if (arguments.length < 2 || arguments.length > 5) {
    throw new Error(`Wrong number of arguments to GoogleMapsDistance. Expected 2 to 5 arguments, but got ${arguments.length} arguments.`);
  }
  const _get2dArray = (value) => Array.isArray(value) ? value : [[value]];
  const now = new Date();
  const endAddress = _get2dArray(end_address);
  const startAddress = Array.isArray(start_address) || !Array.isArray(end_address)
    ? _get2dArray(start_address)
    : endAddress.map(row => row.map(_ => start_address));
  return startAddress.map((row, rowIndex) => row.map((start, columnIndex) => {
    let [end, unit, mode, depart] = [end_address, units, travel_mode, depart_time]
      .map(value => Array.isArray(value) ? value[rowIndex][columnIndex] : value);
    if (!depart || depart === 'now') {
      depart = now;
    }
    try {
      return start && end ? googleMapsDistance_(start, end, unit, mode, depart) : null;
    } catch (error) {
      if (startAddress.length > 1 || startAddress[0].length > 1) {
        return NaN;
      }
      throw error;
    }
  }));
}


/**
* Gets the distance or duration between two addresses as acquired from the Maps service.
* Caches results for up to six hours to help avoid exceeding rate limits.
* The departure date must be in the future. Returns distance and duration for expired
* departures only when the result is already in the cache.
*
* @param {String} startAddress The origin address.
* @param {String} endAddress The destination address.
* @param {String} units One of "kilometers", "miles", "minutes" or "hours".
* @param {String} mode One of "bicycling", "driving", "transit" or "walking".
* @param {Date} depart The future moment of departure.
* @return {Number} The distance or duration between startAddress and endAddress.
* @license https://www.gnu.org/licenses/gpl-3.0.html
*/
function googleMapsDistance_(startAddress, endAddress, units, mode, depart) {
  // version 1.1, written by --Hyde, 19 July 2022
  const functionName = 'GoogleMapsDistance';
  units = String(units).trim().toLowerCase().replace(/^(kms?|kilomet.*)$/i, 'kilometers');
  if (!['kilometers', 'miles', 'minutes', 'hours'].includes(units)) {
    throw new Error(`${functionName} expected units of "kilometers", "miles", "minutes" or "hours" but got "${units}" instead.`);
  }
  mode = String(mode).toLowerCase();
  if (!['bicycling', 'driving', 'transit', 'walking'].includes(mode)) {
    throw new Error(`${functionName} expected a mode of "bicycling", "driving", "transit" or "walking" but got "${mode}" instead.`);
  }
  if (!depart || !depart.toISOString) {
    throw new Error(`${functionName} expected a depart time that is a valid datetime value, but got the ${typeof depart} "${depart}" instead.`);
  }
  const _isMoreThan10SecsInThePast = (date) => Math.trunc((date.getTime() - new Date().getTime()) / 10000) < 0;
  const _simplifyLeg = (leg) => {
    const { distance, duration, duration_in_traffic } = leg;
    return { distance: distance, duration: duration, duration_in_traffic: duration_in_traffic };
  };
  const cache = CacheService.getScriptCache();
  const cacheKey = [functionName, startAddress, endAddress, mode, depart.toISOString()].join('→');
  const cached = cache.get(cacheKey);
  let firstLeg;
  if (cached) {
    firstLeg = _simplifyLeg(JSON.parse(cached));
  } else {
    if (_isMoreThan10SecsInThePast(depart)) {
      throw new Error(`The departure time ${depart.toISOString()} is in the past, which is not allowed.`);
    }
    const directions = Maps.newDirectionFinder()
      .setOrigin(startAddress)
      .setDestination(endAddress)
      .setMode(Maps.DirectionFinder.Mode[mode.toUpperCase()])
      .setDepart(depart)
      .getDirections();
    if (directions && directions.routes && directions.routes.length && directions.routes[0].legs) {
      firstLeg = _simplifyLeg(directions['routes'][0]['legs'][0]);
    } else {
      throw new Error(`${functionName} could not find the distance between "${startAddress}" and "${endAddress}".`);
    }
    cache.put(cacheKey, JSON.stringify(firstLeg), 6 * 60 * 60); // 6 hours
  }
  const meters = firstLeg['distance']['value'];
  const seconds = firstLeg['duration_in_traffic']
    ? firstLeg['duration_in_traffic']['value']
    : firstLeg['duration']['value'];
  switch (units) {
    case 'kilometers':
      return meters / 1000;
    case 'miles':
      return meters / 1609.344;
    case 'minutes':
      return seconds / 60;
    case 'hours':
      return seconds / 60 / 60;
  }
}

请参阅路线示例/交通信息了解更多信息。

Google 地图方向查询的消费者帐户配额为每天 1,000 次调用,而 Google Workspace 域帐户的配额为每天 10,000 次调用。结果缓存有助于避免超出限制。请参阅Google 服务配额


1
投票

这很有趣,我想建议使用事件或触发器,并更新所有路线/到达和目的地


一些补充阅读..

常见问题:在开始之前存在一些问题其他在地图+表格+zapier等第三方组件中遇到过,这可能会帮助您寻找格式化数据以正确更新的方法,请参阅此处

即时VS。轮询:Google 表格触发器被标记为“即时”,但仍需要几分钟才能触发。 Google Sheets 的触发器在 Zapier 触发器中是独一无二的。当电子表格中存在触发事件时,Zapier 会从 Google 收到有关此情况的通知 Webhook。之后,Zapier 向 Google Sheets 发送新数据请求,因此它同时使用轮询和即时触发方法。这个过程总共大约需要3分钟。



代码示例1:基于到达时间,简单

function GetYourDurationBasedonArrivalTime(point1, point2, mode) {
   //set your arrival time 5 hr times 60x60x millisec
   var arrivalTime = new Date(new Date().getTime() + (5 * 360 * 1000));

   // use your arrival time in your configuration
   var myDirections  = Maps.newDirectionFinder().setArrive(arrivalTime)
  .setOrigin(point1)
  .setDestination(point2)
  .setMode(Maps.DirectionFinder.Mode[mode])
  .getDirections();
 return myDirections.routes[0].legs[0].duration.text;
}

代码示例 2:将其自动化,因此如果您愿意,您可以触发。请根据您的需要进行更新。

//CREATING CUSTOM MENU on GOOGLE SHEETS
function onOpen() { 
  var ui = SpreadsheetApp.getUi();
   
  ui.createMenu("Google Travel Time")
    .addItem("Run","getDistance")
    .addItem("Set Triggers","createEveryMinutesTrigger")
    .addItem("Delete Triggers","deleteTrigger")
    .addToUi();
}
 
// GET TRAVEL TIME AND DISTANCE FOR EACH ORIGIN AND DESTINATION
function getDistance() {
   
   var ss = SpreadsheetApp.getActiveSpreadsheet();
   var inputSheet = ss.getSheetByName("Inputs");
   var range = inputSheet.getRange("B2:I");
   var inputs = range.getValues();
   var outputSheet = ss.getSheetByName("Outputs");
   var recordcount = outputSheet.getLastRow();
   var timeZone = "GMT+5:30";
   var now = new Date();
   var rDate = Utilities.formatDate(now, timeZone, "MM/dd/yyyy");
   var rTime = Utilities.formatDate(now, timeZone, "HH:mm:ss");
   var numberOfRoutes = inputSheet.getLastRow()-1;
   
   
  for(i=0;i<numberOfRoutes;i++){
   var setDirections = Maps.newDirectionFinder() 
     .setOrigin(inputs[i][1])
     .setDestination(inputs[i][2])
     .setDepart(now)
     .setMode(Maps.DirectionFinder.Mode["DRIVING"]); 
     
    var wayCount = inputs[i][7];
     
    for(j=0;j<wayCount;j++){
      setDirections.addWaypoint("via:"+inputs[i][3+j]);
    }
     
    var directions = setDirections.getDirections();
    
    var traveltime = directions.routes[0].legs[0].duration_in_traffic.value;
    var distance = directions.routes[0].legs[0].distance.value;
    var route = inputs[i][0];
     
     
    outputSheet.getRange(i+1+recordcount,1).setValue(route);
    outputSheet.getRange(i+1+recordcount,2).setValue(now);
    outputSheet.getRange(i+1+recordcount,3).setValue(secToMin(traveltime));
    outputSheet.getRange(i+1+recordcount,4).setValue(distance/1000);
    outputSheet.getRange(i+1+recordcount,5).setValue((distance/traveltime)*(3600/1000));
    outputSheet.getRange(i+1+recordcount,6).setValue(traveltime);
    outputSheet.getRange(i+1+recordcount,7).setValue(rDate);
    outputSheet.getRange(i+1+recordcount,8).setValue(rTime);
  }
}
 
 
// AUTOMATE IT
// RUN FUNCTION EVERY n MINUTES BETWEEN GIVEN TIME DURATION
function runGetDistance() {
  var date = new Date();  
  var day = date.getDay();
  var hrs = date.getHours();
  var min = date.getMinutes();
   
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var inputSheet = ss.getSheetByName("SetTriggers");
  var startHour = inputSheet.getRange("B1").getValue();
  var endHour = inputSheet.getRange("B2").getValue();
   
  if ((hrs >= startHour) && (hrs <= endHour) && (min >= 0) && (min <= 59 )) {
    getDistance();
  }
}
 
 
//CREATE TRIGGER  
function createEveryMinutesTrigger(){
  var ss = SpreadsheetApp.getActiveSpreadsheet();  
  var inputSheet = ss.getSheetByName("SetTriggers");
  var runningInterval = inputSheet.getRange("B6").getValue();
   
   
  ScriptApp.newTrigger("runGetDistance")
    .timeBased()
    .everyMinutes(runningInterval)
    .create();
}
 
 
//DELETE TRIGGER
function deleteTrigger() {
   
  // Loop over all triggers and delete them
  var allTriggers = ScriptApp.getProjectTriggers();
   
  for (var i = 0; i < allTriggers.length; i++) {
    ScriptApp.deleteTrigger(allTriggers[i]);
  }
}
 
function secToMin(duration){
  var minutes = parseInt((duration/60));
  var seconds = parseInt(duration%60);
   
  return "00:"+minutes+":"+seconds;
} 
© www.soinside.com 2019 - 2024. All rights reserved.