Add Google Maps route avoidance flags
This commit is contained in:
@@ -32,5 +32,15 @@ Required Google APIs for the key:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
node integrations/google-maps/traffic.js eta --from "DFW Airport" --to "Love Field" --departAt now
|
node integrations/google-maps/traffic.js eta --from "DFW Airport" --to "Love Field" --departAt now
|
||||||
|
node integrations/google-maps/traffic.js eta --from "DFW Airport" --to "Love Field" --avoidTolls
|
||||||
node integrations/google-maps/traffic.js leave-by --from "Home" --to "DFW Airport" --arriveBy 2026-03-17T08:30:00-05:00
|
node integrations/google-maps/traffic.js leave-by --from "Home" --to "DFW Airport" --arriveBy 2026-03-17T08:30:00-05:00
|
||||||
|
node integrations/google-maps/traffic.js leave-by --from "Home" --to "DFW Airport" --arriveBy 2026-03-17T08:30:00-05:00 --avoidTolls
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Optional route modifiers
|
||||||
|
|
||||||
|
- `--avoidTolls`
|
||||||
|
- `--avoidHighways`
|
||||||
|
- `--avoidFerries`
|
||||||
|
|
||||||
|
These flags are passed through to the Google Routes API `routeModifiers` field and work with both `eta` and `leave-by`.
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ Commands:
|
|||||||
Optional flags:
|
Optional flags:
|
||||||
--keyPath <path> API key file path (default: ${DEFAULT_KEY_PATH})
|
--keyPath <path> API key file path (default: ${DEFAULT_KEY_PATH})
|
||||||
--timeZone <IANA> Display timezone (default: America/Chicago)
|
--timeZone <IANA> Display timezone (default: America/Chicago)
|
||||||
|
--avoidTolls Avoid toll roads when possible
|
||||||
|
--avoidHighways Avoid highways when possible
|
||||||
|
--avoidFerries Avoid ferries when possible
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- Requires Google Maps APIs (Routes API + Geocoding API) enabled for the key.
|
- Requires Google Maps APIs (Routes API + Geocoding API) enabled for the key.
|
||||||
@@ -82,7 +85,15 @@ function fmtMinutes(sec) {
|
|||||||
return `${Math.round(sec / 60)} min`;
|
return `${Math.round(sec / 60)} min`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function computeRoute({ from, to, departAt, key }) {
|
function routeModifiersFromOpts(opts = {}) {
|
||||||
|
const modifiers = {};
|
||||||
|
if (opts.avoidTolls) modifiers.avoidTolls = true;
|
||||||
|
if (opts.avoidHighways) modifiers.avoidHighways = true;
|
||||||
|
if (opts.avoidFerries) modifiers.avoidFerries = true;
|
||||||
|
return Object.keys(modifiers).length ? modifiers : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function computeRoute({ from, to, departAt, key, modifiers }) {
|
||||||
const [o, d] = await Promise.all([geocode(from, key), geocode(to, key)]);
|
const [o, d] = await Promise.all([geocode(from, key), geocode(to, key)]);
|
||||||
const body = {
|
const body = {
|
||||||
origin: { location: { latLng: { latitude: o.lat, longitude: o.lng } } },
|
origin: { location: { latLng: { latitude: o.lat, longitude: o.lng } } },
|
||||||
@@ -94,6 +105,8 @@ async function computeRoute({ from, to, departAt, key }) {
|
|||||||
units: 'IMPERIAL',
|
units: 'IMPERIAL',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (modifiers) body.routeModifiers = modifiers;
|
||||||
|
|
||||||
if (departAt && departAt !== 'now') body.departureTime = new Date(departAt).toISOString();
|
if (departAt && departAt !== 'now') body.departureTime = new Date(departAt).toISOString();
|
||||||
|
|
||||||
const res = await fetch('https://routes.googleapis.com/directions/v2:computeRoutes', {
|
const res = await fetch('https://routes.googleapis.com/directions/v2:computeRoutes', {
|
||||||
@@ -136,7 +149,8 @@ async function cmdEta(opts) {
|
|||||||
const tz = opts.timeZone || 'America/Chicago';
|
const tz = opts.timeZone || 'America/Chicago';
|
||||||
const departTs = opts.departAt && opts.departAt !== 'now' ? new Date(opts.departAt).getTime() : Date.now();
|
const departTs = opts.departAt && opts.departAt !== 'now' ? new Date(opts.departAt).getTime() : Date.now();
|
||||||
|
|
||||||
const route = await computeRoute({ from: opts.from, to: opts.to, departAt: opts.departAt || 'now', key });
|
const modifiers = routeModifiersFromOpts(opts);
|
||||||
|
const route = await computeRoute({ from: opts.from, to: opts.to, departAt: opts.departAt || 'now', key, modifiers });
|
||||||
const arriveTs = departTs + (route.durationSec || 0) * 1000;
|
const arriveTs = departTs + (route.durationSec || 0) * 1000;
|
||||||
|
|
||||||
console.log(JSON.stringify({
|
console.log(JSON.stringify({
|
||||||
@@ -147,6 +161,9 @@ async function cmdEta(opts) {
|
|||||||
eta: fmtMinutes(route.durationSec),
|
eta: fmtMinutes(route.durationSec),
|
||||||
trafficDelay: route.staticSec && route.durationSec ? fmtMinutes(Math.max(0, route.durationSec - route.staticSec)) : null,
|
trafficDelay: route.staticSec && route.durationSec ? fmtMinutes(Math.max(0, route.durationSec - route.staticSec)) : null,
|
||||||
distanceMiles: route.distanceMiles ? Number(route.distanceMiles.toFixed(1)) : null,
|
distanceMiles: route.distanceMiles ? Number(route.distanceMiles.toFixed(1)) : null,
|
||||||
|
avoidTolls: !!opts.avoidTolls,
|
||||||
|
avoidHighways: !!opts.avoidHighways,
|
||||||
|
avoidFerries: !!opts.avoidFerries,
|
||||||
timeZone: tz,
|
timeZone: tz,
|
||||||
}, null, 2));
|
}, null, 2));
|
||||||
}
|
}
|
||||||
@@ -158,14 +175,16 @@ async function cmdLeaveBy(opts) {
|
|||||||
const arriveTs = new Date(opts.arriveBy).getTime();
|
const arriveTs = new Date(opts.arriveBy).getTime();
|
||||||
if (!Number.isFinite(arriveTs)) throw new Error('Invalid --arriveBy ISO datetime');
|
if (!Number.isFinite(arriveTs)) throw new Error('Invalid --arriveBy ISO datetime');
|
||||||
|
|
||||||
|
const modifiers = routeModifiersFromOpts(opts);
|
||||||
|
|
||||||
// two-pass estimate
|
// two-pass estimate
|
||||||
let departGuess = arriveTs - 45 * 60 * 1000;
|
let departGuess = arriveTs - 45 * 60 * 1000;
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
const route = await computeRoute({ from: opts.from, to: opts.to, departAt: new Date(departGuess).toISOString(), key });
|
const route = await computeRoute({ from: opts.from, to: opts.to, departAt: new Date(departGuess).toISOString(), key, modifiers });
|
||||||
departGuess = arriveTs - (route.durationSec || 0) * 1000;
|
departGuess = arriveTs - (route.durationSec || 0) * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalRoute = await computeRoute({ from: opts.from, to: opts.to, departAt: new Date(departGuess).toISOString(), key });
|
const finalRoute = await computeRoute({ from: opts.from, to: opts.to, departAt: new Date(departGuess).toISOString(), key, modifiers });
|
||||||
|
|
||||||
console.log(JSON.stringify({
|
console.log(JSON.stringify({
|
||||||
from: finalRoute.origin,
|
from: finalRoute.origin,
|
||||||
@@ -175,6 +194,9 @@ async function cmdLeaveBy(opts) {
|
|||||||
eta: fmtMinutes(finalRoute.durationSec),
|
eta: fmtMinutes(finalRoute.durationSec),
|
||||||
trafficDelay: finalRoute.staticSec && finalRoute.durationSec ? fmtMinutes(Math.max(0, finalRoute.durationSec - finalRoute.staticSec)) : null,
|
trafficDelay: finalRoute.staticSec && finalRoute.durationSec ? fmtMinutes(Math.max(0, finalRoute.durationSec - finalRoute.staticSec)) : null,
|
||||||
distanceMiles: finalRoute.distanceMiles ? Number(finalRoute.distanceMiles.toFixed(1)) : null,
|
distanceMiles: finalRoute.distanceMiles ? Number(finalRoute.distanceMiles.toFixed(1)) : null,
|
||||||
|
avoidTolls: !!opts.avoidTolls,
|
||||||
|
avoidHighways: !!opts.avoidHighways,
|
||||||
|
avoidFerries: !!opts.avoidFerries,
|
||||||
timeZone: tz,
|
timeZone: tz,
|
||||||
}, null, 2));
|
}, null, 2));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user