Skip to content

Instantly share code, notes, and snippets.

@dobrovolsky
Created August 24, 2025 10:36
Show Gist options
  • Save dobrovolsky/88f736c8200637c7f343c3691b73556e to your computer and use it in GitHub Desktop.
Save dobrovolsky/88f736c8200637c7f343c3691b73556e to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name GPX Studio Maps Opener
// @namespace http://tampermonkey.net/
// @version 1.4
// @description Adds a button to open the map in Google Maps or Strava
// @author You
// @match https://gpx.studio/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Function to get current coordinates and zoom from URL
function getCurrentMapData() {
const hash = window.location.hash;
// Format: #zoom/lat/lng
const match = hash.match(/#([\d.]+)\/([\d.-]+)\/([\d.-]+)/);
if (match) {
return {
zoom: parseFloat(match[1]),
lat: parseFloat(match[2]),
lng: parseFloat(match[3])
};
}
return { zoom: 15, lat: 49.21075, lng: 28.42181 }; // fallback values
}
// Function to convert zoom level for different services
function convertZoom(originalZoom, service) {
switch(service) {
case 'google':
// Google Maps usually uses similar zoom levels
return Math.round(originalZoom);
case 'strava':
// Strava may differ slightly, but usually similar
return Math.round(originalZoom);
default:
return Math.round(originalZoom);
}
}
// Function to close popup
function closePopup(popup, overlay) {
if (popup && popup.parentNode) {
document.body.removeChild(popup);
}
if (overlay && overlay.parentNode) {
document.body.removeChild(overlay);
}
}
// Function to create popup
function createPopup() {
const mapData = getCurrentMapData();
// Create overlay (darkened background)
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 9999;
`;
// Create popup
const popup = document.createElement('div');
popup.id = 'maps-popup';
popup.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border: 2px solid #333;
border-radius: 8px;
padding: 20px;
z-index: 10000;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
font-family: Arial, sans-serif;
min-width: 300px;
`;
popup.innerHTML = `
<h3 style="margin-top: 0; color: #333; text-align: center;">Open Map</h3>
<div style="text-align: center; color: #666; margin-bottom: 15px;">
<p style="margin: 5px 0;">Coordinates: ${mapData.lat.toFixed(5)}, ${mapData.lng.toFixed(5)}</p>
<p style="margin: 5px 0;">Zoom: ${mapData.zoom.toFixed(1)}</p>
</div>
<div style="margin: 20px 0; display: flex; flex-direction: column; gap: 10px;">
<button id="open-gmaps" style="
background: #4285f4;
color: white;
border: none;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background 0.2s;
width: 100%;
">📍 Google Maps (zoom: ${convertZoom(mapData.zoom, 'google')})</button>
<button id="open-strava" style="
background: #fc4c02;
color: white;
border: none;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background 0.2s;
width: 100%;
">🔥 Strava Heatmap (zoom: ${convertZoom(mapData.zoom, 'strava')})</button>
</div>
<button id="close-popup" style="
background: #666;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
display: block;
margin: 10px auto 0;
">Close</button>
`;
// Hover effects
const gmapsBtn = popup.querySelector('#open-gmaps');
const stravaBtn = popup.querySelector('#open-strava');
gmapsBtn.onmouseenter = () => gmapsBtn.style.background = '#3367d6';
gmapsBtn.onmouseleave = () => gmapsBtn.style.background = '#4285f4';
stravaBtn.onmouseenter = () => stravaBtn.style.background = '#e63900';
stravaBtn.onmouseleave = () => stravaBtn.style.background = '#fc4c02';
// Event handlers for buttons
gmapsBtn.onclick = function() {
const googleZoom = convertZoom(mapData.zoom, 'google');
const url = `https://www.google.com/maps/@${mapData.lat},${mapData.lng},${googleZoom}z`;
window.open(url, '_blank');
closePopup(popup, overlay);
};
stravaBtn.onclick = function() {
const stravaZoom = convertZoom(mapData.zoom, 'strava');
const url = `https://www.strava.com/maps/global-heatmap#${stravaZoom}/${mapData.lat}/${mapData.lng}`;
window.open(url, '_blank');
closePopup(popup, overlay);
};
popup.querySelector('#close-popup').onclick = function() {
closePopup(popup, overlay);
};
// Close on outside click
overlay.onclick = function() {
closePopup(popup, overlay);
};
// Close on Escape
const escapeHandler = function(e) {
if (e.key === 'Escape') {
closePopup(popup, overlay);
document.removeEventListener('keydown', escapeHandler);
}
};
document.addEventListener('keydown', escapeHandler);
// Add elements to DOM
document.body.appendChild(overlay);
document.body.appendChild(popup);
}
// Function to create button
function createButton() {
const button = document.createElement('button');
button.id = 'maps-opener-btn';
button.innerHTML = '🗺️';
button.title = 'Open in Google Maps or Strava';
button.style.cssText = `
background: white;
border: 1px solid #ccc;
border-radius: 4px;
padding: 0px;
margin: 0px;
cursor: pointer;
font-size: 16px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
width: 29px;
height: 29px;
line-height: 1;
`;
// Hover effect
button.onmouseenter = function() {
button.style.background = '#f0f0f0';
};
button.onmouseleave = function() {
button.style.background = 'white';
};
button.onclick = createPopup;
return button;
}
// Function to add button to map controls
function addButton() {
// Look for container for controls
const controlContainer = document.querySelector('.mapboxgl-ctrl-top-right');
if (controlContainer && !document.querySelector('#maps-opener-btn')) {
const button = createButton();
// Create wrapper in Mapbox GL style
const controlGroup = document.createElement('div');
controlGroup.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
controlGroup.appendChild(button);
controlContainer.appendChild(controlGroup);
console.log('Maps opener button added');
} else if (!document.querySelector('#maps-opener-btn')) {
// If container is not ready yet, try again later
setTimeout(addButton, 1000);
}
}
// Wait for page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(addButton, 2000);
});
} else {
setTimeout(addButton, 2000);
}
// Also observe DOM changes in case of dynamic loading
const observer = new MutationObserver(function(mutations) {
const controlContainer = document.querySelector('.mapboxgl-ctrl-top-right');
if (controlContainer && !document.querySelector('#maps-opener-btn')) {
addButton();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment