WME GPX/KML Overlay

Overlay StravaGPX or KML files onto Waze Map Editor

当前为 2024-12-13 提交的版本,查看 最新版本

// ==UserScript==
// @name        WME GPX/KML Overlay
// @namespace   https://www.waze.com/
// @version     1.0
// @description Overlay StravaGPX or KML files onto Waze Map Editor
// @author      Dosojintaizo
// @license     MIT/BSD/X11
// @include     https://www.waze.com/editor*
// @include     https://www.waze.com/*/editor*
// @include     https://beta.waze.com/editor*
// @include     https://beta.waze.com/*/editor*
// @require     https://update.gf.qytechs.cn/scripts/520574/1502033/togeojson.js
// @grant       none
// ==/UserScript==

(function() {
    'use strict';

    if (W?.userscripts?.state.isReady) {
        initializeScript();
    } else {
        document.addEventListener("wme-ready", initializeScript, { once: true });
    }

    const overlays = [];

    async function initializeScript() {
        console.log("WME GPX/KML Overlay script initialized.");

        const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("wme-strava-kml-overlay");
        tabLabel.innerText = "Overlay";
        tabLabel.title = "Import and manage GPX/KML overlays on the map";

        tabPane.innerHTML = `
            <div style="padding: 10px;">
                <h3>Strava/KML Overlay</h3>
                <p>Import GPX or KML files to overlay them on the map.</p>
                <input type="file" id="fileInput" accept=".gpx,.kml" />
                <div id="overlayList" style="margin-top: 20px;"></div>
                <div id="status" style="margin-top: 10px; color: green;"></div>
            </div>
        `;

        await W.userscripts.waitForElementConnected(tabPane);

        const fileInput = tabPane.querySelector("#fileInput");
        const overlayList = tabPane.querySelector("#overlayList");
        const status = tabPane.querySelector("#status");

        fileInput.addEventListener("change", async (event) => {
            const file = event.target.files[0];
            if (!file) {
                status.textContent = "No file selected.";
                return;
            }

            try {
                const text = await file.text();
                let geoJSON;

                if (file.name.endsWith(".gpx")) {
                    geoJSON = parseGPXToGeoJSON(text);
                } else if (file.name.endsWith(".kml")) {
                    geoJSON = parseKMLToGeoJSON(text);
                } else {
                    throw new Error("Unsupported file format. Please upload a GPX or KML file.");
                }

                addOverlay(file.name, geoJSON);
                status.textContent = "Overlay added successfully.";
            } catch (error) {
                console.error("Error processing file:", error);
                status.textContent = `Error: ${error.message}`;
            }
        });
    }

    function parseGPXToGeoJSON(gpxText) {
        const parser = new DOMParser();
        const gpxDoc = parser.parseFromString(gpxText, "application/xml");
        return toGeoJSON.gpx(gpxDoc);
    }

    function parseKMLToGeoJSON(kmlText) {
        const parser = new DOMParser();
        const kmlDoc = parser.parseFromString(kmlText, "application/xml");
        return toGeoJSON.kml(kmlDoc);
    }

    function addOverlay(fileName, geoJSON) {
        const layerName = fileName;
        const vectorLayer = new OpenLayers.Layer.Vector(layerName, {
            styleMap: new OpenLayers.StyleMap({
                "default": new OpenLayers.Style({
                    strokeColor: "#FFFF00",
                    strokeWidth: 3,
                    fillOpacity: 0.4,
                }),
            }),
        });

        geoJSON.features.forEach((feature) => {
            if (feature.geometry && feature.geometry.coordinates) {
                feature.geometry.coordinates = removeZCoordinates(feature.geometry.coordinates);
            }

            const olGeometry = W.userscripts.toOLGeometry(feature.geometry);
            const vectorFeature = new OpenLayers.Feature.Vector(olGeometry);
            vectorLayer.addFeatures([vectorFeature]);
        });

        W.map.addLayer(vectorLayer);

        const overlay = {
            name: fileName,
            layer: vectorLayer,
            color: "#FFFF00",
            width: 3,
        };

        overlays.push(overlay);
        renderOverlayList();
    }

    function removeZCoordinates(coords) {
        if (Array.isArray(coords[0])) {
            return coords.map(removeZCoordinates);
        } else if (coords.length >= 2) {
            return coords.slice(0, 2);
        }
        return coords;
    }

    function renderOverlayList() {
        const overlayList = document.getElementById("overlayList");
        overlayList.innerHTML = "";

        overlays.forEach((overlay, index) => {
            const item = document.createElement("div");
            item.style.marginBottom = "10px";

            const title = document.createElement("div");
            title.textContent = overlay.name;
            title.style.fontWeight = "bold";
            title.style.display = "inline-block";
            title.style.marginRight = "10px";

            const toggle = document.createElement("input");
            toggle.type = "checkbox";
            toggle.checked = true;
            toggle.addEventListener("change", () => {
                overlay.layer.setVisibility(toggle.checked);
            });

            const gearIcon = document.createElement("span");
            gearIcon.textContent = "⚙️";
            gearIcon.style.cursor = "pointer";
            gearIcon.style.marginLeft = "10px";

            const trashIcon = document.createElement("span");
            trashIcon.textContent = "🗑️";
            trashIcon.style.cursor = "pointer";
            trashIcon.style.marginLeft = "10px";
            trashIcon.addEventListener("click", () => {
                W.map.removeLayer(overlay.layer);
                overlays.splice(index, 1);
                renderOverlayList();
            });

            const settings = document.createElement("div");
            settings.style.display = "none";
            settings.style.marginTop = "5px";
            settings.style.border = "1px solid #ccc";
            settings.style.padding = "5px";

            const colorInput = document.createElement("input");
            colorInput.type = "color";
            colorInput.value = overlay.color;
            colorInput.addEventListener("input", (event) => {
                overlay.color = event.target.value;
                overlay.layer.styleMap.styles.default.defaultStyle.strokeColor = overlay.color;
                overlay.layer.redraw();
            });

            const widthInput = document.createElement("input");
            widthInput.type = "number";
            widthInput.value = overlay.width;
            widthInput.min = 1;
            widthInput.max = 10;
            widthInput.addEventListener("input", (event) => {
                overlay.width = parseInt(event.target.value, 10) || 1;
                overlay.layer.styleMap.styles.default.defaultStyle.strokeWidth = overlay.width;
                overlay.layer.redraw();
            });

            settings.appendChild(document.createTextNode("Line Color: "));
            settings.appendChild(colorInput);
            settings.appendChild(document.createElement("br"));
            settings.appendChild(document.createTextNode("Line Width: "));
            settings.appendChild(widthInput);

            gearIcon.addEventListener("click", () => {
                settings.style.display = settings.style.display === "none" ? "block" : "none";
            });
            
            item.appendChild(trashIcon);
            item.appendChild(toggle);
            item.appendChild(gearIcon);
            item.appendChild(title);
            item.appendChild(settings);
            overlayList.appendChild(item);
        });
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址