Greasy Fork is available in English.
The script adds in the WME links to third party mapping systems (Google/Open Street Maps/HERE etc.)
// ==UserScript== // @name WME JumpMaps Baltics // @description The script adds in the WME links to third party mapping systems (Google/Open Street Maps/HERE etc.) // @license MIT // @match https://*.waze.com/*editor* // @match https://www.kadastrs.lv/map/* // @match https://kartes.lgia.gov.lv/* // @match https://*.balticmaps.eu/* // @match https://maps.google.com // @match https://maps.apple.com // @match https://wikimapia.org/* // @match https://www.openstreetmap.org/* // @match https://mapcam.info/speedcam/* // @match https://www.mapillary.com/* // @require https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.11.0/proj4.js // @require https://unpkg.com/[email protected]/dist/leaflet.js // @icon data:image/png;base64,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 // @version 8.1.1 // @author N190392 // @namespace https://greasyfork.org/en/scripts/481079-wme-jumpmaps // ==/UserScript== /* global W, L, proj4 */ (function () { 'use strict'; // ============================================================================ // CONSTANTS & CONFIG // ============================================================================ const SCRIPT_NAME = GM_info.script.name; const SCRIPT_VERSION = GM_info.script.version; const ICON_WME = GM_info.script.icon; const PANEL_ID = `WME.JumpMaps_${SCRIPT_VERSION}`; const LS = { DEBUG: 'WMEJumpMapsDebug', RESTORE_SEL: 'WMEJumpMapsRestoreSelected', HIDE_WINDOW: 'WMEJumpMapsHideWindow', TOP_OFFSET: 'WMEJumpMapsTopOffset', LEFT_OFFSET: 'WMEJumpMapsLeftOffset', CHECKBOX_STATES: 'WMEJumpMapsCheckboxStates', SHOW_MINIMAP: 'WMEJumpMapsShowMinimap', MINIMAP_TOP: 'WMEJumpMapsMinimapTop', MINIMAP_LEFT: 'WMEJumpMapsMinimapLeft', FIXED_POS: 'WMEJumpMapsFixedPos', }; const DEFAULT_TOP = '30px'; const DEFAULT_MINIMAP_TOP = null; // null = use bottom:30px initial positioning const DEFAULT_MINIMAP_LEFT = '10px'; const DEFAULT_LEFT = '0px'; const LKS92_PROJ = '+proj=tmerc +lat_0=0 +lon_0=24 +k=0.9996 +x_0=500000 +y_0=-6000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'; const LKS94_PROJ = '+proj=tmerc +lat_0=0 +lon_0=24 +k=0.9996 +x_0=500000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'; // Zoom conversion table (WME zoom → KDL radius) const ARR_W2KDL = [ {w:0,r:75000},{w:1,r:50000},{w:2,r:15000},{w:3,r:10000},{w:4,r:5000}, {w:5,r:3000},{w:6,r:1000},{w:7,r:750},{w:8,r:500},{w:9,r:200}, ]; // ============================================================================ // STATE // ============================================================================ let debug = false; let restoreSelected = false; let hideWindow = false; let fixedPos = false; let topOffset = DEFAULT_TOP; let leftOffset = DEFAULT_LEFT; let showMinimap = getLS(LS.SHOW_MINIMAP, 'bool', true); let minimapInstance = null; let initProbeCount = 0; // Config is initialised in bootstrap from CONFIG_DEFAULTS let Config = {}; const CONFIG_DEFAULTS = { '_map_WME': { save:0, title:'Open in WME', name:'[WME]', template:'https://www.waze.com/editor/?env=row&zoomLevel={{zoom}}&lat={{lat}}&lon={{lon}}' }, '_map_LI': { save:0, title:'Open in LiveMap', name:'[Live]', template:'https://www.waze.com/livemap/?zoom={{zoom}}&lon={{lon}}&lat={{lat}}' }, '_map_OSM': { save:1, title:'OpenStreetMap', name:'OpenStreetMap', template:'http://www.openstreetmap.org/#map={{zoom}}/{{lat}}/{{lon}}' }, '_map_Google': { save:1, title:'Google Maps', name:'Google', template:'http://www.google.com/maps/?ll={{lat}}%2C{{lon}}&z={{zoom}}&t=m' }, '_map_BING': { save:1, title:'Bing Maps', name:'Bing', template:'http://www.bing.com/maps/?v=2&cp={{lat}}~{{lon}}&lvl={{zoom}}&dir=0&sty=h&form=LMLTEW' }, '_map_HERE': { save:1, title:'HERE WeGo', name:'HERE', template:'https://wego.here.com/?map={{lat}},{{lon}},{{zoom}},normal' }, '_map_APPLE': { save:1, title:'Apple Maps', name:'Apple', template:'https://maps.apple.com/?ll={{lat}},{{lon}}&spn=0.0038614715299516433%2C0.010368359444299813' }, '_map_MRY': { save:1, title:'Mapillary', name:'Mapillary', template:'https://www.mapillary.com/app/?lat={{lat}}&lng={{lon}}&z={{zoom}}' }, '_map_WM': { save:1, title:'Wikimapia', name:'Wikimapia', template:'https://wikimapia.org/#lang=ru&lat={{lat}}&lon={{lon}}&z={{zoom}}&m=b' }, '_map_SC': { save:1, title:'MapCam', name:'MapCam', template:'https://mapcam.info/speedcam/?lng={{lon}}&lat={{lat}}&z={{zoom}}&t=OSM' }, '_map_WMFLAB': { save:1, title:'GeoHack', name:'GeoHack', template:'https://geohack.toolforge.org/geohack.php?params={{lat}}_N_{{lon}}_E_scale:{{zoom}}' }, '_map_OSV': { save:1, title:'KartaView', name:'KartaView', template:'https://kartaview.org/map/@{{lat}},{{lon}},{{zoom}}z' }, '_map_RBASE': { save:1, title:'RadarBase.info', name:'RadarBase', template:'https://radarbase.info/map/actual/{{lat}}/{{lon}}/{{zoom}}' }, '_map_SPRO': { save:1, title:'satellites.pro', name:'SatPRO', template:'https://satellites.pro/#{{lat}},{{lon}},{{zoom}}' }, '_hdr_LV': { type: 'header', label: 'Latvia', flag: 'LV' }, '_map_BM': { save:1, title:'BalticMaps', name:'BalticMaps', template:'https://balticmaps.eu/lv/c___{{lon}}-{{lat}}-{{zoom}}/bl___cl' }, '_map_LGIA': { save:1, title:'LĢIA Kartes', name:'LĢIA', template:'https://kartes.lgia.gov.lv/?x={{lat}}&y={{lon}}&zoom={{zoom}}' }, '_map_KDL': { save:1, title:'Kadastrs', name:'Kadastrs', template:'https://www.kadastrs.lv/map/di?xy={{lat}},{{lon}}&z={{zoom}}' }, '_map_LVM': { save:1, title:'LVM GEO', name:'LVM GEO', template:'https://lvmgeo.lvm.lv/?loc={{lat}};{{lon}};{{zoom}}' }, '_map_CFY': { save:1, title:'Citify', name:'Citify', template:'https://citify.eu/lv/?lng={{lon}}&lat={{lat}}&z={{zoom}}' }, '_map_BIS': { save:1, title:'BIS Plānotie būvdarbi', name:'BIS', template:'https://bis.gov.lv/bisp/lv/planned_constructions/bismap#x={{lon}}&y={{lat}}&z={{zoom}}' }, '_map_LVC': { save:1, title:'LVC Būvdarbi', name:'LVC', template:'https://map.transportdata.gov.lv/public?mode=summer#{{zoom}}/{{lat}}/{{lon}}' }, '_map_DODLV': { save:1, title:'Dodies.lv', name:'Dodies', template:'https://vesture.dodies.lv/#m={{zoom}}/{{lat}}/{{lon}}&l=J' }, '_hdr_LT': { type: 'header', label:'Lithuania', flag: 'LT' }, '_map_MAPLT': { save:1, title:'Maps.lt', name:'Maps.lt', template:'https://maps.lt/map?c={{lon}}%2C{{lat}}&r=0&s={{zoom}}&b=topo&bl=false' }, '_map_REGIA': { save:1, title:'Regia.lt', name:'Regia', template:'https://www.regia.lt/map/?x={{lon}}&y={{lat}}'}, }; // Keys excluded from the visible link bar and settings checkboxes const INTERNAL_KEYS = new Set(['_map_WME', '_map_LI']); // ============================================================================ // LOCAL STORAGE HELPERS // ============================================================================ function getLS(key, type, defaultVal) { const raw = localStorage.getItem(key); if (raw === null) return defaultVal; switch (type) { case 'bool': return raw === 'true' || raw === '1'; case 'int': return !isNaN(parseInt(raw)) ? parseInt(raw) : defaultVal; default: return raw; } } function saveCheckboxStates() { const states = {}; for (const id in Config) { if (!INTERNAL_KEYS.has(id)) states[id] = Config[id].save; } localStorage.setItem(LS.CHECKBOX_STATES, JSON.stringify(states)); } function loadCheckboxStates() { const raw = localStorage.getItem(LS.CHECKBOX_STATES); if (!raw) return; const states = JSON.parse(raw); for (const id in states) { if (Config[id]) Config[id].save = states[id]; } } // ============================================================================ // SITE DETECTION // ============================================================================ const HOSTNAME_MAP = { 'www.waze.com': 'waze', 'editor-beta.waze.com': 'waze', 'beta.waze.com': 'waze', 'www.kadastrs.lv': 'kdlv', }; function getSiteType() { const h = location.hostname; if (HOSTNAME_MAP[h]) return HOSTNAME_MAP[h]; if (h.startsWith('www.google.')) return 'google'; return ''; } // ============================================================================ // URL / COORD HELPERS // ============================================================================ function getQueryParam(url, name) { const prefix = name + '='; const idx = url.indexOf(prefix); if (idx < 0) return -1; const start = idx + prefix.length; const end = url.indexOf('&', start); return end < 0 ? url.slice(start) : url.slice(start, end); } function getFaviconURL(id, template) { if (Config[id]?.icon) return Config[id].icon; const m = typeof template === 'string' && template.match(/https?:\/\/([^/]+)/); if (m) { const domain = m[1].replace(/^www\./, '').replace(/^map\./, ''); return `https://icons.duckduckgo.com/ip3/${encodeURIComponent(domain)}.ico`; } return ''; } // ============================================================================ // GET LAT/LON/ZOOM FROM CURRENT PAGE // Replaces OpenLayers transforms with W.map SDK equivalents // ============================================================================ function getWMECenter() { const center = W.map.getCenter(); let lat = center.lat; let lon = center.lon; // If values look like Mercator (EPSG:900913) rather than WGS-84, // transform them. WGS-84 lat is always -90..90, lon -180..180. // Mercator values for anywhere on earth are in the millions. if (Math.abs(lat) > 90 || Math.abs(lon) > 180) { const ll = new OpenLayers.LonLat(lon, lat) .transform( new OpenLayers.Projection('EPSG:900913'), new OpenLayers.Projection('EPSG:4326') ); lat = ll.lat; lon = ll.lon; } return { lat, lon, zoom: W.map.getZoom() }; } function getLLZ() { const href = location.href; const siteType = getSiteType(); const EXTRACTORS = { waze() { return getWMECenter(); }, google() { const at = href.indexOf('@'); if (at >= 0) { const parts = href.slice(at + 1).split(','); const zoomParts = parts[2].replace(/([0-9]+)([zm]+)(\/.*)?\.?/, '$1.$2').split('.'); if (zoomParts[1] === 'm') { const M2Z = [ {z:1,m:51510000},{z:2,m:25755000},{z:3,m:12877500},{z:4,m:6438750},{z:5,m:3219375}, {z:6,m:1609687},{z:7,m:804844},{z:8,m:402422},{z:9,m:201211},{z:10,m:100605}, {z:11,m:50303},{z:12,m:25151},{z:13,m:12576},{z:14,m:6288},{z:15,m:3144}, {z:16,m:1572},{z:17,m:786},{z:18,m:393},{z:19,m:196},{z:20,m:98}, {z:21,m:49},{z:22,m:25},{z:23,m:12}, ]; const z = parseInt(zoomParts[0]); let zoom = 1; for (let i = 0; i < M2Z.length - 1; i++) { if (z <= M2Z[i].m && z >= M2Z[i + 1].m) { zoom = M2Z[i].z; break; } } return { lat: parts[0], lon: parts[1], zoom }; } return { lat: parts[0], lon: parts[1], zoom: zoomParts[0] }; } return { lat: getQueryParam(href, 'y'), lon: getQueryParam(href, 'x'), zoom: parseInt(getQueryParam(href, 'z')), }; }, kdlv() { let frmap = null; for (let i = 0; i < frames.length; i++) { if (typeof frames[i].esri !== 'undefined') { frmap = frames[i]; break; } } if (!frmap) return { lat: 0, lon: 0, zoom: 0 }; frmap.document.getElementById('dijit_form_Button_15').click(); const urlKdl = frmap.document.getElementById('dijit_Dialog_0').getElementsByTagName('textarea')[0].value; frmap.document.getElementsByClassName('dijitDialogCloseIcon')[0].click(); const ll = getQueryParam(urlKdl, 'xy').split(','); return { lat: ll[1], lon: ll[0], zoom: parseInt(getQueryParam(urlKdl, 'z')) }; }, }; if (siteType === 'mry' || siteType === 'sc') { return { lat: getQueryParam(href, 'lat'), lon: getQueryParam(href, 'lng'), zoom: parseInt(getQueryParam(href, 'z')) }; } if (siteType === 'wm') { return { lat: getQueryParam(href, 'lat'), lon: getQueryParam(href, 'lon'), zoom: parseInt(getQueryParam(href, 'z')) }; } const result = EXTRACTORS[siteType]?.() ?? { lat: 0, lon: 0, zoom: 0 }; if (debug) console.log(`${SCRIPT_NAME}: getLLZ (${siteType}):`, result); return result; } // ============================================================================ // COORDINATE TRANSFORMS — other sites → WME // ============================================================================ function convertToWME(llz) { const siteType = getSiteType(); const TRANSFORMS = { lgia() { const t = proj4(proj4(LKS92_PROJ), proj4('EPSG:4326'), [parseFloat(llz.lon), parseFloat(llz.lat)]); llz.lon = t[0]; llz.lat = t[1]; llz.zoom = parseInt(llz.zoom, 10) + 7; }, kdlv() { const t = proj4(proj4(LKS92_PROJ), proj4('EPSG:4326'), [parseFloat(llz.lon), parseFloat(llz.lat)]); llz.lon = t[0]; llz.lat = t[1]; llz.zoom = 17; }, }; TRANSFORMS[siteType]?.(); if (debug) console.log(`${SCRIPT_NAME}: convertToWME →`, llz); return llz; } // ============================================================================ // COORDINATE TRANSFORMS — WME → external sites // ============================================================================ function convertFromWME(id, llz) { if (id === '_map_LI') llz.zoom -= 1; const TRANSFORMS = { _map_Google() { if (location.href.includes('mapmaker')) llz.zoom++; }, _map_BM() { // Project WGS-84 → EPSG:3857 for BalticMaps const rad = Math.PI / 180; llz.lon = parseFloat(llz.lon) * 20037508.34 / 180; llz.lat = Math.log(Math.tan((90 + parseFloat(llz.lat)) * rad / 2)) / rad * 20037508.34 / 180; }, _map_LGIA() { const t = proj4(proj4('EPSG:4326'), proj4(LKS92_PROJ), [parseFloat(llz.lon), parseFloat(llz.lat)]); llz.lon = t[0]; llz.lat = t[1]; llz.zoom = 9; }, _map_KDL() { const t = proj4(proj4('EPSG:4326'), proj4(LKS92_PROJ), [parseFloat(llz.lon), parseFloat(llz.lat)]); llz.lon = t[1]; llz.lat = t[0]; let z = Math.min(llz.zoom - 12, 7); for (const row of ARR_W2KDL) { if (z === row.w) { z = row.r; break; } } llz.zoom = z; }, _map_LVM() { const t = proj4(proj4('EPSG:4326'), proj4(LKS92_PROJ), [parseFloat(llz.lon), parseFloat(llz.lat)]); llz.lat = t[0]; llz.lon = t[1]; llz.zoom = 14; }, _map_MRY() { llz.zoom--; }, _map_WMFLAB() { const dms = (deg) => { const d = parseInt(deg); const rem = deg - d; const m = parseInt(rem * 60); const s = Math.round((rem * 60 - m) * 60 * 10) / 10; return `${d}_${m}_${s}`; }; llz.lat = dms(llz.lat); llz.lon = dms(llz.lon); llz.zoom = Math.pow(2, 12 - llz.zoom) * 100000; }, _map_OSV() { if (llz.zoom > 18) llz.zoom = 18; }, _map_MAPLT() { const rad = Math.PI / 180; llz.lon = parseFloat(llz.lon) * 20037508.34 / 180; llz.lat = Math.log(Math.tan((90 + parseFloat(llz.lat)) * rad / 2)) / rad * 20037508.34 / 180; llz.zoom = 591657527.61 / Math.pow(2, llz.zoom); }, _map_REGIA() { // Convert WGS-84 to LKS94 (EPSG:33406) for Regia.lt const t = proj4(proj4('EPSG:4326'), proj4(LKS94_PROJ), [parseFloat(llz.lon), parseFloat(llz.lat)]); llz.lon = Math.round(t[0]); // Easting → x parameter llz.lat = Math.round(t[1] + 1200); // Subtract 1200 meters to fix north offset // Convert WME zoom to Regia scale denominator const zoomMap = { 19: 750, 18: 1000, 17: 2000, 16: 3000, 15: 5000, 14: 10000, 13: 20000, 12: 50000 }; llz.zoom = zoomMap[llz.zoom] || 5000; }, }; TRANSFORMS[id]?.(); if (debug) console.log(`${SCRIPT_NAME}: convertFromWME(${id}) →`, llz); return llz; } // ============================================================================ // PERMALINK HELPER (for alpha/beta toggle) // ============================================================================ function generatePermalink() { for (const a of document.querySelectorAll('.WazeControlPermalink a')) { const href = a.href; if (href.includes('.waze.com/') && href.includes('/editor')) { return href.replace('/ru/', '/').replace(/layers=([0-9]+)&/, '') + '&marker=yes'; } } return ''; } // ============================================================================ // CLICK HANDLER — jump to map // ============================================================================ function onLinkClick() { const id = this.id; if (debug) console.log(`${SCRIPT_NAME}: click → ${id}`); let savedSelection = []; if (restoreSelected && id !== '_map_WME' && !id.startsWith('_map_WME_')) { // SDK: W.selectionManager.getSelectedFeatures() is unchanged savedSelection = W.selectionManager.getSelectedFeatures().map(f => f.model); } let llz = getLLZ(); if (id === '_map_WME' || id.startsWith('_map_WME_')) { llz = convertToWME(llz); } else { llz = convertFromWME(id, llz); } let template = Config[id]?.template ?? ''; if (id.startsWith('_map_WME_')) template = Config['_map_WME'].template; const isWME = id === '_map_WME' || id.startsWith('_map_WME_'); let url = template .replace('{{lon}}', llz.lon) .replace('{{lat}}', llz.lat) .replace('{{zoom}}', llz.zoom) + (isWME ? '&marker=yes' : ''); if (savedSelection.length > 0) { setTimeout(() => W.selectionManager.select(savedSelection), 50); } if (id.startsWith('_map_WME_')) { const jmlink = this.getAttribute('jmlink'); if (this.getAttribute('jmfrom') === 'mapbys') { window.open(`http://map.nca.by/map.html?xy=${jmlink}&z=16`, `_url_jm${id}`); } url = url.split('&')[0] + `&jmlink=${jmlink}`; } window.open(url, `_url${id}`); } // ============================================================================ // WME FLOATING LINK BAR // ============================================================================ function buildLinkBar() { let nod = document.getElementById(PANEL_ID); if (nod) { nod.innerHTML = ''; } else { nod = document.createElement('div'); nod.id = PANEL_ID; nod.setAttribute('unselectable', 'on'); nod.style.cssText = `font-size:12px; color:#fff; padding-left:20px; position:absolute; top:${topOffset}; left:${leftOffset}; display:block; background-color:rgba(0,0,0,.7); visibility:${hideWindow ? 'hidden' : 'visible'}; cursor:pointer;`; } loadCheckboxStates(); let html = ''; for (const id in Config) { if (INTERNAL_KEYS.has(id) || Config[id].save !== 1) continue; const { title, name, template } = Config[id]; const icon = getFaviconURL(id, template); const iconTag = icon ? `<img src="${icon}" style="width:12px;height:12px;vertical-align:middle;margin-right:4px;">` : ''; html += `<a id="${id}" style="color:#fff;font-size:11px" title="${title}">${iconTag}${name}</a> <span style="opacity:0.4;">|</span> `; } const isMain = location.hostname === 'www.waze.com'; html += `<a id="_map_LI" style="color:#fff;font-size:10px" title="Open in LiveMap">[Live]</a> ` + `<a id="_map_AB" tp="${isMain ? 'A' : 'B'}" href="" style="color:#fff;font-size:10px"` + ` title="Open in ${isMain ? 'Beta' : 'Main'} editor"` + ` target="WME-JumpMaps-${isMain ? 'b' : 'a'}">[${isMain ? 'β' : 'α'}]</a> ` + `<a href="https://greasyfork.org/en/scripts/481079-wme-jumpmaps"` + ` title="WME-JumpMaps_${SCRIPT_VERSION}" style="color:#fff;font-size:10px" target="_blank">[?]</a> `; nod.innerHTML = html; document.getElementById('waze-map-container').parentElement.appendChild(nod); for (const id in Config) { document.getElementById(id)?.addEventListener('click', onLinkClick); } document.getElementById('_map_LI')?.addEventListener('click', onLinkClick); document.getElementById('_map_AB')?.addEventListener('click', function () { const permalink = '?' + generatePermalink().split('?')[1]; const toMain = this.getAttribute('tp') === 'A'; this.href = (toMain ? 'https://beta.waze.com/editor' : 'https://www.waze.com/editor') + permalink; }); if (!fixedPos) makeDraggable(nod, nod, () => { topOffset = nod.style.top; leftOffset = nod.style.left; localStorage.setItem(LS.TOP_OFFSET, topOffset); localStorage.setItem(LS.LEFT_OFFSET, leftOffset); }); } // ============================================================================ // ICON INSERTION — external (non-WME) sites // ============================================================================ function insertExternalIcon() { const siteType = getSiteType(); if (siteType === 'waze') return true; const INSERTION = { kdlv: { anchor: () => document.getElementById('social_networks'), insert(anchor) { anchor.insertAdjacentHTML('afterbegin', '<a id="_map_WME" class="waze" target="_blank" title="Open in WME"></a>'); document.styleSheets[0].insertRule(`#social_networks a.waze { background:url(${ICON_WME});background-size:100% 100%;right:356px}`, 0); }, }, }; const cfg = INSERTION[siteType]; if (!cfg) return true; const anchor = cfg.anchor(); if (!anchor) { setTimeout(insertExternalIcon, 500); return false; } let nod = document.getElementById(PANEL_ID); if (!nod) { nod = document.createElement('span'); nod.id = PANEL_ID; } cfg.insert(anchor, nod); document.getElementById('_map_WME')?.addEventListener('click', onLinkClick); return true; } // ============================================================================ // MINIMAP // Uses W.map SDK events instead of OpenLayers event registration // ============================================================================ function createMinimap() { if (minimapInstance) return; if (!document.getElementById('leaflet-minimap-css')) { const link = document.createElement('link'); link.id = 'leaflet-minimap-css'; link.rel = 'stylesheet'; link.href = 'https://unpkg.com/[email protected]/dist/leaflet.css'; document.head.appendChild(link); } const mmTop = localStorage.getItem(LS.MINIMAP_TOP); const mmLeft = localStorage.getItem(LS.MINIMAP_LEFT) ?? DEFAULT_MINIMAP_LEFT; const container = document.createElement('div'); container.id = 'latvia-minimap'; // Restore saved position; fall back to bottom:30px if no saved top if (mmTop) { container.style.cssText = `position:absolute;top:${mmTop};left:${mmLeft};width:220px;height:145px;z-index:1;border:1px solid #333;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.3);background:white;overflow:hidden;`; } else { container.style.cssText = `position:absolute;bottom:30px;left:${mmLeft};width:220px;height:145px;z-index:1;border:1px solid #333;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.3);background:white;overflow:hidden;`; } const header = document.createElement('div'); header.style.cssText = 'background:#007acc;color:white;font-weight:400;height:20px;font-size:13px;text-align:center;cursor:move;user-select:none;font-family:Rubik;'; const updateHeader = () => { const el = document.querySelector('.location-info'); if (el?.textContent) header.textContent = el.textContent; }; setTimeout(updateHeader, 1000); setInterval(updateHeader, 2000); container.appendChild(header); const mapDiv = document.createElement('div'); mapDiv.id = 'minimap-leaflet'; mapDiv.style.cssText = 'width:100%;height:calc(100% - 20px);'; container.appendChild(mapDiv); document.querySelector('#map').appendChild(container); makeDraggable(container, header, () => { localStorage.setItem(LS.MINIMAP_TOP, container.style.top); localStorage.setItem(LS.MINIMAP_LEFT, container.style.left); }); minimapInstance = container; setTimeout(() => { const minimap = L.map('minimap-leaflet', { zoomControl: false, attributionControl: false, dragging: false, scrollWheelZoom: false, doubleClickZoom: false, boxZoom: false, keyboard: false, touchZoom: false, }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18 }).addTo(minimap); const COUNTRY_BOUNDS = { LV: [[55.67, 20.97], [58.09, 28.24]], LT: [[53.86, 21.05], [56.39, 25.97]], EE: [[57.51, 21.76], [59.68, 28.21]], }; function detectCountryFromUI() { const el = document.querySelector('.location-info'); if (!el || !el.textContent) return 'LV'; // fallback const txt = el.textContent.toLowerCase(); if (txt.includes('lithuania') || txt.includes('lietuva')) return 'LT'; if (txt.includes('estonia') || txt.includes('eesti')) return 'EE'; if (txt.includes('latvia') || txt.includes('latvija')) return 'LV'; return 'LV'; // safe fallback } function applyBounds() { const country = detectCountryFromUI(); const bounds = COUNTRY_BOUNDS[country] || COUNTRY_BOUNDS.LV; minimap.fitBounds(bounds); } // initial fit (delay ensures location-info is populated) setTimeout(applyBounds, 800); // optional: keep it updated if you move across borders setInterval(applyBounds, 3000); const marker = L.circleMarker([56.95, 24.1], { radius: 5, color: '#ffffff', fillColor: '#0099ff', fillOpacity: 0.8, weight: 1, }).addTo(minimap); function updateMarker() { try { const c = W.map?.getCenter(); if (!c) return; // SDK: use W.map.getCenter() — returns EPSG:900913, project to WGS-84 const ll = new OpenLayers.LonLat(c.lon, c.lat) .transform( new OpenLayers.Projection('EPSG:900913'), new OpenLayers.Projection('EPSG:4326') ); marker.setLatLng([ll.lat, ll.lon]); } catch (e) { console.error(`${SCRIPT_NAME}: minimap update error`, e); } } // SDK: subscribe to map move/zoom via W.map.events (OL2 still present in WME SDK) W.map.events.register('moveend', null, updateMarker); W.map.events.register('zoomend', null, updateMarker); updateMarker(); minimap.on('click', ({ latlng }) => { // SDK: W.map.setCenter accepts an OL LonLat in EPSG:900913 const pt = new OpenLayers.LonLat(latlng.lng, latlng.lat) .transform( new OpenLayers.Projection('EPSG:4326'), new OpenLayers.Projection('EPSG:900913') ); W.map.setCenter(pt); }); }, 500); } function destroyMinimap() { minimapInstance?.remove(); minimapInstance = null; } // ============================================================================ // DRAGGABLE HELPER // ============================================================================ function makeDraggable(element, handle, onDragEnd) { handle = handle ?? element; let x0 = 0, y0 = 0, x1 = 0, y1 = 0; function getMapRect() { return document.getElementById('waze-map-container')?.getBoundingClientRect() ?? document.body.getBoundingClientRect(); } handle.onmousedown = (e) => { e.preventDefault(); x1 = e.clientX; y1 = e.clientY; document.onmouseup = () => { document.onmouseup = document.onmousemove = null; if (onDragEnd) onDragEnd(); }; document.onmousemove = (e) => { e.preventDefault(); x0 = x1 - e.clientX; y0 = y1 - e.clientY; x1 = e.clientX; y1 = e.clientY; const mr = getMapRect(); const parent = element.offsetParent?.getBoundingClientRect() ?? mr; // New position in parent-relative coords let newTop = element.offsetTop - y0; let newLeft = element.offsetLeft - x0; // Clamp within map bounds (converted to parent-relative) const mapTop = mr.top - parent.top; const mapLeft = mr.left - parent.left; const mapRight = mapLeft + mr.width - element.offsetWidth; const mapBottom = mapTop + mr.height - element.offsetHeight; newTop = Math.max(mapTop, Math.min(newTop, mapBottom)); newLeft = Math.max(mapLeft, Math.min(newLeft, mapRight)); element.style.top = newTop + 'px'; element.style.left = newLeft + 'px'; element.style.bottom = element.style.right = 'auto'; }; }; // Also keep within bounds on window resize window.addEventListener('resize', () => { if (element.style.top === '' || element.style.left === '') return; const mr = getMapRect(); const parent = element.offsetParent?.getBoundingClientRect() ?? mr; const mapTop = mr.top - parent.top; const mapLeft = mr.left - parent.left; const mapRight = mapLeft + mr.width - element.offsetWidth; const mapBottom = mapTop + mr.height - element.offsetHeight; element.style.top = Math.max(mapTop, Math.min(parseFloat(element.style.top), mapBottom)) + 'px'; element.style.left = Math.max(mapLeft, Math.min(parseFloat(element.style.left), mapRight)) + 'px'; }); } // ============================================================================ // SETTINGS PANEL // Replaces WazeWrap.Interface.Tab() with the native WME SDK tab API // ============================================================================ /** * Registers a tab in the WME side panel. * * onReadyCallback receives the root container element as its first argument * so all DOM queries can be scoped to it — critical for the modern SDK where * the tab pane may live inside a shadow DOM or disconnected subtree. */ function registerSDKTab(tabLabel, contentHTML, onReadyCallback) { // ── Path 1: Modern SDK (W.userscripts, available ~2024+) ───────────── if (typeof W.userscripts?.registerSidebarTab === 'function') { const { tabLabel: label, tabPane } = W.userscripts.registerSidebarTab(tabLabel); label.innerText = tabLabel; tabPane.innerHTML = contentHTML; // Wire up handlers every time the tab becomes visible tabPane.addEventListener('wme-tab-pane-activate', () => onReadyCallback(tabPane)); // Also wire immediately — the pane is in the DOM from this point onReadyCallback(tabPane); return; } // ── Path 2: Older SDK — inject tab manually into the WME sidebar ───── const navTabs = document.querySelector('#sidebar .nav-tabs'); const tabPanes = document.querySelector('#sidebar .tab-content'); if (!navTabs || !tabPanes) { console.warn(`${SCRIPT_NAME}: sidebar not found, retrying tab registration…`); setTimeout(() => registerSDKTab(tabLabel, contentHTML, onReadyCallback), 1000); return; } const tabId = `wme-jm-tab-${tabLabel.toLowerCase().replace(/\s+/g, '-')}`; navTabs.insertAdjacentHTML('beforeend', `<li><a data-toggle="tab" href="#${tabId}">${tabLabel}</a></li>`); tabPanes.insertAdjacentHTML('beforeend', `<div class="tab-pane" id="${tabId}">${contentHTML}</div>`); const pane = document.getElementById(tabId); // Re-wire every time the tab is clicked (handles dynamic re-renders) navTabs.querySelector(`a[href="#${tabId}"]`) ?.addEventListener('click', () => onReadyCallback(pane)); // Wire once immediately onReadyCallback(pane); } /** * Wire up all interactive settings controls. * @param {Element} root — the tab pane container; ALL queries are scoped here. */ function onSettingsReady(root) { // Guard: if the expected content isn't present yet, bail silently if (!root || !root.querySelector('[data-id]')) return; const $ = (sel) => root.querySelector(sel); const $$ = (sel) => root.querySelectorAll(sel); // ── Map service checkboxes ──────────────────────────────────────────── for (const chk of $$('input[type="checkbox"][data-id]')) { const id = chk.dataset.id; if (!Config[id]) continue; chk.checked = !!Config[id].save; chk.onchange = function () { Config[this.dataset.id].save = this.checked ? 1 : 0; saveCheckboxStates(); buildLinkBar(); }; } // ── Debug ───────────────────────────────────────────────────────────── const dbgChk = $('#wmejm_cfg_debug'); if (dbgChk) { dbgChk.checked = debug; dbgChk.onchange = function () { debug = this.checked; localStorage.setItem(LS.DEBUG, debug ? '1' : '0'); }; } // ── Hide window ─────────────────────────────────────────────────────── const hideChk = $('#wmejm_cfg_window_hide'); if (hideChk) { hideChk.checked = hideWindow; hideChk.onchange = function () { hideWindow = this.checked; localStorage.setItem(LS.HIDE_WINDOW, hideWindow ? '1' : '0'); document.getElementById(PANEL_ID).style.visibility = hideWindow ? 'hidden' : 'visible'; }; } // ── Fixed position — locks bar in place (disables dragging only) ──────── const fixedChk = $('#wmejm_cfg_fixed_pos'); if (fixedChk) { fixedChk.checked = fixedPos; fixedChk.onchange = function () { fixedPos = this.checked; localStorage.setItem(LS.FIXED_POS, fixedPos ? '1' : '0'); const bar = document.getElementById(PANEL_ID); if (!bar) return; if (fixedPos) { bar.onmousedown = null; // disable drag } else { makeDraggable(bar, bar, () => { topOffset = bar.style.top; leftOffset = bar.style.left; localStorage.setItem(LS.TOP_OFFSET, topOffset); localStorage.setItem(LS.LEFT_OFFSET, leftOffset); }); // re-enable drag } }; } // ── Reset window position ───────────────────────────────────────────── $('#wmejm_cfg_resetWPos')?.addEventListener('click', () => { localStorage.removeItem(LS.MINIMAP_TOP); localStorage.removeItem(LS.MINIMAP_LEFT); const bar = document.getElementById(PANEL_ID); if (bar) { bar.style.top = topOffset = DEFAULT_TOP; bar.style.left = leftOffset = DEFAULT_LEFT; localStorage.setItem(LS.TOP_OFFSET, DEFAULT_TOP); localStorage.setItem(LS.LEFT_OFFSET, DEFAULT_LEFT); } // Also reset minimap to default position if (minimapInstance) { minimapInstance.style.top = ''; minimapInstance.style.bottom = '30px'; minimapInstance.style.left = DEFAULT_MINIMAP_LEFT; } }); // ── Reset config ────────────────────────────────────────────────────── $('#wmejm_cfg_resetConfig')?.addEventListener('click', () => { setTimeout(() => { if (!confirm('Reset config for WME-JumpMaps?')) return; localStorage.removeItem(LS.CHECKBOX_STATES); localStorage.removeItem(LS.DEBUG); localStorage.removeItem(LS.MINIMAP_TOP); localStorage.removeItem(LS.MINIMAP_LEFT); debug = false; Config = JSON.parse(JSON.stringify(CONFIG_DEFAULTS)); // Reposition minimap to default if it's visible if (minimapInstance) { minimapInstance.style.top = ''; minimapInstance.style.bottom = '30px'; minimapInstance.style.left = DEFAULT_MINIMAP_LEFT; } buildLinkBar(); buildSettingsPanel(); }, 100); }); // ── Minimap ─────────────────────────────────────────────────────────── const mapChk = $('#wmejm_cfg_minimap'); if (mapChk) { mapChk.checked = getLS(LS.SHOW_MINIMAP, 'bool', true); mapChk.onchange = function () { showMinimap = this.checked; localStorage.setItem(LS.SHOW_MINIMAP, showMinimap ? '1' : '0'); showMinimap ? createMinimap() : destroyMinimap(); }; if (mapChk.checked) setTimeout(createMinimap, 1000); } } let _settingsPanelRegistered = false; function buildSettingsPanel() { const tabVersionId = `WME-JumpMaps-${SCRIPT_VERSION.replace(/\./g, '-')}`; // Use a module-level flag — the tabVersionId div may live outside document scope // in the modern SDK, so getElementById would silently fail as a guard. if (_settingsPanelRegistered) return; _settingsPanelRegistered = true; loadCheckboxStates(); let rows = ''; for (const id in Config) { if (INTERNAL_KEYS.has(id)) continue; const item = Config[id]; // --- HEADER (no checkbox) --- if (item.type === 'header') { const flag = item.flag ? `<img src="https://flagcdn.com/w20/${item.flag.toLowerCase()}.png" style="width:16px;height:12px;">` : ''; rows += ` <div style="margin:10px 0 4px 0; padding-left:20px;"> <div style="display:flex;align-items:center;gap:6px;font-weight:600;font-size:13px;"> ${flag} <span>${item.label}</span> </div> </div>`; continue; } // --- NORMAL ITEM --- const { title, save, template } = item; const baseURL = template?.match(/https?:\/\/[^/]+/)?.[0] ?? ''; const fav = getFaviconURL(id, template); // prefer flag if present, fallback to favicon const iconTag = item.flag ? `<img src="https://flagcdn.com/w20/${item.flag.toLowerCase()}.png" style="width:16px;height:12px;">` : (fav ? `<img src="${fav}" style="width:16px;height:16px;">` : ''); rows += ` <div class="form-group" style="margin-bottom:4px;"> <div class="control-label" style="display:flex;align-items:center;gap:6px;font-weight:normal;font-size:14px;line-height:16px;"> <input data-id="${id}" name="wmejm_cfg_${id}" id="wmejm_cfg_${id}" type="checkbox" ${save ? 'checked' : ''}> ${iconTag} <label id="wmejm_cfg_${id}_chklab" for="wmejm_cfg_${id}" style="cursor:pointer;font-weight:normal;margin:0;" title="${baseURL}"> ${title} </label> </div> </div>`; } const html = ` <div id="${tabVersionId}" class="side-panel-section"> <h4>${SCRIPT_NAME}</h4> <div class="attributes-form side-panel-section"> <div class="form-group"> <label class="control-label">Map services:</label> <div class="controls">${rows}</div> </div> <div class="form-group"> <label class="control-label">Other settings:</label> <div class="controls" style="margin:0;line-height:16px;"> <input type="checkbox" id="wmejm_cfg_minimap"> <label for="wmejm_cfg_minimap" style="cursor:pointer;margin-left:6px;font-weight:normal;margin-bottom:0"> <i class="fa fa-map"></i> Show minimap </label><br> <input type="checkbox" id="wmejm_cfg_fixed_pos"> <label for="wmejm_cfg_fixed_pos" style="cursor:pointer;margin-left:6px;font-weight:normal;"> <i class="fa fa-lock"></i> Fixed link bar position </label><br> <input type="checkbox" id="wmejm_cfg_window_hide"> <label for="wmejm_cfg_window_hide" style="cursor:pointer;margin-left:6px;font-weight:normal;"> <i class="fa fa-eye-slash"></i> Hide link bar </label><br> <input type="checkbox" id="wmejm_cfg_debug"> <label for="wmejm_cfg_debug" style="cursor:pointer;margin-left:6px;font-weight:normal;margin-bottom:0"> <i class="fa fa-bug"></i> Debug script (console logging) </label><br> <button id="wmejm_cfg_resetConfig" class="btn btn-default" style="font-size:9px;padding:5px 9px;margin:4px 0;"> <i class="fa fa-recycle"></i> Reset config </button> <button id="wmejm_cfg_resetWPos" class="btn btn-default" style="font-size:9px;padding:5px 9px;"> <i class="fa fa-recycle"></i> Reset window position </button> </div> </div> </div> </div>`; registerSDKTab('JumpMaps', html, onSettingsReady); } // ============================================================================ // HANDLE jmlink PARAM ON WME LOAD // ============================================================================ function handleJMLink() { const jmlink = getQueryParam(location.href, 'jmlink'); if (jmlink === -1) return; const parts = jmlink.split(','); // Project the stored point (EPSG:4326) back to EPSG:900913 for W.map.setCenter const pt = new OpenLayers.LonLat(parts[1], parts[0]) .transform( new OpenLayers.Projection('EPSG:4326'), new OpenLayers.Projection('EPSG:900913') ); W.map.setCenter(pt); } // ============================================================================ // BOOTSTRAP // Replaces WazeWrap.Ready with the WME SDK event system // ============================================================================ /** * Waits for the WME SDK to be fully ready. * * The SDK dispatches a 'wme-ready' CustomEvent on document when the map * and all core models are loaded. We also poll as a safety net for * environments where the event may have already fired before our script ran. */ function waitForWMEReady(callback) { // Already ready? if (W?.map && W?.selectionManager && document.getElementById('waze-map-container')) { callback(); return; } // Listen for the SDK-dispatched ready event document.addEventListener('wme-ready', function onReady() { document.removeEventListener('wme-ready', onReady); callback(); }); // Polling fallback (covers cases where wme-ready fired before injection) let polls = 0; const poll = setInterval(() => { if (W?.map && W?.selectionManager && document.getElementById('waze-map-container')) { clearInterval(poll); callback(); } else if (++polls > 120) { clearInterval(poll); console.error(`${SCRIPT_NAME}: timed out waiting for WME to be ready`); } }, 500); } function init() { const siteType = getSiteType(); if (document.getElementById(PANEL_ID)) return; // already initialised if (siteType === 'waze') { handleJMLink(); setTimeout(() => { buildLinkBar(); buildSettingsPanel(); }, 1000); return; } // External sites if (!insertExternalIcon()) { if (++initProbeCount < 8) setTimeout(init, 5000); } } function bootstrap() { console.log(`${SCRIPT_NAME} (${SCRIPT_VERSION}): bootstrap`); Config = JSON.parse(JSON.stringify(CONFIG_DEFAULTS)); debug = getLS(LS.DEBUG, 'bool', false); restoreSelected = getLS(LS.RESTORE_SEL, 'bool', false); hideWindow = getLS(LS.HIDE_WINDOW, 'bool', false); fixedPos = getLS(LS.FIXED_POS, 'bool', true); topOffset = getLS(LS.TOP_OFFSET, 'string', DEFAULT_TOP); leftOffset = getLS(LS.LEFT_OFFSET, 'string', DEFAULT_LEFT); loadCheckboxStates(); const siteType = getSiteType(); if (siteType === 'waze') { // Use SDK readiness detection instead of WazeWrap.Ready waitForWMEReady(init); } else { // External sites have no WME SDK; init directly after a short delay setTimeout(init, 500); } } bootstrap(); })();