Greasy Fork镜像 还支持 简体中文。

WME NINA Warnungen Beta

Zeigt NINA Warnmeldungen im Waze Map Editor Scripts-Tab an

目前為 2025-08-21 提交的版本,檢視 最新版本

// ==UserScript==
// @name         WME NINA Warnungen Beta
// @namespace    https://gf.qytechs.cn/de/users/863740-horst-wittlich
// @version      2025.08.20
// @description  Zeigt NINA Warnmeldungen im Waze Map Editor Scripts-Tab an
// @author       Hiwi234
// @match        https://www.waze.com/editor*
// @match        https://www.waze.com/*/editor*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // CSS Styles hinzufügen
    GM_addStyle(`
        .nina-tab-content {
            padding: 10px;
            font-family: Arial, sans-serif;
            height: 100%;
            overflow-y: auto;
        }
        .nina-header {
            background: #ff6b35;
            color: white;
            padding: 10px;
            margin: -10px -10px 10px -10px;
            font-weight: bold;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .nina-stats {
            background: #f8f9fa;
            padding: 8px;
            border-radius: 4px;
            margin-bottom: 10px;
            font-size: 0.9em;
            color: #495057;
        }
        .nina-controls {
            background: #f8f9fa;
            padding: 8px;
            border-radius: 4px;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .nina-checkbox {
            display: flex;
            align-items: center;
            gap: 5px;
            font-size: 0.9em;
        }
        .nina-checkbox input[type="checkbox"] {
            margin: 0;
        }
        .nina-warning {
            border-left: 4px solid #ff6b35;
            padding: 8px;
            margin-bottom: 10px;
            background: #fff5f2;
            cursor: pointer;
            border-radius: 4px;
            transition: all 0.2s ease;
        }
        .nina-warning:hover {
            background: #ffe8e0;
            transform: translateX(2px);
            box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
        }
        .nina-warning-title {
            font-weight: bold;
            color: #d63031;
            margin-bottom: 4px;
            font-size: 1.1em;
        }
        .nina-warning-meta {
            color: #636e72;
            font-size: 0.9em;
            margin-bottom: 6px;
            line-height: 1.4;
        }
        .nina-warning-type {
            background: #ff6b35;
            color: white;
            padding: 2px 6px;
            border-radius: 12px;
            font-size: 0.8em;
            display: inline-block;
            margin-right: 4px;
        }
        .nina-refresh {
            cursor: pointer;
            margin-right: 10px;
            padding: 4px 8px;
            background: rgba(255,255,255,0.2);
            border-radius: 4px;
            transition: background 0.2s;
        }
        .nina-refresh:hover {
            background: rgba(255,255,255,0.3);
        }
        .nina-loading {
            text-align: center;
            padding: 20px;
            color: #636e72;
        }
        .nina-empty {
            text-align: center;
            padding: 30px;
            color: #636e72;
            background: #f8f9fa;
            border-radius: 8px;
            margin: 20px 0;
        }
        .nina-source-badge {
            background: #6c757d;
            color: white;
            padding: 1px 4px;
            border-radius: 2px;
            font-size: 0.7em;
            margin-left: 4px;
        }
        .nina-goto-btn {
            float: right;
            background: #007bff;
            color: white;
            border: none;
            padding: 2px 6px;
            border-radius: 3px;
            font-size: 0.8em;
            cursor: pointer;
            margin-left: 8px;
        }
        .nina-goto-btn:hover {
            background: #0056b3;
        }
    `);

    let ninaWarnings = [];
    let ninaMarkers = [];
    let ninaTab = null;
    let tabPane = null;
    let showMarkersOnMap = true; // Standard: Icons anzeigen

    function initNINAScript() {
        try {
            // Scripts-Tab registrieren
            const { tabLabel, tabPane: pane } = W.userscripts.registerSidebarTab("nina-warnings");
            tabPane = pane;

            // Tab-Label setzen
            tabLabel.innerHTML = 'NINA';
            tabLabel.title = 'NINA Warnmeldungen';
            tabLabel.style.fontSize = '16px';

            // Warten bis Tab im DOM ist
            W.userscripts.waitForElementConnected(tabPane).then(() => {
                setupTabContent();
                loadNINAWarnings();
                // Alle 5 Minuten aktualisieren
                setInterval(loadNINAWarnings, 300000);
            });

        } catch (error) {
            console.error('Fehler beim Registrieren des NINA Tabs:', error);
        }
    }

    function setupTabContent() {
        tabPane.innerHTML = `
            <div class="nina-tab-content">
                <div class="nina-header">
                    <span>🔔 NINA Warnungen</span>
                    <span class="nina-refresh" id="nina-refresh-btn">🔄 Aktualisieren</span>
                </div>
                <div class="nina-stats" id="nina-stats">
                    Lade Warnungen...
                </div>
                <div class="nina-controls">
                    <div class="nina-checkbox">
                        <input type="checkbox" id="show-markers-checkbox" checked>
                        <label for="show-markers-checkbox">📍 Icons auf Karte anzeigen</label>
                    </div>
                </div>
                <div id="nina-warnings-content" class="nina-loading">
                    <div>📡 Verbinde mit NINA API...</div>
                </div>
            </div>
        `;

        // Event Listener hinzufügen
        const refreshBtn = document.getElementById('nina-refresh-btn');
        if (refreshBtn) {
            refreshBtn.addEventListener('click', loadNINAWarnings);
        }

        const showMarkersCheckbox = document.getElementById('show-markers-checkbox');
        if (showMarkersCheckbox) {
            showMarkersCheckbox.addEventListener('change', toggleMapMarkers);
        }
    }

    function loadNINAWarnings() {
        console.log('Lade NINA Warnungen...');
        updateStats('Lade Warnungen...');

        // Alle verfügbaren Warndienste laden
        const sources = [
            'mowas',
            'biwapp',
            'katwarn',
            'dwd',
            'lhp'
        ];

        let allWarnings = [];
        let loadedCount = 0;

        sources.forEach(source => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://warnung.bund.de/api31/${source}/mapData.json`,
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (data && Array.isArray(data) && data.length > 0) {
                            console.log(`Gefunden ${data.length} Warnungen von ${source}`);
                            // Quelle zu jeder Warnung hinzufügen
                            data.forEach(warning => {
                                warning.source = source;
                            });
                            allWarnings = allWarnings.concat(data);
                        }
                    } catch (e) {
                        console.warn(`Fehler beim Laden von ${source}:`, e);
                    }

                    loadedCount++;
                    if (loadedCount === sources.length) {
                        console.log(`Insgesamt ${allWarnings.length} Warnungen geladen`);
                        processWarnings(allWarnings);
                    }
                },
                onerror: function(error) {
                    console.error(`Fehler beim Laden von ${source}:`, error);
                    loadedCount++;
                    if (loadedCount === sources.length) {
                        console.log(`Insgesamt ${allWarnings.length} Warnungen geladen`);
                        processWarnings(allWarnings);
                    }
                }
            });
        });
    }

    function processWarnings(warnings) {
        console.log('Verarbeite', warnings.length, 'Warnungen');

        // Sortierung: Neueste zuerst (nach startDate oder falls nicht vorhanden nach ID)
        warnings.sort((a, b) => {
            const dateA = a.startDate ? new Date(a.startDate) : new Date(0);
            const dateB = b.startDate ? new Date(b.startDate) : new Date(0);
            return dateB - dateA; // Neueste zuerst
        });

        ninaWarnings = warnings;

        // Alte Marker entfernen
        clearMarkers();

        // Neue Marker hinzufügen (nur wenn Checkbox aktiviert)
        if (showMarkersOnMap) {
            addMarkersToMap(warnings);
        }

        // Tab aktualisieren
        updateTabContent(warnings);
    }

    function clearMarkers() {
        ninaMarkers.forEach(markerData => {
            if (markerData.layer && markerData.marker) {
                try {
                    markerData.layer.removeMarker(markerData.marker);
                } catch (e) {
                    console.warn('Fehler beim Entfernen des Markers:', e);
                }
            }
        });
        ninaMarkers = [];

        // Falls Layer komplett neu erstellt werden soll
        const existingLayer = W.map.getOLMap().getLayersByName('ninaLayer')[0];
        if (existingLayer) {
            try {
                existingLayer.clearMarkers();
            } catch (e) {
                console.warn('clearMarkers nicht verfügbar, verwende destroy:', e);
                try {
                    W.map.getOLMap().removeLayer(existingLayer);
                } catch (e2) {
                    console.warn('Layer konnte nicht entfernt werden:', e2);
                }
            }
        }
    }

    function addMarkersToMap(warnings) {
        // Nur hinzufügen wenn Checkbox aktiviert ist
        if (!showMarkersOnMap) {
            console.log('Marker-Anzeige deaktiviert, überspringe Marker-Erstellung');
            return;
        }

        // NINA Layer erstellen oder wiederverwenden
        let ninaLayer = W.map.getOLMap().getLayersByName('ninaLayer')[0];
        if (!ninaLayer) {
            ninaLayer = new OpenLayers.Layer.Markers('ninaLayer');
            W.map.getOLMap().addLayer(ninaLayer);
            console.log('NINA Layer erstellt');
        }

        console.log(`Füge ${warnings.length} Marker hinzu`);

        warnings.forEach((warning, index) => {
            // Deutsche Städte für Demo-Zwecke
            const demoLocations = [
                [52.5200, 13.4050], // Berlin
                [48.1351, 11.5820], // München
                [53.5511, 9.9937],  // Hamburg
                [50.9375, 6.9603],  // Köln
                [50.1109, 8.6821],  // Frankfurt
                [48.7758, 9.1829],  // Stuttgart
                [51.2277, 6.7735],  // Düsseldorf
                [51.0504, 13.7373], // Dresden
                [53.0793, 8.8017],  // Bremen
                [52.3759, 9.7320],  // Hannover
                [49.4521, 11.0767], // Nürnberg
                [51.3127, 9.4797],  // Kassel
                [54.0924, 13.2015], // Rostock
                [49.0069, 8.4037],  // Karlsruhe
                [47.9990, 7.8421],  // Freiburg
                [50.7753, 6.0839]   // Aachen
            ];

            const [lat, lon] = demoLocations[index % demoLocations.length];

            // Koordinaten zur Warnung hinzufügen für später
            warning.demoCoords = { lat, lon };

            try {
                const lonLat = new OpenLayers.LonLat(lon, lat).transform(
                    new OpenLayers.Projection("EPSG:4326"),
                    W.map.getOLMap().getProjectionObject()
                );

                // Icon basierend auf Warnungstyp und Quelle
                const iconUrl = getWarningIcon(warning);
                const size = new OpenLayers.Size(32, 32);
                const offset = new OpenLayers.Pixel(-16, -16);
                const icon = new OpenLayers.Icon(iconUrl, size, offset);

                const marker = new OpenLayers.Marker(lonLat, icon);

                // Click Event für Marker
                marker.events.register('click', marker, function() {
                    showWarningDetails(warning);
                });

                ninaLayer.addMarker(marker);
                ninaMarkers.push({marker: marker, layer: ninaLayer});

            } catch (e) {
                console.error(`Fehler beim Erstellen von Marker ${index}:`, e);
            }
        });

        console.log(`${ninaMarkers.length} Marker erfolgreich hinzugefügt`);
    }

    function getWarningIcon(warning) {
        let color = '#ff6b35'; // Default orange

        // Farbe basierend auf Schweregrad
        if (warning.severity) {
            switch(warning.severity.toLowerCase()) {
                case 'extreme': color = '#8B0000'; break; // Dunkelrot
                case 'severe': color = '#FF0000'; break;  // Rot
                case 'moderate': color = '#FF8C00'; break; // Orange
                case 'minor': color = '#FFD700'; break;   // Gelb
                default: color = '#ff6b35'; break;
            }
        }

        // Symbol basierend auf Quelle (nur ASCII-Zeichen)
        let symbol = '!';
        if (warning.source) {
            switch(warning.source) {
                case 'dwd': symbol = 'W'; break;     // Weather
                case 'mowas': symbol = '!'; break;   // Alert
                case 'katwarn': symbol = 'K'; break; // KatWarn
                case 'biwapp': symbol = 'B'; break;  // BiWapp
                case 'lhp': symbol = 'H'; break;     // Hochwasser
                default: symbol = '!'; break;
            }
        }

        // SVG ohne problematische Unicode-Zeichen
        const svgContent = `<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
            <circle cx="16" cy="16" r="15" fill="${color}" stroke="#ffffff" stroke-width="2"/>
            <text x="16" y="20" text-anchor="middle" fill="white" font-size="16" font-weight="bold" font-family="Arial">${symbol}</text>
        </svg>`;

        try {
            return `data:image/svg+xml;base64,${btoa(svgContent)}`;
        } catch (e) {
            console.error('Fehler beim Erstellen des Icons:', e);
            // Fallback: einfaches farbiges Quadrat
            return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(`<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg"><rect width="32" height="32" fill="${color}"/><text x="16" y="20" text-anchor="middle" fill="white" font-size="16">${symbol}</text></svg>`)}`;
        }
    }

    function updateStats(message) {
        const statsEl = document.getElementById('nina-stats');
        if (statsEl) {
            if (typeof message === 'string') {
                statsEl.innerHTML = message;
            } else {
                const total = ninaWarnings.length;
                const sources = {};
                ninaWarnings.forEach(w => {
                    sources[w.source] = (sources[w.source] || 0) + 1;
                });

                const sourceStats = Object.entries(sources)
                    .map(([source, count]) => `${source.toUpperCase()}: ${count}`)
                    .join(' | ');

                statsEl.innerHTML = `📊 Gesamt: <strong>${total}</strong> Warnungen | ${sourceStats} | Letztes Update: ${new Date().toLocaleTimeString('de-DE')}`;
            }
        }
    }

    function updateTabContent(warnings) {
        updateStats();

        const content = document.getElementById('nina-warnings-content');
        if (!content) return;

        if (!warnings || warnings.length === 0) {
            content.innerHTML = `
                <div class="nina-empty">
                    <div style="font-size: 2em; margin-bottom: 10px;">✅</div>
                    <div style="font-weight: bold; margin-bottom: 5px;">Keine aktuellen Warnungen</div>
                    <div style="font-size: 0.9em;">Alle Systeme normal</div>
                </div>
            `;
            return;
        }

        const warningsHtml = warnings.map((warning, index) => {
            // Titel aus verschiedenen Quellen extrahieren
            const title = warning.i18nTitle?.de || warning.headline || warning.title || 'Warnung';

            // Zeitinfo formatieren
            const startDate = warning.startDate ? new Date(warning.startDate).toLocaleString('de-DE') : '';
            const expiresDate = warning.expiresDate ? new Date(warning.expiresDate).toLocaleString('de-DE') : '';

            // Schweregrad und Quelle
            const severity = warning.severity || 'Unbekannt';
            const source = warning.source || 'Unbekannt';
            const urgency = warning.urgency || '';

            // Status
            const type = warning.type || 'Alert';
            const isCancel = type === 'Cancel';

            return `
                <div class="nina-warning" data-warning-index="${index}" style="${isCancel ? 'opacity: 0.6; border-left-color: #95a5a6;' : ''}">
                    <div class="nina-warning-title">
                        ${isCancel ? '✅ Entwarnung: ' : ''}${title}
                        <button class="nina-goto-btn" data-goto-index="${index}">📍 Zur Karte</button>
                    </div>
                    <div class="nina-warning-meta">
                        📡 Quelle: ${source.toUpperCase()}<span class="nina-source-badge">${getSourceSymbol(source)}</span>
                        ${startDate ? `<br>🕐 Seit: ${startDate}` : ''}
                        ${expiresDate ? `<br>⏰ Bis: ${expiresDate}` : ''}
                    </div>
                    <div>
                        <span class="nina-warning-type" style="background: ${getSeverityColor(severity)}">${severity}</span>
                        ${urgency ? `<span class="nina-warning-type" style="background: #e74c3c; margin-left: 5px;">${urgency}</span>` : ''}
                        ${isCancel ? '<span class="nina-warning-type" style="background: #27ae60; margin-left: 5px;">Entwarnung</span>' : ''}
                    </div>
                </div>
            `;
        }).join('');

        content.innerHTML = warningsHtml;

        // Event Listener für Warnungen hinzufügen
        content.querySelectorAll('.nina-warning').forEach(warningEl => {
            const index = parseInt(warningEl.dataset.warningIndex);
            warningEl.addEventListener('click', () => showWarningDetails(index));
        });

        // Event Listener für "Zur Karte" Buttons hinzufügen
        content.querySelectorAll('.nina-goto-btn').forEach(btn => {
            const index = parseInt(btn.dataset.gotoIndex);
            btn.addEventListener('click', (event) => {
                event.stopPropagation();
                gotoWarning(index);
            });
        });
    }

    function getSourceSymbol(source) {
        switch(source) {
            case 'dwd': return 'W';
            case 'mowas': return '!';
            case 'katwarn': return 'K';
            case 'biwapp': return 'B';
            case 'lhp': return 'H';
            default: return '?';
        }
    }

    function getSeverityColor(severity) {
        switch(severity?.toLowerCase()) {
            case 'extreme': return '#8B0000';
            case 'severe': return '#FF0000';
            case 'moderate': return '#FF8C00';
            case 'minor': return '#FFD700';
            default: return '#ff6b35';
        }
    }

    function showWarningDetails(index) {
        const warning = ninaWarnings[index];
        if (!warning) return;

        const title = warning.i18nTitle?.de || warning.headline || warning.title || 'Warnung';
        const description = warning.description || warning.info?.[0]?.description || 'Keine Details verfügbar';
        const source = warning.source ? warning.source.toUpperCase() : 'Unbekannte Quelle';
        const severity = warning.severity || 'Unbekannt';
        const urgency = warning.urgency || 'Unbekannt';
        const type = warning.type || 'Alert';
        const startDate = warning.startDate ? new Date(warning.startDate).toLocaleString('de-DE') : 'Unbekannt';
        const expiresDate = warning.expiresDate ? new Date(warning.expiresDate).toLocaleString('de-DE') : 'Unbekannt';

        const isCancel = type === 'Cancel';
        const statusText = isCancel ? '✅ ENTWARNUNG' : '🚨 AKTIVE WARNUNG';

        alert(`${statusText}\n\n📢 ${title}\n\n📊 Quelle: ${source}\n🔴 Schweregrad: ${severity}\n⚡ Dringlichkeit: ${urgency}\n\n🕐 Gültig seit: ${startDate}\n⏰ Gültig bis: ${expiresDate}\n\n📄 Details:\n${description}`);
    }

    function toggleMapMarkers() {
        const checkbox = document.getElementById('show-markers-checkbox');
        showMarkersOnMap = checkbox ? checkbox.checked : true;

        if (showMarkersOnMap) {
            console.log('Aktiviere Karten-Marker');
            // Marker anzeigen
            addMarkersToMap(ninaWarnings);
        } else {
            console.log('Deaktiviere Karten-Marker');
            // Marker verstecken
            clearMarkers();
        }
    }

    function gotoWarning(index) {
        const warning = ninaWarnings[index];
        if (!warning || !warning.demoCoords) return;

        const { lat, lon } = warning.demoCoords;

        // Zur Position auf der Karte springen
        const lonLat = new OpenLayers.LonLat(lon, lat).transform(
            new OpenLayers.Projection("EPSG:4326"),
            W.map.getOLMap().getProjectionObject()
        );

        // Karte zur Position bewegen
        W.map.getOLMap().setCenter(lonLat);

        // Optional: Zoom-Level setzen
        if (W.map.getOLMap().getZoom() < 5) {
            W.map.getOLMap().zoomTo(5);
        }

        console.log(`Springe zur Warnung: ${warning.i18nTitle?.de || warning.id} (${lat}, ${lon})`);

        // Kurz blinken lassen für visuelles Feedback (nur wenn Marker sichtbar)
        if (showMarkersOnMap) {
            const marker = ninaMarkers[index];
            if (marker && marker.marker) {
                const originalIcon = marker.marker.icon;
                // Temporär rotes Icon setzen
                setTimeout(() => {
                    if (marker.marker.icon) {
                        const redIcon = getWarningIcon({...warning, severity: 'extreme'});
                        marker.marker.setUrl(redIcon);
                        setTimeout(() => {
                            marker.marker.setUrl(originalIcon.url);
                        }, 1000);
                    }
                }, 100);
            }
        }
    }

    // Globale Funktionen entfernen - nicht mehr benötigt
    // window.showWarningDetails = showWarningDetails;

    // Script initialisieren
    function initializeScript() {
        if (W?.userscripts?.state.isInitialized) {
            initNINAScript();
        } else {
            document.addEventListener("wme-initialized", initNINAScript, {
                once: true,
            });
        }
    }

    // Script starten
    console.log('NINA WME Script gestartet');
    initializeScript();

})();

QingJ © 2025

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