WME Places Name Normalizer

Normaliza nombres de lugares en Waze Map Editor (WME)

当前为 2025-03-21 提交的版本,查看 最新版本

// ==UserScript==
// @name         WME Places Name Normalizer
// @namespace    https://gf.qytechs.cn/en/users/mincho77
// @version      1.4.1
// @description  Normaliza nombres de lugares en Waze Map Editor (WME)
// @author       mincho77
// @match        https://www.waze.com/*editor*
// @grant        none
// @license      MIT
// @run-at       document-end
// ==/UserScript==
/*global W*/

(() => {
    "use strict";

    const SCRIPT_NAME = "PlacesNameNormalizer";
    const VERSION = "1.4.1";
    let excludeWords = JSON.parse(localStorage.getItem("excludeWords")) || ["EDS", "IPS", "McDonald's", "EPS"];
    let maxPlaces = 50;
    let normalizeArticles = true;

    function createSidebarTab() {
        const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("PlacesNormalizer");

        if (!tabPane) {
            console.error(`[${SCRIPT_NAME}] Error: No se pudo registrar el sidebar tab.`);
            return;
        }

        tabLabel.innerText = "Normalizer";
        tabLabel.title = "Places Name Normalizer";
        tabPane.innerHTML = getSidebarHTML();

        setTimeout(() => {
            // Llamar a la función para esperar el DOM antes de ejecutar eventos
            waitForDOM("#normalizer-tab", attachEvents);
            //attachEvents();
        }, 500);
    }


    function waitForDOM(selector, callback, interval = 500, maxAttempts = 10) {
    let attempts = 0;
    const checkExist = setInterval(() => {
        const element = document.querySelector(selector);
        if (element) {
            clearInterval(checkExist);
            callback(element);
        } else if (attempts >= maxAttempts) {
            clearInterval(checkExist);
            console.error(`[${SCRIPT_NAME}] Error: No se encontraron elementos en el DOM después de ${maxAttempts} intentos.`);
        }
        attempts++;
    }, interval);
}



    function getSidebarHTML() {
        return `
            <div id='normalizer-tab'>
                <h4>Places Name Normalizer <span style='font-size:11px;'>v${VERSION}</span></h4>
               <div>
<input type="checkbox" id="normalizeArticles" checked>
<label for="normalizeArticles">
    No normalizar artículos (el, la, los, las)
</label>
                <div>
                    <label>Máximo de Places a buscar: </label>
                    <input type='number' id='maxPlacesInput' value='${maxPlaces}' min='1' max='500' style='width: 60px;'>
                </div>
                <div>
                    <label>Palabras Excluidas:</label>
                    <input type='text' id='excludeWord' style='width: 120px;'>
                    <button id='addExcludeWord'>Añadir</button>
                    <ul id='excludeList'>${excludeWords.map(word => `<li>${word}</li>`).join('')}</ul>
                </div>
                <button id='scanPlaces' class='btn btn-primary'>Escanear</button>
            </div>`;
    }

    function attachEvents() {
        console.log(`[${SCRIPT_NAME}] Adjuntando eventos...`);

        let normalizeArticlesCheckbox = document.getElementById("normalizeArticles");
        let maxPlacesInput = document.getElementById("maxPlacesInput");
        let addExcludeWordButton = document.getElementById("addExcludeWord");
        let scanPlacesButton = document.getElementById("scanPlaces");

        if (!normalizeArticlesCheckbox || !maxPlacesInput || !addExcludeWordButton || !scanPlacesButton) {
            console.error(`[${SCRIPT_NAME}] Error: No se encontraron elementos en el DOM.`);
            return;
        }

        normalizeArticlesCheckbox.addEventListener("change", (e) => {
            normalizeArticles = e.target.checked;
        });

        maxPlacesInput.addEventListener("input", (e) => {
            maxPlaces = parseInt(e.target.value, 10);
        });

        addExcludeWordButton.addEventListener("click", () => {
            const wordInput = document.getElementById("excludeWord");
            const word = wordInput.value.trim();
            if (word && !excludeWords.includes(word)) {
                excludeWords.push(word);
                localStorage.setItem("excludeWords", JSON.stringify(excludeWords));
                updateExcludeList();
                wordInput.value = "";
            }
        });

        scanPlacesButton.addEventListener("click", scanPlaces);
    }

    function updateExcludeList() {
        const list = document.getElementById("excludeList");
        list.innerHTML = excludeWords.map(word => `<li>${word}</li>`).join('');
    }



function scanPlaces() {
     console.log(`[${SCRIPT_NAME}] Iniciando escaneo de lugares...`);

    if (!W || !W.model || !W.model.venues || !W.model.venues.objects) {
        console.error(`[${SCRIPT_NAME}] WME no está listo.`);
        return;
    }

    let places = Object.values(W.model.venues.objects);
    console.log(`[${SCRIPT_NAME}] Total de lugares encontrados: ${places.length}`);

    if (places.length === 0) {
        alert("No se encontraron Places en WME.");
        return;
    }
    // 1) Filtra places con nombre y que no estén en la lista de exclusiones
    let placesToNormalize = places
    .slice(0, maxPlaces)
    .filter(place =>
        place &&
        place.attributes &&
        place.attributes.name &&
        !excludeWords.some(excluded => place.attributes.name.includes(excluded))
    );

    // 2) Mapea a un array con originalName y newName
    let placesMapped = placesToNormalize.map(place => {
        let originalName = place.attributes.name;
        let newName = normalizePlaceName(originalName);
        return {
            id: place.attributes.id,
            originalName,
            newName
        };
    });

    // 3) Filtra solo aquellos que sí van a cambiar
    let filteredPlaces = placesMapped.filter(p =>
        p.newName.trim() !== p.originalName.trim()
    );

    console.log(`[${SCRIPT_NAME}] Lugares que cambiarán: ${filteredPlaces.length}`);

    if (filteredPlaces.length === 0) {
        alert("No se encontraron Places que requieran cambio.");
        return;
    }

    // 4) Llama al panel flotante con los que realmente cambiarán
    openFloatingPanel(filteredPlaces);
    /*openFloatingPanel(placesToNormalize.map(place => ({
        id: place.attributes.id,
        originalName: place.attributes.name,
        newName: normalizePlaceName(place.attributes.name)
    })));*/
}


    function applyNormalization(selectedPlaces) {
    if (!selectedPlaces || selectedPlaces.length === 0) {
        alert("No se ha seleccionado ningún lugar.");
        return;
    }

    selectedPlaces.forEach((place) => {
        let wazePlace = W.model.venues.getObjectById(place.id);
        if (wazePlace) {
            wazePlace.attributes.name = place.newName;
            console.log(`Updated ${wazePlace.attributes.id} -> ${place.newName}`);
        }
    });

    alert("Normalization applied!");
}

function normalizePlaceName(name) {
    if (!name) return "";

    const articles = ["el", "la", "los", "las", "del","de", "y"];
    const exclusions = ["IPS", "EDS", "McDonald's"];

    let words = name.split(" ");

    let normalizedWords = words.map((word) => {
        // Si la palabra está en la lista de exclusiones, se deja igual.
        if (exclusions.includes(word)) return word;


            // Si la primera palabra es un artículo
     // Si la palabra es un artículo
        if (articles.includes(word.toLowerCase())) {
            // Checkbox marcado => NO normalizar => devolvemos tal cual esté escrito
            if (normalizeArticles) {
                return word;
            } else {
                // Checkbox desmarcado => normalizar => capitalizar
                return word[0].toUpperCase() + word.substring(1).toLowerCase();
            }
        }

        // Para el resto de las palabras, se normalizan (primera letra mayúscula).
        return word[0].toUpperCase() + word.substring(1).toLowerCase();
    });

    return normalizedWords.join(" ");
}

 function openFloatingPanel(placesToNormalize) {
    console.log(`[${SCRIPT_NAME}] Creando panel flotante...`);

    if (!placesToNormalize || placesToNormalize.length === 0) {
        console.warn(`[${SCRIPT_NAME}] No hay lugares para normalizar.`);
        return;
    }

    // Elimina cualquier panel flotante previo
    let existingPanel = document.getElementById("normalizer-floating-panel");
    if (existingPanel) existingPanel.remove();

    // Crear el panel flotante
    let panel = document.createElement("div");
    panel.id = "normalizer-floating-panel";
    panel.style.position = "fixed";
    panel.style.top = "100px";
    panel.style.right = "20px";
    panel.style.width = "420px";
    panel.style.background = "white";
    panel.style.border = "1px solid black";
    panel.style.padding = "10px";
    panel.style.boxShadow = "0px 0px 10px rgba(0,0,0,0.5)";
    panel.style.zIndex = "10000";
    panel.style.overflowY = "auto";
    panel.style.maxHeight = "500px";

    // Contenido del panel
    let panelContent = `
        <h3 style="text-align: center;">Lugares para Normalizar</h3>
        <table style="width: 100%; border-collapse: collapse;">
            <thead>
                <tr style="border-bottom: 2px solid black;">
                    <th><input type="checkbox" id="selectAllCheckbox"></th>
                    <th style="text-align: left;">Nombre Original</th>
                    <th style="text-align: left;">Nuevo Nombre</th>
                </tr>
            </thead>
            <tbody>
    `;

    placesToNormalize.forEach((place, index) => {
        if (place && place.originalName) {
            let originalName = place.originalName;
            let newName = place.newName;

            panelContent += `
                <tr>
                    <td><input type="checkbox" class="normalize-checkbox" data-index="${index}" checked></td>
                    <td>${originalName}</td>
                    <td><input type="text" class="new-name-input" data-index="${index}" value="${newName}" style="width: 100%;"></td>
                </tr>
            `;
        }
    });

    panelContent += `</tbody></table>`;

    // Agregar botones al panel sin eventos inline
    panelContent += `
        <button id="applyNormalizationBtn" style="margin-top: 10px; width: 100%; padding: 8px; background: #4CAF50; color: white; border: none; cursor: pointer;">
            Aplicar Normalización
        </button>
        <button id="closeFloatingPanelBtn" style="margin-top: 5px; width: 100%; padding: 8px; background: #d9534f; color: white; border: none; cursor: pointer;">
            Cerrar
        </button>
    `;

    panel.innerHTML = panelContent;
    document.body.appendChild(panel);

    // Evento para seleccionar todas las casillas
    document.getElementById("selectAllCheckbox").addEventListener("change", function() {
        let isChecked = this.checked;
        document.querySelectorAll(".normalize-checkbox").forEach(checkbox => {
            checkbox.checked = isChecked;
        });
    });

    // Evento para cerrar el panel (CORREGIDO)
    document.getElementById("closeFloatingPanelBtn").addEventListener("click", function() {
        let panel = document.getElementById("normalizer-floating-panel");
        if (panel) panel.remove();
    });

    // Evento para aplicar normalización
    document.getElementById("applyNormalizationBtn").addEventListener("click", function() {
        let selectedPlaces = [];
        document.querySelectorAll(".normalize-checkbox:checked").forEach(checkbox => {
            let index = checkbox.getAttribute("data-index");
            let newName = document.querySelector(`.new-name-input[data-index="${index}"]`).value;
            selectedPlaces.push({ ...placesToNormalize[index], newName });
        });

        if (selectedPlaces.length === 0) {
            alert("No se ha seleccionado ningún lugar.");
            return;
        }

        applyNormalization(selectedPlaces);
    });
}



    function waitForWME() {
        if (W && W.userscripts && W.model && W.model.venues) {
            console.log(`[${SCRIPT_NAME}] Inicializando v${VERSION}`);
            createSidebarTab();
        } else {
            console.log(`[${SCRIPT_NAME}] Esperando que WME esté listo...`);
            setTimeout(waitForWME, 1000);
        }
    }
    console.log(window.applyNormalization);
window.applyNormalization = applyNormalization;
    waitForWME();
})();

QingJ © 2025

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