WME Multi Map Overlay

Erweiterte Version mit TopPlus, Dark Map, Basemap DE und allen DACH Geoportal Overlays

התקן את הסקריפט?
סקריפטים מומלצים של יוצר זה

אולי תאהב גם את WME GeoPortal Overlay DACH Beta.

התקן את הסקריפט
// ==UserScript==
// @name WME Multi Map Overlay
// @namespace https://greasyfork.org/de/users/863740-horst-wittlich
// @version 2025.09.13
// @description Erweiterte Version mit TopPlus, Dark Map, Basemap DE und allen DACH Geoportal Overlays
// @author Hiwi234
// @match https://www.waze.com/editor*
// @match https://www.waze.com/*/editor*
// @match https://beta.waze.com/editor*
// @match https://beta.waze.com/*/editor*
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_info
// @connect sgx.geodatenzentrum.de
// @connect owsproxy.lgl-bw.de
// @connect www.wmts.nrw.de
// @connect geoservices.bayern.de
// @connect mapsneu.wien.gv.at
// @connect basemap.at
// @connect wmts.geo.admin.ch
// @connect cdnjs.cloudflare.com
// @connect a.tile.openstreetmap.org
// @connect b.tile.openstreetmap.org
// @connect c.tile.openstreetmap.org
// @connect a.basemaps.cartocdn.com
// @connect b.basemaps.cartocdn.com
// @connect c.basemaps.cartocdn.com
// @license MIT
// ==/UserScript==

(function() {
'use strict';

const SCRIPT_NAME = 'WME Multi Overlay';
const SCRIPT_ID = 'wme-multi-overlay';
const DEFAULT_OPACITY = 0.64;
const DEFAULT_ZINDEX = 2010; // KORRIGIERT: Korrekte Standardwert 2010
const STORAGE_KEY = 'wme-overlay-settings';
const COLLAPSED_STATE_KEY = 'wme-overlay-collapsed-groups';

// Layer storage
const layers = {};

// KORRIGIERT: Zusätzliche Metadaten-Speicherung für Z-Index
const layerMetadata = {};

// Collapsed state storage
let collapsedGroups = {};

// Settings Management - KORRIGIERT
function saveSettings() {
    const settings = {
        version: GM_info.script.version,
        layers: {}
    };

    Object.keys(layers).forEach(layerId => {
        const layer = layers[layerId];
        if (layer) {
            // KORRIGIERT: Verwende Metadaten für Z-Index als Fallback
            const metadata = layerMetadata[layerId] || {};
            settings.layers[layerId] = {
                visible: layer.getVisibility(),
                opacity: layer.opacity || DEFAULT_OPACITY,
                zIndex: metadata.zIndex || layer.getZIndex() || DEFAULT_ZINDEX // KORRIGIERT
            };
        }
    });

    try {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
        console.log('Settings gespeichert:', settings);
    } catch (error) {
        console.error('Fehler beim Speichern der Settings:', error);
    }
}

function saveCollapsedState() {
    try {
        localStorage.setItem(COLLAPSED_STATE_KEY, JSON.stringify(collapsedGroups));
    } catch (error) {
        console.error('Fehler beim Speichern des Collapsed State:', error);
    }
}

function loadCollapsedState() {
    try {
        const saved = localStorage.getItem(COLLAPSED_STATE_KEY);
        if (saved) {
            collapsedGroups = JSON.parse(saved);
        }
    } catch (error) {
        console.error('Fehler beim Laden der Collapsed State:', error);
        collapsedGroups = {};
    }
}

function saveGlobalFilters() {
    try {
        localStorage.setItem('wme-overlay-global-filters', JSON.stringify(globalFilters));
    } catch (error) {
        console.error('Fehler beim Speichern der globalen Filter:', error);
    }
}

function loadGlobalFilters() {
    try {
        const saved = localStorage.getItem('wme-overlay-global-filters');
        if (saved) {
            const filters = JSON.parse(saved);
            globalFilters = { ...globalFilters, ...filters };
        }
    } catch (error) {
        console.error('Fehler beim Laden der globalen Filter:', error);
    }
}

function loadSettings() {
    try {
        const saved = localStorage.getItem(STORAGE_KEY);
        if (saved) {
            const settings = JSON.parse(saved);
            console.log('Settings geladen:', settings);
            return settings;
        }
    } catch (error) {
        console.error('Fehler beim Laden der Settings:', error);
    }
    return { layers: {} };
}

function applySettingsToLayer(layerId, layer) {
    const settings = loadSettings();
    const layerSettings = settings.layers[layerId];

    if (layerSettings) {
        // KORRIGIERT: Initialisiere Metadaten für Layer
        if (!layerMetadata[layerId]) {
            layerMetadata[layerId] = {};
        }

        // Sichtbarkeit anwenden
        layer.setVisibility(layerSettings.visible || false);

        // Transparenz anwenden
        layer.setOpacity(layerSettings.opacity || DEFAULT_OPACITY);

        // KORRIGIERT: Z-Index anwenden und in Metadaten speichern
        const zIndex = layerSettings.zIndex || DEFAULT_ZINDEX;
        layer.setZIndex(zIndex);
        layerMetadata[layerId].zIndex = zIndex;

        console.log(`Settings für ${layerId} angewendet:`, layerSettings);
    } else {
        // KORRIGIERT: Standardwerte setzen wenn keine Settings vorhanden
        if (!layerMetadata[layerId]) {
            layerMetadata[layerId] = {};
        }
        layer.setZIndex(DEFAULT_ZINDEX);
        layerMetadata[layerId].zIndex = DEFAULT_ZINDEX;
    }
}

// Filter-Updates für alle Layer
function updateAllLayerFilters() {
    const brightness = globalFilters.brightness;
    const contrast = globalFilters.contrast;
    const saturation = globalFilters.saturation;
    const sharpness = globalFilters.sharpness;

    // CSS Filter mit funktionierender Schärfe erstellen
    // Schärfe wird über blur() (umgekehrt) implementiert: 100% = kein blur, über 100% = unsharp mask via contrast
    let filterString = `brightness(${brightness}%) contrast(${contrast}%) saturate(${saturation}%)`;

    // Schärfe-Implementierung
    if (sharpness < 100) {
        // Unter 100%: Blur hinzufügen (weicher)
        const blurAmount = (100 - sharpness) / 100 * 2; // Max 2px blur bei 0%
        filterString += ` blur(${blurAmount}px)`;
    } else if (sharpness > 100) {
        // Über 100%: Zusätzlichen Kontrast für Schärfe-Effekt
        const extraContrast = 100 + (sharpness - 100) * 0.5; // Moderate Verstärkung
        filterString += ` contrast(${extraContrast}%)`;
    }

    console.log('Applying global filters:', filterString);

    // Filter auf alle OpenLayers Divs anwenden
    setTimeout(() => {
        const layerElements = document.querySelectorAll('.olLayerDiv');
        layerElements.forEach(element => {
            if (element && element.style) {
                element.style.filter = filterString;
                element.style.webkitFilter = filterString;
            }
        });

        // Zusätzlich auf alle Layer-Container anwenden
        Object.keys(layers).forEach(layerId => {
            const layer = layers[layerId];
            if (layer && layer.div && layer.getVisibility()) {
                layer.div.style.filter = filterString;
                layer.div.style.webkitFilter = filterString;
            }
        });

        // Für Canvas-Elemente (falls vorhanden)
        const canvasElements = document.querySelectorAll('#map canvas');
        canvasElements.forEach(canvas => {
            if (canvas && canvas.style) {
                canvas.style.filter = filterString;
                canvas.style.webkitFilter = filterString;
            }
        });
    }, 100);
}

// Neu organisierte Layer-Konfiguration
const layerGroups = {
    basic: {
        name: "🔧 Basis Layer",
        layers: [
            {
                id: 'topplus',
                name: 'TopPlus WMS',
                type: 'wms',
                url: 'https://sgx.geodatenzentrum.de/wms_topplus_web_open',
                params: {
                    layers: 'web',
                    format: 'image/png',
                    transparent: true
                },
                attribution: '© BKG'
            },
            {
                id: 'topplus-grau',
                name: 'TopPlus Grau',
                type: 'wms',
                url: 'https://sgx.geodatenzentrum.de/wms_topplus_web_open',
                params: {
                    layers: 'web_grau',
                    format: 'image/png',
                    transparent: true
                },
                attribution: '© BKG'
            }
        ]
    },
    germany: {
        name: "🇩🇪 GeoOverlays DE",
        layers: [
            {
                id: 'basemap-de',
                name: 'Basemap DE',
                type: 'wms',
                url: 'https://sgx.geodatenzentrum.de/wms_basemapde',
                params: {
                    layers: 'de_basemapde_web_raster_farbe',
                    format: 'image/png',
                    transparent: true
                },
                attribution: '© <a href="https://www.basemap.de">basemap.de</a>'
            },
            {
                id: 'basemap-de-grau',
                name: 'Basemap.de Grau',
                type: 'wms',
                url: 'https://sgx.geodatenzentrum.de/wms_basemapde',
                params: {
                    layers: 'de_basemapde_web_raster_grau',
                    format: 'image/png',
                    transparent: true
                },
                attribution: '© <a href="https://www.basemap.de">basemap.de</a>'
            },
            {
                id: "geoportal-nrw",
                name: "GeoPortal NRW",
                type: "wmts",
                source: "https://www.wmts.nrw.de/geobasis/wmts_nw_dtk/1.0.0/WMTSCapabilities.xml",
                layerName: "nw_dtk_col",
                matrixSet: "EPSG_3857_16",
            },
            {
                id: "geoportal-nrw-overlay",
                name: "GeoPortal NRW Overlay",
                type: "wmts",
                source: "https://www.wmts.nrw.de/geobasis/wmts_nw_dop_overlay/1.0.0/WMTSCapabilities.xml",
                layerName: "nw_dop_overlay",
                matrixSet: "EPSG_3857_16",
            },
            {
                id: "geoportal-by",
                name: "GeoPortal BY",
                type: "wmts",
                source: "https://geoservices.bayern.de/od/wmts/geobasis/v1/1.0.0/WMTSCapabilities.xml",
                layerName: "by_webkarte",
                matrixSet: "smerc",
            },
            {
                id: "dop-nrw",
                name: "Luftbild NRW",
                type: "wmts",
                source: "https://www.wmts.nrw.de/geobasis/wmts_nw_dop/1.0.0/WMTSCapabilities.xml",
                layerName: "nw_dop",
                matrixSet: "EPSG_3857_16",
            },
            {
                id: "dop-by",
                name: "Luftbild BY",
                type: "wmts",
                source: "https://geoservices.bayern.de/od/wmts/geobasis/v1/1.0.0/WMTSCapabilities.xml",
                layerName: "by_dop",
                matrixSet: "smerc",
            },
            {
                id: 'vg25-de',
                name: 'Verwaltungsgebiete DE 1:25.000',
                type: 'wms',
                url: 'https://sg.geodatenzentrum.de/wms_vg25',
                params: {
                    layers: 'vg25',
                    format: 'image/png',
                    transparent: true
                },
                attribution: '© BKG'
            },
            {
                id: 'vg25-li-de',
                name: 'Grenzlinien DE 1:25.000',
                type: 'wms',
                url: 'https://sg.geodatenzentrum.de/wms_vg25',
                params: {
                    layers: 'vg25_li',
                    format: 'image/png',
                    transparent: true
                },
                attribution: '© BKG'
            }
        ]
    },
    austria: {
        name: "🇦🇹 GeoOverlays AT",
        layers: [
            {
                id: "basemap-at",
                name: "Basemap AT",
                type: "wmts",
                source: "https://mapsneu.wien.gv.at/basemapneu/1.0.0/WMTSCapabilities.xml",
                layerName: "geolandbasemap",
                matrixSet: "google3857",
            },
            {
                id: "overlay-at",
                name: "Overlay AT",
                type: "wmts",
                source: "https://www.basemap.at/wmts/1.0.0/WMTSCapabilities.xml",
                layerName: "bmapoverlay",
                matrixSet: "google3857",
            }
        ]
    },
    switzerland: {
        name: "🇨🇭 GeoOverlays CH",
        layers: [
            {
                id: "swiss-strassen",
                name: "Strassenkarte",
                type: "wmts",
                source: "https://wmts.geo.admin.ch/EPSG/3857/1.0.0/WMTSCapabilities.xml",
                layerName: "ch.swisstopo.swisstne-base",
                matrixSet: "3857_18",
            },
            {
                id: "swiss-basis",
                name: "Basisnetz",
                type: "wmts",
                source: "https://wmts.geo.admin.ch/EPSG/3857/1.0.0/WMTSCapabilities.xml",
                layerName: "ch.swisstopo.swisstlm3d-strassen",
                matrixSet: "3857_18",
            },
            {
                id: "swiss-luft",
                name: "Luftbild",
                type: "wmts",
                source: "https://wmts.geo.admin.ch/EPSG/3857/1.0.0/WMTSCapabilities.xml",
                layerName: "ch.swisstopo.swissimage-product",
                matrixSet: "3857_20",
            }
        ]
    },
    unofficial: {
        name: "🌍 Nicht Amtliche Karten",
        layers: [
            {
                id: 'osm-standard',
                name: 'OpenStreetMap Standard',
                type: 'xyz',
                urls: [
                    'https://a.tile.openstreetmap.org/${z}/${x}/${y}.png',
                    'https://b.tile.openstreetmap.org/${z}/${x}/${y}.png',
                    'https://c.tile.openstreetmap.org/${z}/${x}/${y}.png'
                ],
                attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
            },
            {
                id: 'darkmap',
                name: 'OSM Dark Matter',
                type: 'xyz',
                urls: [
                    'https://a.basemaps.cartocdn.com/dark_all/${z}/${x}/${y}@2x.png',
                    'https://b.basemaps.cartocdn.com/dark_all/${z}/${x}/${y}@2x.png',
                    'https://c.basemaps.cartocdn.com/dark_all/${z}/${x}/${y}@2x.png'
                ],
                attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
            }
        ]
    }
};

// Hilfsfunktionen für OpenLayers Patches
function loadOLScript(filename) {
    const version = OpenLayers.VERSION_NUMBER.replace(/Release /, "");
    console.info("Loading openlayers/" + version + "/" + filename + ".js");
    const script = document.createElement("script");
    script.src = `https://cdnjs.cloudflare.com/ajax/libs/openlayers/${version}/${filename}.js`;
    script.type = "text/javascript";
    script.async = false;
    document.head.appendChild(script);
}

function patchOpenLayers() {
    console.log("Patching OpenLayers for WMTS support...");

    if (!OpenLayers.VERSION_NUMBER.match(/^Release [0-9.]*$/)) {
        console.error("OpenLayers version mismatch - cannot apply patch");
        return;
    }

    // Lade notwendige OpenLayers Komponenten
    loadOLScript("lib/OpenLayers/Format/XML");
    loadOLScript("lib/OpenLayers/Format/XML/VersionedOGC");
    loadOLScript("lib/OpenLayers/Layer/WMTS");
    loadOLScript("lib/OpenLayers/Format/OWSCommon");
    loadOLScript("lib/OpenLayers/Format/OWSCommon/v1");
    loadOLScript("lib/OpenLayers/Format/OWSCommon/v1_1_0");
    loadOLScript("lib/OpenLayers/Format/WMSCapabilities");
    loadOLScript("lib/OpenLayers/Format/WMSCapabilities/v1");
    loadOLScript("lib/OpenLayers/Format/WMSCapabilities/v1_3");
    loadOLScript("lib/OpenLayers/Format/WMSCapabilities/v1_3_0");
    loadOLScript("lib/OpenLayers/Format/WMTSCapabilities");
    loadOLScript("lib/OpenLayers/Format/WMTSCapabilities/v1_0_0");
}

// Basis Layer erstellen - KORRIGIERT
function createBasicLayer(config) {
    const olMap = W.map.getOLMap();
    let layer;

    switch (config.type) {
        case 'wms':
            layer = new OpenLayers.Layer.WMS(
                config.name,
                config.url,
                config.params,
                {
                    transitionEffect: 'resize',
                    attribution: config.attribution,
                    isBaseLayer: false,
                    visibility: false,
                    opacity: DEFAULT_OPACITY,
                    projection: new OpenLayers.Projection("EPSG:3857"),
                    displayInLayerSwitcher: false,
                    alwaysInRange: true,
                    zIndex: DEFAULT_ZINDEX
                }
            );
            break;

        case 'xyz':
            layer = new OpenLayers.Layer.XYZ(
                config.name,
                config.urls,
                {
                    attribution: config.attribution,
                    transitionEffect: 'resize',
                    isBaseLayer: false,
                    visibility: false,
                    opacity: DEFAULT_OPACITY,
                    displayInLayerSwitcher: false,
                    alwaysInRange: true,
                    zIndex: DEFAULT_ZINDEX
                }
            );
            break;
    }

    if (layer) {
        layer.setOpacity(DEFAULT_OPACITY);
        olMap.addLayer(layer);
        layers[config.id] = layer;

        // KORRIGIERT: Metadaten initialisieren
        layerMetadata[config.id] = { zIndex: DEFAULT_ZINDEX };

        // Gespeicherte Einstellungen anwenden
        applySettingsToLayer(config.id, layer);
    }
}

// WMTS Layer erstellen - KORRIGIERT
function createWMTSLayer(layerConfig) {
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: layerConfig.source,
            onload: (response) => {
                try {
                    let responseXML = response.responseXML;
                    if (!responseXML) {
                        responseXML = new DOMParser().parseFromString(response.responseText, "text/xml");
                    }

                    if (!responseXML || !(responseXML instanceof XMLDocument)) {
                        throw new Error("Invalid XML response");
                    }

                    const format = new OpenLayers.Format.WMTSCapabilities();
                    const capabilities = format.read(responseXML);

                    const layer = format.createLayer(capabilities, {
                        layer: layerConfig.layerName,
                        matrixSet: layerConfig.matrixSet,
                        opacity: DEFAULT_OPACITY,
                        isBaseLayer: false,
                        requestEncoding: layerConfig.requestEncoding || "REST",
                        visibility: false,
                        zIndex: DEFAULT_ZINDEX
                    });

                    if (layer && layer.url && layer.url.length) {
                        const olMap = W.map.getOLMap();
                        olMap.addLayer(layer);
                        olMap.setLayerIndex(layer, 9);

                        layers[layerConfig.id] = layer;

                        // KORRIGIERT: Metadaten initialisieren
                        layerMetadata[layerConfig.id] = { zIndex: DEFAULT_ZINDEX };

                        // Gespeicherte Einstellungen anwenden
                        applySettingsToLayer(layerConfig.id, layer);

                        console.log(`✓ ${layerConfig.name} loaded successfully`);
                        resolve(layer);
                    } else {
                        throw new Error("No valid URLs found");
                    }

                } catch (error) {
                    console.error(`✗ Failed to load ${layerConfig.name}:`, error);
                    reject(error);
                }
            },
            onerror: () => reject(new Error("Network error")),
            ontimeout: () => reject(new Error("Request timeout"))
        });
    });
}

// Update Notification System
function showUpdateNotification() {
    // Prüfe ob bereits angezeigt wurde
    const lastShown = localStorage.getItem('wme-overlay-update-shown');
    const currentVersion = GM_info.script.version;

    if (lastShown === currentVersion) {
        return; // Bereits für diese Version angezeigt
    }

    // Erstelle Overlay
    const overlay = document.createElement('div');
    overlay.className = 'update-notification-overlay';
    overlay.innerHTML = `
        <div class="update-notification">
            <div class="update-header">
                <h2>WME Multi Map 🗺️ Overlay</h2>
                <div class="header-right">
                    <span class="version-badge">v${currentVersion}</span>
                    <button class="close-btn" id="header-close-btn">×</button>
                </div>
            </div>
            <div class="update-content">
                <h3>🎨 Vollständige Overlay-Features!</h3>
                <div class="new-features">
                    <div class="feature-group">
                        <h4>🎛️ Filter-Optionen:</h4>
                        <ul>
                            <li>✅ Helligkeit (0-300%)</li>
                            <li>✅ Kontrast (0-300%)</li>
                            <li>✅ Sättigung (0-300%)</li>
                            <li>✅ Schärfe (0-300%)</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>📁 Klappmenüs:</h4>
                        <ul>
                            <li>✅ Alle Kategorien ein-/ausklappbar</li>
                            <li>✅ Zustand wird gespeichert</li>
                            <li>✅ Übersichtliche Navigation</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>🖱️ Drag & Drop:</h4>
                        <ul>
                            <li>✅ Kategorien per Drag & Drop neu ordnen</li>
                            <li>✅ Menüreihenfolge individuell anpassbar</li>
                            <li>✅ Visuelle Drop-Indikatoren</li>
                            <li>✅ Persistente Speicherung</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>⚙️ Erweiterte Features:</h4>
                        <ul>
                            <li>✅ Z-Index Kontrolle (Ebenen)</li>
                            <li>✅ Transparenz-Kontrolle</li>
                            <li>✅ Persistente Einstellungen</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>🗺️ Alle Karten verfügbar:</h4>
                        <ul>
                            <li>✅ Deutschland, Österreich, Schweiz</li>
                            <li>✅ Amtliche und nicht-amtliche Karten</li>
                            <li>✅ TopPlus, Basemap, OpenStreetMap</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>🆕 Neue Karten hinzugefügt:</h4>
                        <ul>
                            <li>🌍 <strong>Luftbild NRW (dop-nrw)</strong> - Hochauflösende Luftbilder von Nordrhein-Westfalen</li>
                            <li>🏔️ <strong>Luftbild Bayern (dop-by)</strong> - Detaillierte Luftaufnahmen aus Bayern</li>
                            <li>📍 <strong>Verwaltungsgebiete DE 1:25.000 (vg25-de)</strong> - Präzise deutsche Verwaltungsgrenzen im Maßstab 1:25.000</li>
                            <li>🔲 <strong>Grenzlinien DE 1:25.000 (vg25-li-de)</strong> - Deutsche Verwaltungsgrenzen ohne Beschriftung</li>
                        </ul>
                    </div>
                </div>
                <div class="feature-info">
                    <p><strong>🎯 NEU:</strong> Vier zusätzliche Karten für Deutschland verfügbar - perfekt für detaillierte Kartierung und Verwaltungsarbeiten! Danke an endradon</p>
                </div>
            </div>
            <div class="update-actions">
                <button class="btn-primary" id="explore-maps-btn">🚀 Neue Karten erkunden und Schließen!
                </button>
            </div>
        </div>
    `;

    // Styles für das Update-Popup
    const updateStyle = document.createElement('style');
    updateStyle.textContent = `
        .update-notification-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.8);
            z-index: 999999;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        }

        .update-notification {
            background: white;
            border-radius: 12px;
            max-width: 600px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            animation: slideIn 0.3s ease-out;
        }

        @keyframes slideIn {
            from {
                opacity: 0;
                transform: scale(0.9) translateY(-20px);
            }
            to {
                opacity: 1;
                transform: scale(1) translateY(0);
            }
        }

        .update-header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 20px;
            border-radius: 12px 12px 0 0;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .header-right {
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .close-btn {
            background: rgba(255, 255, 255, 0.2);
            border: none;
            color: white;
            width: 32px;
            height: 32px;
            border-radius: 50%;
            font-size: 18px;
            font-weight: bold;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background 0.2s;
        }

        .close-btn:hover {
            background: rgba(255, 255, 255, 0.3);
        }

        .update-header h2 {
            margin: 0;
            font-size: 24px;
            font-weight: 600;
        }

        .version-badge {
            background: rgba(255, 255, 255, 0.2);
            padding: 4px 12px;
            border-radius: 20px;
            font-size: 14px;
            font-weight: 500;
        }

        .update-content {
            padding: 24px;
        }

        .update-content h3 {
            margin: 0 0 20px 0;
            color: #333;
            font-size: 20px;
        }

        .new-features {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
            margin-bottom: 24px;
        }

        .feature-group {
            background: #f8f9fa;
            padding: 16px;
            border-radius: 8px;
            border-left: 4px solid #667eea;
        }

        .feature-group h4 {
            margin: 0 0 12px 0;
            color: #333;
            font-size: 16px;
            font-weight: 600;
        }

        .feature-group ul {
            margin: 0;
            padding-left: 0;
            list-style: none;
        }

        .feature-group li {
            margin: 6px 0;
            color: #555;
            font-size: 14px;
        }

        .feature-info {
            background: #e3f2fd;
            padding: 16px;
            border-radius: 8px;
            border-left: 4px solid #2196f3;
        }

        .feature-info p {
            margin: 0;
            color: #1565c0;
            font-size: 14px;
        }

        .feature-info strong {
            font-weight: 600;
        }

        .update-actions {
            padding: 20px 24px;
            border-top: 1px solid #eee;
            display: flex;
            gap: 12px;
            justify-content: flex-end;
        }

        .btn-primary, .btn-secondary {
            padding: 12px 24px;
            border: none;
            border-radius: 6px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-primary {
            background: #667eea;
            color: white;
        }

        .btn-primary:hover {
            background: #5a6fd8;
            transform: translateY(-1px);
        }

        .btn-secondary {
            background: #e0e0e0;
            color: #333;
        }

        .btn-secondary:hover {
            background: #d0d0d0;
        }
    `;
    document.head.appendChild(updateStyle);
    document.body.appendChild(overlay);

    // Event Listener für Buttons hinzufügen
    overlay.querySelector('#explore-maps-btn').addEventListener('click', function() {
        overlay.remove();
        localStorage.setItem('wme-overlay-update-shown', currentVersion);
    });



    overlay.querySelector('#header-close-btn').addEventListener('click', function() {
        overlay.remove();
        localStorage.setItem('wme-overlay-update-shown', currentVersion);
    });

    // Overlay bei Klick außerhalb schließen
    overlay.addEventListener('click', function(e) {
        if (e.target === overlay) {
            overlay.remove();
            localStorage.setItem('wme-overlay-update-shown', currentVersion);
        }
    });

    // ESC-Taste zum Schließen
    document.addEventListener('keydown', function(e) {
        if (e.key === 'Escape' && document.contains(overlay)) {
            overlay.remove();
            localStorage.setItem('wme-overlay-update-shown', currentVersion);
        }
    });
}

// Drag & Drop Funktionalität für Hauptmenüs/Kategorien
function makeGroupDraggable(container, groupKey) {
    container.dataset.groupKey = groupKey;

    const header = container.querySelector('.group-header');
    if (!header) return;

    // Drag-Handle zur Gruppe hinzufügen (rechts positioniert)
    const dragHandle = document.createElement('span');
    dragHandle.className = 'group-drag-handle';
    dragHandle.innerHTML = '⋮⋮';
    dragHandle.title = 'Kategorie verschieben';
    dragHandle.draggable = true; // Nur das Handle ist draggable

    // Handle am Ende des Headers einfügen
    header.appendChild(dragHandle);

    // Drag Events nur für das Handle
    dragHandle.addEventListener('dragstart', (e) => {
        container.classList.add('group-dragging');
        e.dataTransfer.setData('text/plain', groupKey);
        e.dataTransfer.effectAllowed = 'move';
        e.stopPropagation();
    });

    dragHandle.addEventListener('dragend', () => {
        container.classList.remove('group-dragging');
        // Entferne alle Drop-Indikatoren
        document.querySelectorAll('.layer-group').forEach(el => {
            el.classList.remove('group-drop-target-above', 'group-drop-target-below');
        });
    });

    // Drop Events auf dem Container
    container.addEventListener('dragover', (e) => {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';

        const draggingElement = document.querySelector('.group-dragging');
        if (draggingElement && draggingElement !== container) {
            const rect = container.getBoundingClientRect();
            const midY = rect.top + rect.height / 2;

            // Entferne vorherige Indikatoren
            container.classList.remove('group-drop-target-above', 'group-drop-target-below');

            // Füge entsprechenden Indikator hinzu
            if (e.clientY < midY) {
                container.classList.add('group-drop-target-above');
            } else {
                container.classList.add('group-drop-target-below');
            }
        }
    });

    container.addEventListener('dragleave', (e) => {
        // Nur entfernen wenn wir den Container wirklich verlassen
        if (!container.contains(e.relatedTarget)) {
            container.classList.remove('group-drop-target-above', 'group-drop-target-below');
        }
    });

    container.addEventListener('drop', (e) => {
        e.preventDefault();
        const draggedGroupKey = e.dataTransfer.getData('text/plain');
        const draggedElement = document.querySelector(`[data-group-key="${draggedGroupKey}"]`);

        if (draggedElement && draggedElement !== container) {
            const rect = container.getBoundingClientRect();
            const midY = rect.top + rect.height / 2;
            const parent = container.parentNode;

            if (e.clientY < midY) {
                // Einfügen vor dem aktuellen Element
                parent.insertBefore(draggedElement, container);
            } else {
                // Einfügen nach dem aktuellen Element
                parent.insertBefore(draggedElement, container.nextSibling);
            }

            // Speichere neue Gruppenreihenfolge
            saveGroupOrder();

            console.log(`Gruppe ${draggedGroupKey} verschoben`);
        }

        // Cleanup
        container.classList.remove('group-drop-target-above', 'group-drop-target-below');
    });

    // Verhindere Toggle beim Drag-Handle
    dragHandle.addEventListener('click', (e) => {
        e.stopPropagation();
        e.preventDefault();
    });
}

// Speichere und lade Gruppenreihenfolge
function saveGroupOrder() {
    const overlayTab = document.querySelector('.overlay-tab');
    if (!overlayTab) return;

    const groupElements = overlayTab.querySelectorAll('.layer-group[data-group-key]');
    const order = Array.from(groupElements).map(el => el.dataset.groupKey);

    try {
        localStorage.setItem('wme-overlay-group-order', JSON.stringify(order));
        console.log('Gruppenreihenfolge gespeichert:', order);
    } catch (error) {
        console.error('Fehler beim Speichern der Gruppenreihenfolge:', error);
    }
}

function loadGroupOrder() {
    try {
        const saved = localStorage.getItem('wme-overlay-group-order');
        if (saved) {
            return JSON.parse(saved);
        }
    } catch (error) {
        console.error('Fehler beim Laden der Gruppenreihenfolge:', error);
    }
    // Standard-Reihenfolge
    return ['basic', 'germany', 'austria', 'switzerland', 'unofficial'];
}
function createLayerControl(layerId, name, isGroup = false, groupKey = null) {
    const container = document.createElement('div');
    container.className = isGroup ? 'layer-group' : 'layer-control';

    if (isGroup) {
        const header = document.createElement('div');
        header.className = 'group-header';
        header.style.cursor = 'pointer';

        // Erstelle Toggle-Button mit Pfeil
        const toggleButton = document.createElement('span');
        toggleButton.className = 'toggle-button';
        toggleButton.innerHTML = collapsedGroups[groupKey] ? '▶' : '▼';

        const titleSpan = document.createElement('span');
        titleSpan.className = 'group-title';
        titleSpan.textContent = name;

        header.appendChild(toggleButton);
        header.appendChild(titleSpan);

        // Erstelle Content-Container
        const content = document.createElement('div');
        content.className = 'group-content';
        content.style.display = collapsedGroups[groupKey] ? 'none' : 'block';

        // Click-Handler für Toggle
        header.addEventListener('click', () => {
            const isCollapsed = content.style.display === 'none';
            content.style.display = isCollapsed ? 'block' : 'none';
            toggleButton.innerHTML = isCollapsed ? '▼' : '▶';

            // Speichere Zustand
            collapsedGroups[groupKey] = !isCollapsed;
            saveCollapsedState();
        });

        container.appendChild(header);
        container.appendChild(content);
        container.content = content; // Referenz für späteren Zugriff

        // Drag & Drop für Gruppen aktivieren
        makeGroupDraggable(container, groupKey);

        return container;
    }

    const layer = layers[layerId];
    if (!layer) return container;

    // Sichtbarkeits-Checkbox
    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.checked = layer.getVisibility();
    checkbox.addEventListener('change', () => {
        layer.setVisibility(checkbox.checked);
        saveSettings(); // Speichern nach Änderung
    });

    const label = document.createElement('label');
    label.appendChild(checkbox);
    label.appendChild(document.createTextNode(name));

    // Transparenz-Slider - KORRIGIERT
    const opacityContainer = document.createElement('div');
    opacityContainer.className = 'slider-container';

    const opacityLabel = document.createElement('span');
    opacityLabel.textContent = 'Transparenz: ';

    const opacityValue = document.createElement('span');
    opacityValue.className = 'slider-value';
    opacityValue.textContent = Math.round((layer.opacity || DEFAULT_OPACITY) * 100) + '%';

    const opacitySlider = document.createElement('input');
    opacitySlider.type = 'range';
    opacitySlider.min = '0';
    opacitySlider.max = '100';
    opacitySlider.step = '1';
    opacitySlider.value = Math.round((layer.opacity || DEFAULT_OPACITY) * 100);

    opacitySlider.addEventListener('input', () => {
        const value = parseInt(opacitySlider.value) / 100;
        layer.setOpacity(value);
        layer.opacity = value; // KORRIGIERT: Speichere auch im Layer-Objekt
        opacityValue.textContent = opacitySlider.value + '%';
        saveSettings(); // Speichern nach Änderung
    });

    opacityContainer.appendChild(opacityLabel);
    opacityContainer.appendChild(opacitySlider);
    opacityContainer.appendChild(opacityValue);

    // Z-Index-Slider - KORRIGIERT
    const zIndexContainer = document.createElement('div');
    zIndexContainer.className = 'slider-container';

    const zIndexLabel = document.createElement('span');
    zIndexLabel.textContent = 'Ebene: ';

    const zIndexValue = document.createElement('span');
    zIndexValue.className = 'slider-value';

    // KORRIGIERT: Verwende Metadaten für aktuellen Z-Index-Wert
    const currentZIndex = layerMetadata[layerId]?.zIndex || layer.getZIndex() || DEFAULT_ZINDEX;
    zIndexValue.textContent = currentZIndex;

    const zIndexSlider = document.createElement('input');
    zIndexSlider.type = 'range';
    zIndexSlider.min = '1900';
    zIndexSlider.max = '2500';
    zIndexSlider.step = '5';
    zIndexSlider.value = currentZIndex; // KORRIGIERT: Verwende currentZIndex

    zIndexSlider.addEventListener('input', () => {
        const zIndex = parseInt(zIndexSlider.value);
        layer.setZIndex(zIndex);

        // KORRIGIERT: Speichere Z-Index in Metadaten
        if (!layerMetadata[layerId]) {
            layerMetadata[layerId] = {};
        }
        layerMetadata[layerId].zIndex = zIndex;

        zIndexValue.textContent = zIndex;
        saveSettings(); // Speichern nach Änderung
    });

    zIndexContainer.appendChild(zIndexLabel);
    zIndexContainer.appendChild(zIndexSlider);
    zIndexContainer.appendChild(zIndexValue);

    container.appendChild(label);
    container.appendChild(opacityContainer);
    container.appendChild(zIndexContainer);

    return container;
}

// Globale Filter-Einstellungen
let globalFilters = {
    brightness: 100,
    contrast: 100,
    saturation: 100,
    sharpness: 100
};

function createGlobalFilterControls() {
    const container = createLayerControl(null, '🎨 Karten Einstellungen', true, 'global-filters');
    const content = container.content;

    // Helligkeit-Slider
    const brightnessContainer = document.createElement('div');
    brightnessContainer.className = 'slider-container global-filter';

    const brightnessLabel = document.createElement('span');
    brightnessLabel.textContent = 'Helligkeit: ';

    const brightnessValue = document.createElement('span');
    brightnessValue.className = 'slider-value';
    brightnessValue.textContent = globalFilters.brightness + '%';

    const brightnessSlider = document.createElement('input');
    brightnessSlider.type = 'range';
    brightnessSlider.min = '0';
    brightnessSlider.max = '300';
    brightnessSlider.step = '1';
    brightnessSlider.value = globalFilters.brightness;

    brightnessSlider.addEventListener('input', () => {
        const value = parseInt(brightnessSlider.value);
        globalFilters.brightness = value;
        brightnessValue.textContent = value + '%';
        updateAllLayerFilters();
        saveGlobalFilters();
    });

    brightnessContainer.appendChild(brightnessLabel);
    brightnessContainer.appendChild(brightnessSlider);
    brightnessContainer.appendChild(brightnessValue);

    // Kontrast-Slider
    const contrastContainer = document.createElement('div');
    contrastContainer.className = 'slider-container global-filter';

    const contrastLabel = document.createElement('span');
    contrastLabel.textContent = 'Kontrast: ';

    const contrastValue = document.createElement('span');
    contrastValue.className = 'slider-value';
    contrastValue.textContent = globalFilters.contrast + '%';

    const contrastSlider = document.createElement('input');
    contrastSlider.type = 'range';
    contrastSlider.min = '0';
    contrastSlider.max = '300';
    contrastSlider.step = '1';
    contrastSlider.value = globalFilters.contrast;

    contrastSlider.addEventListener('input', () => {
        const value = parseInt(contrastSlider.value);
        globalFilters.contrast = value;
        contrastValue.textContent = value + '%';
        updateAllLayerFilters();
        saveGlobalFilters();
    });

    contrastContainer.appendChild(contrastLabel);
    contrastContainer.appendChild(contrastSlider);
    contrastContainer.appendChild(contrastValue);

    // Sättigung-Slider
    const saturationContainer = document.createElement('div');
    saturationContainer.className = 'slider-container global-filter';

    const saturationLabel = document.createElement('span');
    saturationLabel.textContent = 'Sättigung: ';

    const saturationValue = document.createElement('span');
    saturationValue.className = 'slider-value';
    saturationValue.textContent = globalFilters.saturation + '%';

    const saturationSlider = document.createElement('input');
    saturationSlider.type = 'range';
    saturationSlider.min = '0';
    saturationSlider.max = '300';
    saturationSlider.step = '1';
    saturationSlider.value = globalFilters.saturation;

    saturationSlider.addEventListener('input', () => {
        const value = parseInt(saturationSlider.value);
        globalFilters.saturation = value;
        saturationValue.textContent = value + '%';
        updateAllLayerFilters();
        saveGlobalFilters();
    });

    saturationContainer.appendChild(saturationLabel);
    saturationContainer.appendChild(saturationSlider);
    saturationContainer.appendChild(saturationValue);

    // Schärfe-Slider (mit funktionierender Implementierung)
    const sharpnessContainer = document.createElement('div');
    sharpnessContainer.className = 'slider-container global-filter';

    const sharpnessLabel = document.createElement('span');
    sharpnessLabel.textContent = 'Schärfe: ';

    const sharpnessValue = document.createElement('span');
    sharpnessValue.className = 'slider-value';
    sharpnessValue.textContent = globalFilters.sharpness + '%';

    const sharpnessSlider = document.createElement('input');
    sharpnessSlider.type = 'range';
    sharpnessSlider.min = '0';
    sharpnessSlider.max = '300';
    sharpnessSlider.step = '1';
    sharpnessSlider.value = globalFilters.sharpness;

    sharpnessSlider.addEventListener('input', () => {
        const value = parseInt(sharpnessSlider.value);
        globalFilters.sharpness = value;
        sharpnessValue.textContent = value + '%';
        updateAllLayerFilters();
        saveGlobalFilters();
    });

    sharpnessContainer.appendChild(sharpnessLabel);
    sharpnessContainer.appendChild(sharpnessSlider);
    sharpnessContainer.appendChild(sharpnessValue);

    content.appendChild(brightnessContainer);
    content.appendChild(contrastContainer);
    content.appendChild(saturationContainer);
    content.appendChild(sharpnessContainer);

    return container;
}

async function initializeScript() {
    try {
        // Registriere Script-Info
        W.userscripts[SCRIPT_ID] = {
            name: SCRIPT_NAME,
            author: 'Hiwi234',
            version: GM_info.script.version
        };

        // Lade gespeicherte Zustände
        loadCollapsedState();

        // Patche OpenLayers für WMTS Support
        patchOpenLayers();

        // Warte kurz für OpenLayers Patches
        await new Promise(resolve => setTimeout(resolve, 1000));

        // Erstelle alle Layer
        console.log("Creating layers...");
        const wmtsPromises = [];

        Object.keys(layerGroups).forEach(groupKey => {
            const group = layerGroups[groupKey];
            group.layers.forEach(layerConfig => {
                if (layerConfig.type === 'wmts') {
                    wmtsPromises.push(createWMTSLayer(layerConfig));
                } else {
                    createBasicLayer(layerConfig);
                }
            });
        });

        // Warte auf alle WMTS Layer
        await Promise.allSettled(wmtsPromises);

        // Erstelle Sidebar Tab
        const { tabLabel, tabPane } = W.userscripts.registerSidebarTab(SCRIPT_ID);
        tabLabel.textContent = 'Multi Map 🗺️';
        tabLabel.title = 'Erweiterte Karten-Overlays';

        await W.userscripts.waitForElementConnected(tabPane);

        // Erstelle Content Container
        const content = document.createElement('div');
        content.className = 'overlay-tab';

        // Lade globale Filter-Einstellungen
        loadGlobalFilters();

        // Füge alle Gruppen in der gespeicherten Reihenfolge hinzu
        const savedGroupOrder = loadGroupOrder();
        savedGroupOrder.forEach(groupKey => {
            if (layerGroups[groupKey]) {
                const group = layerGroups[groupKey];
                const groupContainer = createLayerControl(null, group.name, true, groupKey);

                group.layers.forEach(layerConfig => {
                    if (layers[layerConfig.id]) {
                        groupContainer.content.appendChild(createLayerControl(layerConfig.id, layerConfig.name));
                    }
                });
                content.appendChild(groupContainer);
            }
        });

        // Füge globale Filter-Kontrollen am Ende hinzu
        content.appendChild(createGlobalFilterControls());

        tabPane.appendChild(content);

        // Füge Reset-Button hinzu
        const resetContainer = document.createElement('div');
        resetContainer.className = 'reset-container';

        const resetButton = document.createElement('button');
        resetButton.textContent = '🔄 Einstellungen zurücksetzen';
        resetButton.className = 'reset-button';
        resetButton.addEventListener('click', () => {
            if (confirm('Alle Einstellungen zurücksetzen?')) {
                localStorage.removeItem(STORAGE_KEY);
                localStorage.removeItem('wme-overlay-global-filters');
                localStorage.removeItem(COLLAPSED_STATE_KEY);
                localStorage.removeItem('wme-overlay-group-order');
                location.reload();
            }
        });

        resetContainer.appendChild(resetButton);
        tabPane.appendChild(resetContainer);

        // Füge erweiterte Styles hinzu
        const style = document.createElement('style');
        style.textContent = `
            .overlay-tab {
                padding: 8px;
                max-height: 70vh;
                overflow-y: auto;
            }

            .layer-group {
                margin-bottom: 15px;
                border: 1px solid #ddd;
                border-radius: 6px;
                overflow: hidden;
            }

            .group-header {
                background: #f5f5f5;
                padding: 8px 12px;
                font-weight: bold;
                border-bottom: 1px solid #ddd;
                display: flex;
                align-items: center;
                gap: 8px;
                transition: background-color 0.2s ease;
                position: relative;
            }

            .group-header:hover {
                background: #e8e8e8;
            }

            .group-drag-handle {
                position: absolute;
                right: 8px;
                top: 50%;
                transform: translateY(-50%);
                cursor: grab;
                color: #999;
                font-size: 14px;
                user-select: none;
                padding: 4px 6px;
                border-radius: 3px;
                transition: all 0.2s ease;
                background: rgba(255,255,255,0.8);
                border: 1px solid #ddd;
            }

            .group-drag-handle:hover {
                background: rgba(0,0,0,0.1);
                color: #666;
                border-color: #999;
            }

            .toggle-button {
                font-size: 12px;
                color: #666;
                transition: transform 0.2s ease;
                display: inline-block;
                width: 16px;
                text-align: center;
            }

            .group-title {
                font-size: 14px;
                flex: 1;
            }

            .group-content {
                transition: all 0.3s ease;
                overflow: hidden;
            }

            .layer-control {
                margin: 0;
                padding: 12px;
                border-bottom: 1px solid #eee;
            }

            .layer-control:last-child {
                border-bottom: none;
            }

            .layer-control label {
                display: block;
                margin-bottom: 8px;
                font-weight: 500;
                cursor: pointer;
            }

            .layer-control input[type="checkbox"] {
                margin-right: 8px;
            }

            .slider-container {
                margin: 6px 0;
                display: flex;
                align-items: center;
            }

            .global-filter {
                padding: 8px 12px;
                border-bottom: 1px solid #eee;
            }

            .global-filter:last-child {
                border-bottom: none;
            }

            .slider-container span {
                display: inline-block;
                width: 90px;
                font-size: 12px;
                color: #666;
            }

            .slider-container input[type="range"] {
                flex: 1;
                margin: 0 8px;
            }

            .slider-value {
                width: 40px !important;
                text-align: right;
                font-weight: 500;
                color: #333 !important;
            }

            .reset-container {
                margin-top: 15px;
                padding: 15px;
                border-top: 1px solid #ddd;
                text-align: center;
            }

            .reset-button {
                background: #f44336;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 12px;
                transition: background 0.2s;
            }

            .reset-button:hover {
                background: #d32f2f;
            }

            /* Drag & Drop Styles für Gruppen */

            .layer-group[draggable="true"] {
                transition: all 0.2s ease;
            }

            .layer-group.group-dragging {
                opacity: 0.6;
                transform: rotate(1deg);
                box-shadow: 0 8px 25px rgba(0,0,0,0.3);
                z-index: 1000;
                border: 2px dashed #666;
            }

            .layer-group.group-drop-target-above {
                border-top: 4px solid #4CAF50;
                margin-top: 4px;
            }

            .layer-group.group-drop-target-below {
                border-bottom: 4px solid #4CAF50;
                margin-bottom: 4px;
            }

            .group-drag-handle[draggable="true"]:active {
                cursor: grabbing;
            }
        `;
        document.head.appendChild(style);

        console.log(SCRIPT_NAME + ': Erfolgreich initialisiert mit', Object.keys(layers).length, 'Layern');

        // Zeige Update-Benachrichtigung
        showUpdateNotification();

        // Initiale Anwendung der globalen Filter
        setTimeout(() => {
            updateAllLayerFilters();
        }, 2000);

    } catch (error) {
        console.error(SCRIPT_NAME + ': Fehler bei der Initialisierung:', error);
    }
}

// Starte Initialisierung wenn WME bereit ist
if (W?.userscripts?.state.isReady) {
    initializeScript();
} else {
    document.addEventListener('wme-ready', initializeScript, { once: true });
}

})();