Created
November 25, 2025 07:26
-
-
Save MrOplus/6c203d564e33026e1beba82c102a8bbb to your computer and use it in GitHub Desktop.
Google Maps to Waze Intent (Cloudflare Workers)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export default { | |
| async fetch(request, env, ctx) { | |
| return handleRequest(request); | |
| } | |
| }; | |
| async function handleRequest(request) { | |
| const url = new URL(request.url); | |
| const pathname = url.pathname; | |
| if (pathname === '/test' || pathname.startsWith('/test/')) { | |
| const testUrl = pathname === '/test' | |
| ? 'https: | |
| : pathname.substring(6); | |
| const coords = await extractCoordinatesFromGoogleMapsUrl(testUrl); | |
| return new Response( | |
| JSON.stringify({ | |
| testUrl: testUrl, | |
| coordinates: coords, | |
| success: coords !== null | |
| }, null, 2), | |
| { | |
| status: 200, | |
| headers: { 'Content-Type': 'application/json' } | |
| } | |
| ); | |
| } | |
| if (pathname === '/' || pathname === '') { | |
| return new Response( | |
| `<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Google Maps to Waze Converter</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <meta name="description" content="Instantly convert any Google Maps location to Waze navigation. Supports shortened URLs, coordinates, and automatic mobile redirection."> | |
| <!-- Open Graph / Facebook --> | |
| <meta property="og:type" content="website"> | |
| <meta property="og:url" content="${url.origin}/"> | |
| <meta property="og:title" content="Google Maps to Waze Converter"> | |
| <meta property="og:description" content="Instantly convert any Google Maps location to Waze navigation. Supports shortened URLs, coordinates, and automatic mobile redirection."> | |
| <meta property="og:site_name" content="Maps to Waze"> | |
| <!-- Twitter --> | |
| <meta name="twitter:card" content="summary_large_image"> | |
| <meta name="twitter:url" content="${url.origin}/"> | |
| <meta name="twitter:title" content="Google Maps to Waze Converter"> | |
| <meta name="twitter:description" content="Instantly convert any Google Maps location to Waze navigation. Supports shortened URLs, coordinates, and automatic mobile redirection."> | |
| <!-- Additional Meta Tags --> | |
| <meta name="author" content="Claigrid"> | |
| <meta name="keywords" content="google maps, waze, navigation, converter, url shortener, maps, directions"> | |
| <meta name="theme-color" content="#000000"> | |
| <style> | |
| body { | |
| font-family: system-ui, -apple-system, sans-serif; | |
| max-width: 900px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| line-height: 1.6; | |
| background: #f5f5f5; | |
| color: #222; | |
| } | |
| .container { | |
| background: white; | |
| padding: 30px; | |
| border-radius: 12px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| h1 { | |
| color: #000; | |
| margin-top: 0; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-weight: 700; | |
| } | |
| h2 { | |
| color: #222; | |
| border-bottom: 2px solid #333; | |
| padding-bottom: 8px; | |
| margin-top: 30px; | |
| font-weight: 600; | |
| } | |
| code { | |
| background: #f4f4f4; | |
| padding: 3px 8px; | |
| border-radius: 4px; | |
| font-size: 0.9em; | |
| color: #333; | |
| border: 1px solid #ddd; | |
| word-break: break-all; | |
| } | |
| .example { | |
| background: #fafafa; | |
| padding: 15px 20px; | |
| margin: 15px 0; | |
| border-radius: 8px; | |
| border-left: 4px solid #333; | |
| border: 1px solid #e0e0e0; | |
| } | |
| .example strong { | |
| color: #000; | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| } | |
| .feature { | |
| background: #f8f8f8; | |
| padding: 15px 20px; | |
| margin: 15px 0; | |
| border-radius: 8px; | |
| border-left: 4px solid #666; | |
| border: 1px solid #e0e0e0; | |
| } | |
| .feature-title { | |
| font-weight: 600; | |
| color: #000; | |
| margin-bottom: 5px; | |
| } | |
| a { | |
| color: #000; | |
| text-decoration: underline; | |
| text-decoration-color: #999; | |
| } | |
| a:hover { | |
| text-decoration-color: #000; | |
| } | |
| .badge { | |
| display: inline-block; | |
| background: #333; | |
| color: white; | |
| padding: 4px 12px; | |
| border-radius: 20px; | |
| font-size: 0.85em; | |
| font-weight: 500; | |
| margin-left: 10px; | |
| } | |
| .badge.mobile { background: #555; } | |
| .badge.desktop { background: #666; } | |
| ul { | |
| margin: 10px 0; | |
| padding-left: 20px; | |
| } | |
| li { | |
| margin: 8px 0; | |
| color: #333; | |
| } | |
| .highlight { | |
| background: #f0f0f0; | |
| padding: 15px 20px; | |
| border-radius: 8px; | |
| border: 2px solid #333; | |
| margin: 20px 0; | |
| } | |
| .grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 15px; | |
| margin: 20px 0; | |
| } | |
| .grid-item { | |
| background: #fafafa; | |
| padding: 15px; | |
| border-radius: 8px; | |
| border: 1px solid #ddd; | |
| } | |
| .grid-item strong { | |
| color: #000; | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 20px 0; | |
| } | |
| th { | |
| padding: 12px; | |
| text-align: left; | |
| border: 1px solid #ddd; | |
| background: #f5f5f5; | |
| font-weight: 600; | |
| color: #000; | |
| } | |
| td { | |
| padding: 10px; | |
| border: 1px solid #ddd; | |
| } | |
| tr:nth-child(even) { | |
| background: #fafafa; | |
| } | |
| small { | |
| color: #666; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1> | |
| 🗺️ Google Maps to Waze Converter | |
| <span class="badge">v2.0</span> | |
| </h1> | |
| <p style="font-size: 1.1em; color: #555;"> | |
| Instantly convert any Google Maps location to Waze navigation with automatic mobile redirection | |
| </p> | |
| <h2>🚀 Quick Start</h2> | |
| <div class="highlight"> | |
| <strong>📱 On Mobile:</strong> Automatic redirect to Waze app<br> | |
| <strong>💻 On Desktop:</strong> Get a Waze URL to open on your phone | |
| </div> | |
| <h2>📍 Supported Formats</h2> | |
| <div class="example"> | |
| <strong>🔗 Shortened Google Maps URLs</strong> | |
| Just paste the short code after your worker URL:<br><br> | |
| <code>${url.origin}/UwxS8G7nwq9Zk5mp9</code><br><br> | |
| <small>Works with maps.app.goo.gl links - just copy the code!</small> | |
| </div> | |
| <div class="example"> | |
| <strong>🌐 Full Google Maps URLs</strong> | |
| Copy any Google Maps URL and add it to the path:<br><br> | |
| <code>${url.origin}/https: | |
| <code>${url.origin}/https: | |
| </div> | |
| <div class="example"> | |
| <strong>📌 Direct Coordinates</strong> | |
| Quick format for known coordinates:<br><br> | |
| <code>${url.origin}/@35.7402314,51.3325282</code><br> | |
| <code>${url.origin}/35.7402314,51.3325282</code><br> | |
| <code>${url.origin}/q=35.7402314,51.3325282</code> | |
| </div> | |
| <h2>✨ Supported Google Maps Formats</h2> | |
| <div class="grid"> | |
| <div class="grid-item"> | |
| <strong>Short Codes</strong> | |
| <code>/abc123xyz</code> | |
| </div> | |
| <div class="grid-item"> | |
| <strong>@ Pattern</strong> | |
| <code>@lat,lng,zoom</code> | |
| </div> | |
| <div class="grid-item"> | |
| <strong>Place Format</strong> | |
| <code>/place/lat,lng/</code> | |
| </div> | |
| <div class="grid-item"> | |
| <strong>Data Parameters</strong> | |
| <code>3d...!4d...</code> | |
| </div> | |
| <div class="grid-item"> | |
| <strong>Query Params</strong> | |
| <code>?q=lat,lng</code> | |
| </div> | |
| <div class="grid-item"> | |
| <strong>Directions</strong> | |
| <code>/dir | |
| </div> | |
| </div> | |
| <h2>💡 How It Works</h2> | |
| <div class="feature"> | |
| <div class="feature-title">1. Automatic URL Expansion</div> | |
| Shortened Google Maps URLs (maps.app.goo.gl) are automatically expanded to extract coordinates | |
| </div> | |
| <div class="feature"> | |
| <div class="feature-title">2. Smart Coordinate Detection</div> | |
| Supports multiple Google Maps coordinate formats including @ patterns, place URLs, and data parameters | |
| </div> | |
| <div class="feature"> | |
| <div class="feature-title">3. Device-Aware Redirect</div> | |
| Mobile devices get instant Waze navigation, desktop users get a shareable Waze link | |
| </div> | |
| <h2>📝 Usage Examples</h2> | |
| <div class="example"> | |
| <strong>Example 1: Share Location from Google Maps</strong> | |
| <ul> | |
| <li>Open Google Maps on your phone</li> | |
| <li>Tap "Share" on any location</li> | |
| <li>Copy the link (looks like: <code>maps.app.goo.gl/xyz123</code>)</li> | |
| <li>Extract just the code: <code>xyz123</code></li> | |
| <li>Visit: <code>${url.origin}/xyz123</code></li> | |
| <li>✅ Opens in Waze automatically!</li> | |
| </ul> | |
| </div> | |
| <div class="example"> | |
| <strong>Example 2: From Browser</strong> | |
| <ul> | |
| <li>Find a location in Google Maps on desktop</li> | |
| <li>Copy the URL from address bar</li> | |
| <li>Add it after your worker URL</li> | |
| <li>Send to your phone</li> | |
| <li>✅ One-tap Waze navigation!</li> | |
| </ul> | |
| </div> | |
| <h2>🔧 Technical Details</h2> | |
| <p>This Cloudflare Worker:</p> | |
| <ul> | |
| <li>✅ Extracts coordinates from 10+ different Google Maps URL formats</li> | |
| <li>✅ Follows redirects for shortened URLs</li> | |
| <li>✅ Detects mobile devices (Android, iOS)</li> | |
| <li>✅ Generates proper Waze deep links (<code>waze.com/ul?ll=...</code>)</li> | |
| <li>✅ Works worldwide with any coordinates</li> | |
| <li>✅ Zero data storage - instant processing</li> | |
| </ul> | |
| <h2>🎯 URL Formats Reference</h2> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Google Maps Format</th> | |
| <th>Worker URL</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><code>maps.app.goo.gl/UwxS8G7nwq9Zk5mp9</code></td> | |
| <td><code>${url.origin}/UwxS8G7nwq9Zk5mp9</code></td> | |
| </tr> | |
| <tr> | |
| <td><code>@35.74,51.33,15z</code></td> | |
| <td><code>${url.origin}/@35.74,51.33</code></td> | |
| </tr> | |
| <tr> | |
| <td><code>/place/35.74,51.33/</code></td> | |
| <td><code>${url.origin}/place/Name/@35.74,51.33</code></td> | |
| </tr> | |
| <tr> | |
| <td><code>?q=35.74,51.33</code></td> | |
| <td><code>${url.origin}/q=35.74,51.33</code></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div style="margin-top: 40px; padding-top: 20px; border-top: 2px solid #e0e0e0; color: #666; font-size: 0.9em;"> | |
| <p><strong>Need help?</strong> If a URL format doesn't work, open the Google Maps link in your browser first, then copy the full expanded URL.</p> | |
| <p><strong>Privacy:</strong> No data is stored. All processing happens in real-time on Cloudflare's edge network.</p> | |
| </div> | |
| </div> | |
| </body> | |
| </html>`, | |
| { | |
| status: 200, | |
| headers: { 'Content-Type': 'text/html; charset=utf-8' } | |
| } | |
| ); | |
| } | |
| try { | |
| const coordinates = await extractCoordinatesFromPath(pathname, url); | |
| if (!coordinates) { | |
| const isShortUrl = pathname.includes('goo.gl') || pathname.includes('maps.app.goo.gl'); | |
| return new Response( | |
| JSON.stringify({ | |
| error: 'Could not extract coordinates from URL', | |
| providedPath: pathname, | |
| isShortUrl: isShortUrl, | |
| usage: 'Use formats like: /@37.7749,-122.4194 or /37.7749,-122.4194', | |
| note: isShortUrl ? 'Shortened URL detected. Please open the URL first in a browser to get the full Google Maps URL with coordinates, then use that URL.' : 'Could not find coordinates in the provided URL' | |
| }), | |
| { | |
| status: 400, | |
| headers: { 'Content-Type': 'application/json' } | |
| } | |
| ); | |
| } | |
| const wazeUrl = generateWazeUrl(coordinates); | |
| const userAgent = request.headers.get('User-Agent') || ''; | |
| const isMobile = /Android|iPhone|iPad|iPod/i.test(userAgent); | |
| if (isMobile) { | |
| return Response.redirect(wazeUrl, 302); | |
| } | |
| return new Response( | |
| `<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Open in Waze</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <style> | |
| body { font-family: system-ui, sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; text-align: center; } | |
| .coordinates { font-size: 24px; font-weight: bold; color: #333; margin: 20px 0; } | |
| .button { display: inline-block; background: #00d4ff; color: white; padding: 15px 30px; text-decoration: none; border-radius: 8px; font-size: 18px; margin: 20px 0; } | |
| .button:hover { background: #00b8e6; } | |
| .info { color: #666; margin-top: 30px; } | |
| code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🗺️ Navigate with Waze</h1> | |
| <div class="coordinates">${coordinates.latitude}, ${coordinates.longitude}</div> | |
| <a href="${wazeUrl}" class="button">Open in Waze</a> | |
| <div class="info"> | |
| <p>Click the button above to open this location in Waze</p> | |
| <p><small>Or copy this URL: <code>${wazeUrl}</code></small></p> | |
| </div> | |
| </body> | |
| </html>`, | |
| { | |
| status: 200, | |
| headers: { 'Content-Type': 'text/html; charset=utf-8' } | |
| } | |
| ); | |
| } catch (error) { | |
| return new Response( | |
| JSON.stringify({ | |
| error: 'Failed to process URL', | |
| details: error.message | |
| }), | |
| { | |
| status: 500, | |
| headers: { 'Content-Type': 'application/json' } | |
| } | |
| ); | |
| } | |
| } | |
| async function extractCoordinatesFromPath(pathname, url) { | |
| try { | |
| if (pathname.startsWith('/http: | |
| let embeddedUrl = pathname.substring(1); | |
| if (url.search) { | |
| if (embeddedUrl.includes('?')) { | |
| embeddedUrl += '&' + url.search.substring(1); | |
| } else { | |
| embeddedUrl += url.search; | |
| } | |
| } | |
| return await extractCoordinatesFromGoogleMapsUrl(embeddedUrl); | |
| } | |
| let match = pathname.match(/@(-?\d+\.?\d*),(-?\d+\.?\d*)(?:,\d+\.?\d*z?)?/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| match = pathname.match(/^\/(-?\d+\.?\d*),(-?\d+\.?\d*)$/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| match = pathname.match(/\/q=(-?\d+\.?\d*),(-?\d+\.?\d*)/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| match = pathname.match(/\/place\/[^/]+\/@(-?\d+\.?\d*),(-?\d+\.?\d*)/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| const shortCodeMatch = pathname.match(/^\/([a-zA-Z0-9]{10,20})$/); | |
| if (shortCodeMatch) { | |
| const shortCode = shortCodeMatch[1]; | |
| const fullShortUrl = `https: | |
| return await extractCoordinatesFromGoogleMapsUrl(fullShortUrl); | |
| } | |
| if (pathname.includes('google.com/maps') || pathname.includes('maps.app.goo.gl') || pathname.includes('goo.gl')) { | |
| let embeddedUrl = pathname.substring(1); | |
| if (!embeddedUrl.startsWith('http')) { | |
| embeddedUrl = 'https: | |
| } | |
| if (url.search) { | |
| if (embeddedUrl.includes('?')) { | |
| embeddedUrl += '&' + url.search.substring(1); | |
| } else { | |
| embeddedUrl += url.search; | |
| } | |
| } | |
| return await extractCoordinatesFromGoogleMapsUrl(embeddedUrl); | |
| } | |
| const qParam = url.searchParams.get('q'); | |
| if (qParam) { | |
| match = qParam.match(/^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| } | |
| return null; | |
| } catch (error) { | |
| return null; | |
| } | |
| } | |
| async function extractCoordinatesFromGoogleMapsUrl(urlString) { | |
| try { | |
| if (urlString.includes('/sorry/index')) { | |
| try { | |
| const url = new URL(urlString); | |
| const continueParam = url.searchParams.get('continue'); | |
| if (continueParam) { | |
| const decodedContinue = decodeURIComponent(continueParam); | |
| return await extractCoordinatesFromGoogleMapsUrl(decodedContinue); | |
| } | |
| } catch (e) { | |
| } | |
| } | |
| if (urlString.includes('maps.app.goo.gl') || urlString.includes('goo.gl')) { | |
| const expandedUrl = await expandShortUrl(urlString); | |
| if (expandedUrl && expandedUrl !== urlString) { | |
| urlString = expandedUrl; | |
| if (expandedUrl.includes('/sorry/index')) { | |
| return await extractCoordinatesFromGoogleMapsUrl(expandedUrl); | |
| } | |
| } else { | |
| } | |
| } | |
| let match = urlString.match(/@(-?\d+\.?\d*),(-?\d+\.?\d*)(?:,\d+\.?\d*z?)?/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| match = urlString.match(/\/place\/(-?\d+\.?\d*),(-?\d+\.?\d*)(?:\/|$)/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| match = urlString.match(/\/maps\/search\/(-?\d+\.?\d*),\s*\+?(-?\d+\.?\d*)/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| match = urlString.match(/[!&?]3d(-?\d+\.?\d*)[!&]4d(-?\d+\.?\d*)/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| match = urlString.match(/(?:\/dir\/\/|destination=)(-?\d+\.?\d*),(-?\d+\.?\d*)/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| try { | |
| const url = new URL(urlString); | |
| const qParam = url.searchParams.get('q'); | |
| if (qParam) { | |
| match = qParam.match(/^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| } | |
| const llParam = url.searchParams.get('ll'); | |
| if (llParam) { | |
| match = llParam.match(/^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| } | |
| const centerParam = url.searchParams.get('center'); | |
| if (centerParam) { | |
| match = centerParam.match(/^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/); | |
| if (match) { | |
| return { | |
| latitude: parseFloat(match[1]), | |
| longitude: parseFloat(match[2]) | |
| }; | |
| } | |
| } | |
| } catch (e) { | |
| } | |
| return null; | |
| } catch (error) { | |
| return null; | |
| } | |
| } | |
| async function expandShortUrl(shortUrl) { | |
| try { | |
| const response = await fetch(shortUrl, { | |
| method: 'GET', | |
| redirect: 'follow', | |
| headers: { | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' | |
| } | |
| }); | |
| const finalUrl = response.url; | |
| if (finalUrl === shortUrl) { | |
| return null; | |
| } | |
| return finalUrl; | |
| } catch (error) { | |
| return null; | |
| } | |
| } | |
| function generateWazeUrl(coordinates) { | |
| const { latitude, longitude } = coordinates; | |
| return `https: | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment