WME Coordinate Extractor

Extrae coordenadas con clic derecho en el editor de Waze.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل 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);
    }

})();