我有两个实现,我尝试获取任意驾驶路线的持续时间,并使用 Google Sheets 中的 Apps 脚本设置到达或出发时间。我已经用多个出发地、目的地和时间组合对它们进行了测试,但我无法返回因到达或出发时间而异的持续时间。我已经验证直接访问 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;
};
我怎样才能让这些实现之一与出发或到达时间一起工作?
请在下面找到一个自定义函数,用于从
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 服务配额。
这很有趣,我想建议使用事件或触发器,并更新所有路线/到达和目的地
一些补充阅读..
常见问题:在开始之前存在一些问题其他在地图+表格+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;
}