WME Open GMaps Editor

Opens the Google Maps editor based on WME's map center.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         WME Open GMaps Editor
// @namespace    https://github.com/WazeDev/wme-open-gmaps-editor
// @version      0.0.6
// @description  Opens the Google Maps editor based on WME's map center.
// @author       Gavin Canon-Phratsachack (https://github.com/gncnpk)
// @match        https://beta.waze.com/*editor*
// @match        https://www.waze.com/*editor*
// @exclude      https://www.waze.com/*user/*editor/*
// @exclude      https://www.waze.com/discuss/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=waze.com
// @license      MIT
// @contributionURL https://github.com/WazeDev/Thank-The-Authors
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    let wmeSdk;
    const zoomLevels = {
        1: 52428800,
        2: 26214400,
        3: 13107200,
        4: 6553600,
        5: 3276800,
        6: 1638400,
        7: 819200,
        8: 409600,
        9: 204800,
        10: 102400,
        11: 51200,
        12: 25600,
        13: 12800,
        14: 6400,
        15: 3200,
        16: 1600,
        17: 800,
        18: 400,
        19: 200,
        20: 100,
        21: 50,
        22: 25
    }

    function encodeProtobufToBase64({
        latitude,
        longitude,
        zoomLevel,
        sequenceNumber,
        url
    } = {}) {

        // Helper function to encode varint - EXACT ORIGINAL
        function encodeVarint(value) {
            const bytes = [];
            while (value >= 0x80) {
                bytes.push((value & 0xFF) | 0x80);
                value >>>= 7;
            }
            bytes.push(value & 0xFF);
            return new Uint8Array(bytes);
        }

        // Helper function to encode 64-bit fixed (double)
        function encodeFixed64(value) {
            const buffer = new ArrayBuffer(8);
            const view = new DataView(buffer);
            view.setFloat64(0, value, true); // little endian
            return new Uint8Array(buffer);
        }

        // Helper function to encode 32-bit fixed (uint32)
        function encodeFixed32(value) {
            const buffer = new ArrayBuffer(4);
            const view = new DataView(buffer);
            view.setUint32(0, value, true); // little endian
            return new Uint8Array(buffer);
        }

        // Helper function to encode length-delimited data
        function encodeLengthDelimited(data) {
            const length = encodeVarint(data.length);
            const result = new Uint8Array(length.length + data.length);
            result.set(length, 0);
            result.set(data, length.length);
            return result;
        }

        // Helper function to encode string
        function encodeString(str) {
            const encoder = new TextEncoder();
            const data = encoder.encode(str);
            return encodeLengthDelimited(data);
        }

        // Helper function to encode field header (field number + wire type)
        function encodeFieldHeader(fieldNumber, wireType) {
            return encodeVarint((fieldNumber << 3) | wireType);
        }

        // Helper function to concatenate Uint8Arrays
        function concatUint8Arrays(...arrays) {
            const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
            const result = new Uint8Array(totalLength);
            let offset = 0;
            for (const arr of arrays) {
                result.set(arr, offset);
                offset += arr.length;
            }
            return result;
        }

        // Wire types
        const VARINT = 0;
        const FIXED64 = 1;
        const LENGTH_DELIMITED = 2;
        const FIXED32 = 5;

        try {
            // Encode the inner coordinate message (field 1.1)
            const coordMessage = concatUint8Arrays(
                encodeFieldHeader(2, FIXED64), encodeFixed64(longitude),
                encodeFieldHeader(3, FIXED64), encodeFixed64(latitude)
            );

            // Encode the dimensions message (field 1.3)
            const dimensionsMessage = concatUint8Arrays(
                encodeFieldHeader(1, VARINT), encodeVarint(1920),
                encodeFieldHeader(2, VARINT), encodeVarint(957)
            );

            // Encode the main nested message (field 1)
            const mainMessage = concatUint8Arrays(
                encodeFieldHeader(1, LENGTH_DELIMITED), encodeLengthDelimited(coordMessage),
                encodeFieldHeader(3, LENGTH_DELIMITED), encodeLengthDelimited(dimensionsMessage),
                encodeFieldHeader(4, FIXED32), encodeFixed32(zoomLevels[zoomLevel])
            );

            // Encode the complete protobuf message
            const completeMessage = concatUint8Arrays(
                encodeFieldHeader(1, LENGTH_DELIMITED), encodeLengthDelimited(mainMessage),
                encodeFieldHeader(2, VARINT), encodeVarint(sequenceNumber),
                encodeFieldHeader(4, LENGTH_DELIMITED), encodeString(url)
            );

            // Convert to URL-safe Base64
            const base64String = btoa(String.fromCharCode(...completeMessage))
                .replace(/\+/g, '-')
                .replace(/\//g, '_')
                .replace(/=/g, '');

            return base64String;
        } catch (error) {
            console.error('Error encoding protobuf:', error);
            return null;
        }
    }

    function waitForElm(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                return resolve(document.querySelector(selector));
            }

            const observer = new MutationObserver(mutations => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });
    }

    window.SDK_INITIALIZED.then(initialize)

    async function initialize() {
        wmeSdk = await getWmeSdk({
            scriptId: 'wme-open-gmaps-editor',
            scriptName: 'WME Open GMaps Editor'
        })

        wmeSdk.Events.on({
            eventHandler: replaceGoogleLink,
            eventName: "wme-map-move-end"
        })

        wmeSdk.Events.on({
            eventHandler: replaceGoogleLink,
            eventName: "wme-map-zoom-changed"
        })

        let GMELink = document.createElement("a");
        GMELink.id = "wme-open-gmaps-editor-url";
        GMELink.target = "_blank";
        let img = document.createElement("img");
        img.src = "https://upload.wikimedia.org/wikipedia/commons/a/aa/Google_Maps_icon_%282020%29.svg";
        img.style = "margin-right: 12px;margin-left: 12px;width: 16px;";
        img.title = "Open in Google Maps Editor (GME)"
        GMELink.appendChild(img);

        waitForElm('.secondary-toolbar-actions').then((elm) => {
            let toolbar = document.getElementsByClassName("secondary-toolbar-actions")[0];
            toolbar.insertBefore(GMELink, toolbar.children[0]);
            replaceGoogleLink();
        });
    }

    function replaceGoogleLink() {
        const coords = wmeSdk.Map.getMapCenter();
        const mapZoom = wmeSdk.Map.getZoomLevel();

        const trafficMapLayer_DO = "!5m1!1e1"
        const mapFixDialog_DO = "!10m1!1e3"
        let mapUrl = `https://www.google.com/maps/place/@${coords.lat},${coords.lon},${zoomLevels[mapZoom]}m/data=${trafficMapLayer_DO}${mapFixDialog_DO}`

        const base64Data = encodeProtobufToBase64({
            latitude: coords.lat,
            longitude: coords.lon,
            zoomLevel: mapZoom,
            sequenceNumber: 10,
            url: mapUrl
        });

        if (base64Data) {
            let url = `https://maps.google.com/roadeditor/iframe?bpb=${base64Data}`
            document.getElementById("wme-open-gmaps-editor-url").href = url;
        } else {
            console.error('Failed to encode protobuf data');
        }
    }
})();