WME Places Name Normalizer

Normaliza nombres de lugares en Waze Map Editor (WME)

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

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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();
})();