WME Places Name Normalizer

Normaliza nombres de lugares en Waze Map Editor (WME)

目前為 2025-03-19 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         WME Places Name Normalizer
// @namespace    https://greasyfork.org/en/users/mincho77
// @version      1.4
// @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";
    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;
    }
   let placesToNormalize = places
        .slice(0, maxPlaces)
        .filter(place =>
            place &&
            place.attributes &&
            place.attributes.name &&
            !excludeWords.some(excluded => place.attributes.name.includes(excluded))
        );

    console.log(`[${SCRIPT_NAME}] Lugares para normalizar: ${placesToNormalize.length}`);

    if (placesToNormalize.length === 0) {
        alert("No se encontraron Places para normalizar.");
        return;
    }

    // Enviar solo los lugares con nombre válido
    openFloatingPanel(placesToNormalize.map(place => ({
        id: place.attributes.id,
        originalName: place.attributes.name,
        newName: normalizePlaceName(place.attributes.name)
    })));
}


    function applyNormalization(selectedPlaces) {
    if (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 normalizePlaceName(name) {
    if (!name) return "";

    // Lista de artículos que no se deben normalizar si están al inicio
    const articles = ["el", "la", "los", "las"];

    // Lista de palabras excluidas (IPS, EDS, etc.)
    const exclusions = ["IPS", "EDS", "McDonald's"];

    // Separar el nombre en palabras
    let words = name.split(" ");

    // Verificar si la primera palabra es un artículo
    if (articles.includes(words[0].toLowerCase())) {
        words[0] = words[0][0].toUpperCase() + words[0].substring(1); // Capitalizar el artículo
    }

    // Aplicar normalización: convertir en minúsculas excepto la primera letra de cada palabra
    let normalizedWords = words.map(word => {
        // Si la palabra está en exclusiones, se mantiene igual
        if (exclusions.includes(word)) return word;

        // Si es la primera palabra, la capitalizamos siempre
        if (word === words[0]) {
            return word[0].toUpperCase() + word.substring(1).toLowerCase();
        }

        // Convertir a minúsculas y mantener primera letra en mayúsculas
        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();
})();