Segment Smoothing Tool

Smooths the geometry of the selected segment in Waze Map Editor

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Segment Smoothing Tool
// @namespace    https://github.com/georgenqueiroz
// @version      0.1
// @description  Smooths the geometry of the selected segment in Waze Map Editor
// @author       George Queiroz (georgenqueiroz)
// @match        https://www.waze.com/editor*
// @match        https://*.waze.com/*/editor*
// @match        https://*.waze.com/editor*
// @license      MIT
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    
    let wmeSDK = null;

    const TRANSLATIONS = {
        en: {
            lbl_this_script_is_currently_in_beta: "This script is currently in beta.",
            lbl_select_a_segment_to_smooth: "Select a segment to smooth.",
            lbl_to_smooth: "Smooth",

            alert_no_segment_selected: "No segment selected.",
            alert_the_tool_only_works_with_one_segment_at_a_time: "The tool only works with one segment at a time.",
            alert_the_selected_item_is_not_a_segment: "The selected item is not a segment.",
            alert_this_segment_has_too_many_geometry_nodes: "This segment has too many geometry nodes.",
            alert_this_segment_is_a_straight_line: "This segment is a straight line. Please add a geometry node manually before smoothing."
        },
        pt: {
            lbl_this_script_is_currently_in_beta: "Este script está atualmente em beta.",
            lbl_select_a_segment_to_smooth: "Selecione um segmento para suavizar.",
            lbl_to_smooth: "Suavizar",
            alert_no_segment_selected: "Nenhum segmento selecionado.",
            alert_the_tool_only_works_with_one_segment_at_a_time: "A ferramenta só funciona com um segmento por vez.",
            alert_the_selected_item_is_not_a_segment: "O item selecionado não é um segmento.",
            alert_this_segment_has_too_many_geometry_nodes: "Este segmento tem muitos nós de geometria.",
            alert_this_segment_is_a_straight_line: "Este segmento é uma linha reta. Por favor, adicione um nó de geometria manualmente antes de suavizar."
        }
    };

    function _t(key) {
        let language = I18n.locale && I18n.locale.toLowerCase();
        const isPt = language && language.startsWith('pt');
        const lang = isPt ? 'pt' : 'en';

        let text = TRANSLATIONS[lang][key] || key;
        return text;
    }

    //Smoothing - Chaikin's Algorithm
    function chaikinSmooth(coords, iterations = 1) {
        if (iterations <= 0) return coords;
        if (coords.length < 3) return coords;

        let smoothed = [];

        const Ax = Number(coords[0][0]);
        const Ay = Number(coords[0][1]);
        smoothed.push([Ax, Ay]);

        for (let i = 0; i < coords.length - 1; i++) {            
            const p0x = Number(coords[i][0]);
            const p0y = Number(coords[i][1]);
            const p1x = Number(coords[i + 1][0]);
            const p1y = Number(coords[i + 1][1]);

            // Chaikin: 25% e 75%
            const Qx = 0.75 * p0x + 0.25 * p1x;
            const Qy = 0.75 * p0y + 0.25 * p1y;

            const Rx = 0.25 * p0x + 0.75 * p1x;
            const Ry = 0.25 * p0y + 0.75 * p1y;

            smoothed.push([Qx, Qy]);
            smoothed.push([Rx, Ry]);
        }

        const Bx = Number(coords[coords.length - 1][0]);
        const By = Number(coords[coords.length - 1][1]);
        smoothed.push([Bx, By]);

        return chaikinSmooth(smoothed, iterations - 1);
    }

    function getSegmentNodeLimit(segment) {
        const lengthInMeters = segment.attributes ? segment.attributes.length : (segment.length || 0);
        const limit = Math.floor(lengthInMeters);
        const hardLimit = 60;
        return Math.min(limit, hardLimit);
    }
    
    async function smoothSegmentButtonClick()
    {        
        if (!wmeSDK) {
            try {
                wmeSDK = getWmeSdk({scriptId: "smooth-segment-tool", scriptName: "Segment Smoothing Tool"});
            } catch (e) {
                alert("Error initializing WME SDK. Try reloading the page.");
                return;
            }
        }
        
        const selection = W.selectionManager.getSelectedWMEFeatures();
        
        if (selection.length === 0) {
            alert(_t("alert_no_segment_selected"));
            return;
        }

        if (selection.length > 1) {
            alert(_t("alert_the_tool_only_works_with_one_segment_at_a_time"));
            return;
        }

        const selectedItem = selection[0];

        if (selectedItem.featureType !== "segment") {
             alert(_t("alert_the_selected_item_is_not_a_segment"));
             return;
        }
        
        const segId = selectedItem.attributes ? selectedItem.attributes.id : selectedItem.id;
        const realSegment = W.model.segments.getObjectById(segId);

        if (!realSegment) {
            alert("Error retrieving segment.");
            return;
        }

        const nodeLimit = getSegmentNodeLimit(realSegment);
        if (realSegment.getGeometry().coordinates.length * 2 > nodeLimit) {
            alert(_t("alert_this_segment_has_too_many_geometry_nodes"));
            return;
        }
        
        const geometry = realSegment.getGeometry();
        const oldCoordinates = geometry.coordinates;

        if (!oldCoordinates || oldCoordinates.length < 3) {
            alert(_t("alert_this_segment_is_a_straight_line"));
            return;
        }
        
        const newCoordinates = chaikinSmooth(oldCoordinates, 1);

        console.log(`Smoothing: from ${oldCoordinates.length} points to ${newCoordinates.length} points.`);

        const cleanCoordinates = newCoordinates.map(pt => [Number(pt[0]), Number(pt[1])]);
        
        const newGeometryGeoJSON = {
            type: "LineString",
            coordinates: cleanCoordinates
        };
        
        try {
            await wmeSDK.DataModel.Segments.updateSegment({
                segmentId: segId,
                geometry: newGeometryGeoJSON
            });

            console.log("Segment smoothed successfully!");
        } catch (e) {
            console.error("Error updating segment:", e);
            alert("Error updating segment: " + e.message);
        }
    }

    function init()
    {
       if (typeof W === 'undefined' || !W.userscripts)
       {
          window.setTimeout(init, 100);
          return;
       }       

       console.log("Initialising...");

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

    function createHTMLTab() {
       let html = `<div style="padding: 15px; font-family: Helvetica, Arial, sans-serif;">`;       
       html += `<p style="font-size: 13px; color: #555; line-height: 1.4;">${_t("lbl_this_script_is_currently_in_beta")}</p>`;
       html += `<p style="font-size: 13px; color: #555; line-height: 1.4;">${_t("lbl_select_a_segment_to_smooth")}</p>`;
       html += `<button id="smoothSegmentButton" style="width: 100%; padding: 8px; cursor: pointer; border: 1px solid #ccc; background-color: #f0f0f0; border-radius: 5px; display: flex; justify-content: center; align-items: center; line-height: normal;">${_t("lbl_to_smooth")}</button>`;

       html += '</div>';
       return html;
   }

    async function createTab()
    {
       let { tabLabel, tabPane } = W.userscripts.registerSidebarTab("Segment Smoothing Tool");
       tabLabel.innerText = "Segment Smoothing Tool";
       tabPane.innerHTML = createHTMLTab();

       await W.userscripts.waitForElementConnected(tabPane);

       const btn = document.getElementById("smoothSegmentButton");
       if (btn) {
           btn.addEventListener("click", smoothSegmentButtonClick);
       }
    }

    init();
})();