Skip to content

Instantly share code, notes, and snippets.

@chrahunt
Created May 30, 2016 05:56
Show Gist options
  • Select an option

  • Save chrahunt/f7d6eb3371db433bb73ff6f4f07c4792 to your computer and use it in GitHub Desktop.

Select an option

Save chrahunt/f7d6eb3371db433bb73ff6f4f07c4792 to your computer and use it in GitHub Desktop.

Revisions

  1. chrahunt created this gist May 30, 2016.
    110 changes: 110 additions & 0 deletions Code.gs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,110 @@
    var Properties = PropertiesService.getScriptProperties();

    /**
    * Return rate-limited function under specified delay.
    * @param {Function} fn The function to wrap.
    * @param {string} domain The domain under which to apply the delay.
    * @param {number} delay Time (in ms) between successive calls.
    * @return {Function} The function to call.
    */
    function RateLimited(fn, domain, delay) {
    var key = domain + "-lastcall";
    return function() {
    var now = Date.now();
    var lock = LockService.getDocumentLock();
    lock.waitLock(5000);
    var last_call = Properties.getProperty(key);
    var delay_exec = 0;
    if (last_call === null) {
    Properties.setProperty(key, now);
    } else {
    var next = +last_call + delay;
    if (next > now) {
    delay_exec = next - now;
    Properties.setProperty(key, next);
    } else {
    Properties.setProperty(key, now);
    }
    }
    lock.releaseLock();
    if (delay_exec) {
    Utilities.sleep(delay_exec);
    }
    return fn.apply(null, arguments);
    };
    }

    // Safe, rate-limited methods.
    var Safe = {
    getDrivingTime: RateLimited(getDrivingTime, "maps", 2000)
    };

    // "hash" arbitrary number of string arguments to use as a key.
    function hash() {
    var out = "";
    for (var i = 0; i < arguments.length; i++) {
    out += arguments[i];
    }
    return out;
    }

    /**
    * Get driving time given origin, destination, and arrival time.
    * @private
    * @param {string} origin
    * @param {string} dest
    * @param {Date} arrival
    * @return {number?} The driving time in seconds or null if no route was found.
    */
    function getDrivingTime(origin, dest, arrival) {
    var directions = Maps.newDirectionFinder()
    .setOrigin(origin)
    .setDestination(dest)
    .setMode(Maps.DirectionFinder.Mode.DRIVING)
    .setArrive(arrival)
    .getDirections();
    if (directions && directions.routes.length > 0) {
    var sum = directions.routes[0].legs.map(function (leg) {
    return leg.duration.value;
    }).reduce(function (sum, leg) {
    return sum + leg;
    });
    return sum;
    } else {
    return null;
    }
    }

    /**
    * Calculates the time it takes to travel between two locations.
    *
    * @param {(string|range)} start The start address.
    * @param {string} end The end address.
    * @return {string} The driving time.
    * @customfunction
    */
    function drivingTime(start, end) {
    // Handle range in start.
    if (start.map) {
    return start.map(function (s) { return drivingTime(s, end); });
    }
    var startWorkDay = new Date(2016, 5, 23, 9, 30);
    var cache = CacheService.getDocumentCache();
    var key = hash(start, end, startWorkDay);
    var val = cache.get(key);
    // Clean up bad data.
    if (isNaN(val)) {
    cache.remove(key);
    val = null;
    }
    // Cache miss.
    if (val === null) {
    val = Safe.getDrivingTime(start, end, startWorkDay);
    if (!isNaN(val) && typeof val == "number") {
    cache.put(key, val);
    } else {
    return "Not found";
    }
    }
    return Math.ceil(val / 60) + "m";
    }