WME Coordinate Extractor

Extrae coordenadas con clic derecho en el editor de Waze.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         WME Coordinate Extractor
// @name:en      WME Coordinate Extractor
// @namespace    http://tampermonkey.net/
// @version      1.2.1
// @description  Extrae coordenadas con clic derecho en el editor de Waze.
// @description:en Extracts coordinates with a right-click in the Waze editor.
// @author       edarague
// @match        https://www.waze.com/*editor*
// @run-at       document-end
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ─── Estilos ──────────────────────────────────────────────────────────────
    function injectStyles() {
        const s = document.createElement('style');
        s.id = 'wce-styles';
        s.textContent = `
            .waze-coord-final {
                position: fixed !important;
                background: #0a0a0a !important;
                color: #00ff00 !important;
                padding: 14px 18px !important;
                border-radius: 6px !important;
                font-family: 'Courier New', monospace !important;
                font-size: 13px !important;
                font-weight: bold !important;
                z-index: 999999 !important;
                border: 2px solid #00ff00 !important;
                min-width: 250px !important;
                box-shadow: 0 4px 20px rgba(0,255,0,0.2) !important;
            }
            .waze-coord-coords {
                margin-bottom: 10px !important;
                font-size: 14px !important;
            }
            .waze-coord-actions {
                display: flex !important;
                gap: 8px !important;
            }
            .waze-coord-btn {
                flex: 1 !important;
                padding: 6px 8px !important;
                border-radius: 4px !important;
                border: 1px solid #00ff00 !important;
                background: transparent !important;
                color: #00ff00 !important;
                font-family: 'Courier New', monospace !important;
                font-size: 11px !important;
                font-weight: bold !important;
                cursor: pointer !important;
                text-align: center !important;
                transition: background 0.15s !important;
                text-decoration: none !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                gap: 4px !important;
            }
            .waze-coord-btn:hover {
                background: #00ff00 !important;
                color: #0a0a0a !important;
            }
        `;
        document.head.appendChild(s);
    }

    // ─── Obtención de coordenadas ─────────────────────────────────────────────
    // Método primario: API oficial de Waze (W.map / OpenLayers)
    function getCoordsFromAPI(e) {
        try {
            const map = W.map.getOLMap ? W.map.getOLMap() : W.map;

            // Convertir posición del evento a píxeles del viewport del mapa
            const mapDiv = map.div || map.getDiv();
            const rect = mapDiv.getBoundingClientRect();
            const px = new OpenLayers.Pixel(
                e.clientX - rect.left,
                e.clientY - rect.top
            );

            // getLonLatFromPixel devuelve coordenadas en la proyección nativa del mapa (Mercator)
            const mercator = map.getLonLatFromPixel(px);

            // Transformar explícitamente desde EPSG:900913 a EPSG:4326
            const wgs84 = mercator.clone().transform(
                new OpenLayers.Projection('EPSG:900913'),
                new OpenLayers.Projection('EPSG:4326')
            );

            return {
                lat: wgs84.lat.toFixed(6),
                lng: wgs84.lon.toFixed(6)
            };
        } catch (_) {
            return null;
        }
    }

    // Método de respaldo: regex sobre el texto visible del DOM
    function getCoordsFromDOM() {
        try {
            const m = document.body.innerText.match(/(\d+\.\d{4,})\s+(-?\d+\.\d{4,})/);
            return m ? { lat: parseFloat(m[1]).toFixed(6), lng: parseFloat(m[2]).toFixed(6) } : null;
        } catch (_) {
            return null;
        }
    }

    function getCoords(e) {
        return getCoordsFromAPI(e) || getCoordsFromDOM();
    }

    // ─── Popup ────────────────────────────────────────────────────────────────
    function showPopup(c, x, y) {
        const old = document.querySelector('.waze-coord-final');
        if (old) old.remove();

        const mapsUrl = `https://maps.google.com/?q=${c.lat},${c.lng}`;
        const p = document.createElement('div');
        p.className = 'waze-coord-final';
        p.innerHTML = `
            <div class="waze-coord-coords">📍 ${c.lat} ${c.lng}</div>
            <div class="waze-coord-actions">
                <button class="waze-coord-btn" id="wce-copy">📋 Copiar</button>
                <a class="waze-coord-btn" href="${mapsUrl}" target="_blank" id="wce-maps">🗺️ Maps</a>
            </div>
        `;
        p.style.left = Math.min(x + 15, window.innerWidth - 240) + 'px';
        p.style.top  = Math.min(y + 15, window.innerHeight - 120) + 'px';
        document.body.appendChild(p);

        // Botón Copiar
        p.querySelector('#wce-copy').addEventListener('click', (e) => {
            e.stopPropagation();
            navigator.clipboard.writeText(`${c.lat}, ${c.lng}`);
            const btn = p.querySelector('#wce-copy');
            if (btn) btn.textContent = '✅ Copiado';
            removePopup(p);
        });

        // Botón Maps
        p.querySelector('#wce-maps').addEventListener('click', (e) => {
            e.stopPropagation();
            removePopup(p);
        });

        // Cierre al hacer clic fuera del popup (sustituye los setTimeout de limpieza)
        const outsideClickHandler = (ev) => {
            if (!p.contains(ev.target)) {
                removePopup(p, outsideClickHandler);
            }
        };
        // Delay mínimo para no capturar el mismo clic derecho que abrió el popup
        setTimeout(() => {
            document.addEventListener('click', outsideClickHandler, true);
        }, 100);
    }

    function removePopup(p, handler) {
        if (p && p.parentNode) p.remove();
        if (handler) document.removeEventListener('click', handler, true);
    }

    // ─── Inicialización ───────────────────────────────────────────────────────
    function init() {
        injectStyles();

        document.addEventListener('contextmenu', (e) => {
            const c = getCoords(e);
            if (c) {
                e.preventDefault();
                showPopup(c, e.clientX, e.clientY);
            }
        }, true);
    }

    // Esperar al evento wme-ready (estándar SDK moderno).
    // Si el evento nunca llega (editor muy viejo), se usa DOMContentLoaded como fallback.
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            document.addEventListener('wme-ready', init, { once: true });
            // Fallback: si wme-ready no dispara en 15 s, inicializar de todos modos
            setTimeout(() => {
                if (!document.getElementById('wce-styles')) init();
            }, 15000);
        });
    } else {
        document.addEventListener('wme-ready', init, { once: true });
        setTimeout(() => {
            if (!document.getElementById('wce-styles')) init();
        }, 15000);
    }

})();