Skip to content

Instantly share code, notes, and snippets.

@zamai
Created October 19, 2026 06:49
Show Gist options
  • Select an option

  • Save zamai/72064cc39c2ca1b2db17fec34ae3a506 to your computer and use it in GitHub Desktop.

Select an option

Save zamai/72064cc39c2ca1b2db17fec34ae3a506 to your computer and use it in GitHub Desktop.
Google Maps place links to exact Waze coordinates (Scriptable source and setup)
// Scriptable script. Input: one Google Maps URL as the Shortcuts "Parameter".
// Output: a waze:// URL, or an ERROR: message. No place-name fallback.
function fail(message) { throw new Error(message); }
function firstURL(value) {
const match = String(value || "").match(/https?:\/\/[^\s<>"']+/i);
if (!match) fail("Provide a Google Maps URL.");
return match[0].replace(/[),.;]+$/, "");
}
function urlParts(url) {
const match = /^https:\/\/([^/?#:]+)(\/[^?#]*)?(?:\?([^#]*))?/i.exec(url);
if (!match) fail("Only HTTPS Google Maps links are supported.");
const host = match[1].toLowerCase();
const google = /^(?:(?:www|maps|consent)\.)?google\.(?:com|[a-z]{2}|co\.[a-z]{2})$/.test(host);
if (!google && host !== "maps.app.goo.gl" && host !== "goo.gl") {
fail("This is not a supported Google Maps link.");
}
return { host, path: match[2] || "/", query: match[3] || "" };
}
function parameter(url, key) {
const query = urlParts(url).query;
for (const field of query.split("&")) {
const equal = field.indexOf("=");
if (equal < 0 || field.slice(0, equal) !== key) continue;
try { return decodeURIComponent(field.slice(equal + 1).replace(/\+/g, "%20")); }
catch (_) { fail("The Google Maps URL has invalid encoding."); }
}
return null;
}
function unwrapConsent(url) {
for (let i = 0; i < 3 && urlParts(url).host.startsWith("consent.google."); i++) {
const next = parameter(url, "continue");
if (!next) fail("Google's consent page did not contain a destination URL.");
urlParts(next); // Check the nested URL before requesting it.
url = next;
}
if (urlParts(url).host.startsWith("consent.google.")) fail("Google consent redirects nested too deeply.");
return url;
}
function addConsentBypass(url) {
if (parameter(url, "ucbcb") !== null) return url;
const hash = url.indexOf("#");
const head = hash < 0 ? url : url.slice(0, hash);
const tail = hash < 0 ? "" : url.slice(hash);
return head + (head.includes("?") ? "&" : "?") + "ucbcb=1" + tail;
}
function coordinatePair(latText, lonText) {
const lat = Number(latText), lon = Number(lonText);
if (!Number.isFinite(lat) || !Number.isFinite(lon) || Math.abs(lat) > 90 || Math.abs(lon) > 180) {
fail("The destination coordinates are invalid.");
}
return [lat, lon];
}
function literalPair(text) {
const match = /^\s*(-?\d{1,2}(?:\.\d+)?)\s*,\s*(-?\d{1,3}(?:\.\d+)?)\s*$/.exec(text || "");
return match ? coordinatePair(match[1], match[2]) : null;
}
function featureID(url) {
const ftid = parameter(url, "ftid");
if (ftid && /^0x[0-9a-f]+:0x[0-9a-f]+$/i.test(ftid)) return ftid.toLowerCase();
const match = /!1s(0x[0-9a-f]+:0x[0-9a-f]+)/i.exec(url);
return match ? match[1].toLowerCase() : null;
}
function explicitDestination(url) {
const parts = urlParts(url);
if (parts.host === "maps.app.goo.gl" || parts.host === "goo.gl") return null;
// In Maps URLs, a place ID takes precedence over a query coordinate.
if (!parameter(url, "destination_place_id")) {
const destination = literalPair(parameter(url, "destination"));
if (destination) return destination;
}
if (/^\/maps\/place\//.test(parts.path)) {
const pairs = [];
const pattern = /!3d(-?\d+(?:\.\d+)?)!4d(-?\d+(?:\.\d+)?)/g;
let match;
while ((match = pattern.exec(url)) !== null) pairs.push(coordinatePair(match[1], match[2]));
if (pairs.length && pairs.every(p => p[0] === pairs[0][0] && p[1] === pairs[0][1])) return pairs[0];
if (pairs.length) fail("The place URL contains conflicting destination coordinates.");
}
if (!featureID(url) && !parameter(url, "query_place_id")) {
const query = literalPair(parameter(url, "query")) || literalPair(parameter(url, "q"));
if (query) return query;
}
return null; // Deliberately ignore map viewport @lat,lng and ll=.
}
const desktopHeaders = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/131.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9"
};
async function headFinalURL(url) {
urlParts(url);
const request = new Request(url);
request.method = "HEAD";
request.headers = desktopHeaders;
request.timeoutInterval = 10;
await request.load();
if (!request.response || request.response.statusCode !== 200) fail("Google did not resolve the short link.");
const finalURL = request.response.url || url;
urlParts(finalURL);
return finalURL;
}
function smallPreviewURL(id) {
const pb = "!1m5!1s" + id + "!2s!3m2!1i1024!2i768";
return "https://www.google.com/maps/preview/place?pb=" + encodeURIComponent(pb);
}
async function getPage(url) {
urlParts(url);
const request = new Request(url);
request.headers = desktopHeaders;
request.timeoutInterval = 20;
const body = await request.loadString();
const finalURL = request.response && request.response.url || url;
urlParts(finalURL);
if (!request.response || request.response.statusCode !== 200) {
fail("Google returned HTTP " + (request.response && request.response.statusCode || "error") + ".");
}
return { body, url: finalURL };
}
function previewURL(html, id) {
const match = /<link\b[^>]*href="(\/maps\/preview\/place\?[^"<>]+)"/i.exec(html);
if (!match) fail("Google did not expose place details for this link.");
const path = match[1].replace(/&amp;/g, "&");
let decoded;
try { decoded = decodeURIComponent(path).toLowerCase(); }
catch (_) { fail("Google's place details link was unreadable."); }
if (!decoded.includes(id)) {
fail("Google's place details did not match the shared place.");
}
return "https://www.google.com" + path;
}
function coordinatesForID(text, id) {
let data;
try { data = JSON.parse(text.replace(/^\)\]\}'\s*/, "")); }
catch (_) { fail("Google's place data was unreadable."); }
const found = [];
function visit(value) {
if (!Array.isArray(value)) return;
for (let i = 0; i < value.length - 1; i++) {
const pair = value[i];
if (typeof value[i + 1] === "string" && value[i + 1].toLowerCase() === id &&
Array.isArray(pair) && pair[0] === null && pair[1] === null &&
typeof pair[2] === "number" && typeof pair[3] === "number") {
found.push(coordinatePair(pair[2], pair[3]));
}
}
for (const child of value) visit(child);
}
visit(data);
if (!found.length) fail("Google did not provide coordinates tied to this place ID.");
if (!found.every(p => p[0] === found[0][0] && p[1] === found[0][1])) {
fail("Google returned conflicting coordinates for this place.");
}
return found[0];
}
async function resolve(input) {
let url = unwrapConsent(firstURL(input));
let coordinates = explicitDestination(url);
if (coordinates) return coordinates;
// Fast path: resolve only the redirect, then ask for details by the same ID.
// The ID-only place endpoint is undocumented, so retain the page-based fallback.
try {
const finalURL = featureID(url) ? url : await headFinalURL(url);
if (!urlParts(finalURL).host.startsWith("consent.google.")) {
coordinates = explicitDestination(finalURL);
if (coordinates) return coordinates;
const id = featureID(finalURL);
if (id) {
const details = await getPage(smallPreviewURL(id));
return coordinatesForID(details.body, id);
}
}
} catch (_) {
// A failed or changed fast endpoint must not become a guessed destination.
}
for (let attempt = 0; attempt < 3; attempt++) {
const page = await getPage(url);
const finalURL = unwrapConsent(page.url);
if (finalURL !== page.url) {
url = addConsentBypass(finalURL);
continue;
}
coordinates = explicitDestination(finalURL);
if (coordinates) return coordinates;
const id = featureID(finalURL);
if (!id) fail("This Maps link has no explicit coordinates or identifiable place ID.");
const details = await getPage(previewURL(page.body, id));
if (urlParts(details.url).host.startsWith("consent.google.")) {
fail("Google requires consent before exposing this place. Try again later or provide a place URL containing !3d and !4d coordinates.");
}
return coordinatesForID(details.body, id);
}
fail("Google kept redirecting to consent. Try again later or provide a place URL containing !3d and !4d coordinates.");
}
try {
const [lat, lon] = await resolve(args.shortcutParameter);
Script.setShortcutOutput("waze://?ll=" + lat + "," + lon + "&navigate=yes");
} catch (error) {
Script.setShortcutOutput("ERROR: " + (error && error.message || String(error)));
}
Script.complete();

Google Maps to Waze — Scriptable setup

This shortcut needs Scriptable, Google Maps, and Waze on your iPhone. The iCloud shortcut does not bundle Scriptable's JavaScript file, so install that file first:

  1. Copy the full contents of Google Maps to Waze.js from this gist.
  2. In Scriptable, create a new script named Google Maps to Waze, paste the code, and save it.
  3. Install the shortcut from its RoutineHub page. In Shortcuts, check its Run Script action: select the script Google Maps to Waze if Shortcuts did not select it automatically.
  4. Give Scriptable network access when prompted. Share a Google Maps place link to the shortcut, or copy the link and run the shortcut from Shortcuts.

The shortcut converts a Google Maps destination to coordinates and opens waze://?ll=latitude,longitude&navigate=yes. The example https://maps.app.goo.gl/ZJ4EJxQLgfS3WNgf8?g_st=it resolves to 52.1182836,21.0925635 (Parking w Powsinie, Warsaw).

Shortened place links normally require HTTP requests to Google. Explicit destination coordinates in a full link can be handled without a request. The script deliberately ignores map viewport coordinates and does not substitute a name search. If Google changes or blocks its undocumented place-data format, the shortcut shows an error rather than navigating to a guessed point. Waze can snap its route to a nearby accessible road.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment