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://gf.qytechs.cn/de/users/863740-horst-wittlich
// @version 2025.06.09
// @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 www.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 = 2000;
const STORAGE_KEY = 'wme-overlay-settings';

// Layer storage
const layers = {};

// Settings Management
function saveSettings() {
    const settings = {
        version: GM_info.script.version,
        layers: {}
    };
    
    Object.keys(layers).forEach(layerId => {
        const layer = layers[layerId];
        if (layer) {
            settings.layers[layerId] = {
                visible: layer.getVisibility(),
                opacity: layer.opacity || DEFAULT_OPACITY,
                zIndex: layer.getZIndex() || DEFAULT_ZINDEX
            };
        }
    });
    
    localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
    console.log('Settings gespeichert:', settings);
}

function saveGlobalFilters() {
    localStorage.setItem('wme-overlay-global-filters', JSON.stringify(globalFilters));
}

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) {
        // Sichtbarkeit anwenden
        layer.setVisibility(layerSettings.visible || false);
        // Transparenz anwenden
        layer.setOpacity(layerSettings.opacity || DEFAULT_OPACITY);
        // Z-Index anwenden
        layer.setZIndex(layerSettings.zIndex || DEFAULT_ZINDEX);
        
        console.log(`Settings für ${layerId} angewendet:`, layerSettings);
    }
}

// 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",
            }
        ]
    },
    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
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;
        
        // Gespeicherte Einstellungen anwenden
        applySettingsToLayer(config.id, layer);
    }
}

// WMTS Layer erstellen
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;
                        
                        // 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>🎨 Neue Overlay-Einstellungen!</h3>
                <div class="new-features">
                    <div class="feature-group">
                        <h4>🎛️ Neue 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>🔧 Basis Layer:</h4>
                        <ul>
                            <li>✅ TopPlus WMS</li>
                            <li>✅ TopPlus Grau</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>🇩🇪 GeoOverlays DE:</h4>
                        <ul>
                            <li>✅ Basemap DE</li>
                            <li>✅ Basemap.de Grau</li>
                            <li>✅ GeoPortal NRW</li>
                            <li>✅ GeoPortal NRW Overlay</li>
                            <li>✅ GeoPortal BY</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>🇦🇹 GeoOverlays AT:</h4>
                        <ul>
                            <li>✅ Basemap AT</li>
                            <li>✅ Overlay AT</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>🇨🇭 GeoOverlays CH:</h4>
                        <ul>
                            <li>✅ Strassenkarte</li>
                            <li>✅ Basisnetz</li>
                            <li>✅ Luftbild</li>
                        </ul>
                    </div>
                    <div class="feature-group">
                        <h4>🌍 Nicht Amtliche Karten:</h4>
                        <ul>
                            <li>✅ OpenStreetMap Standard</li>
                            <li>✅ OSM Dark Matter</li>
                        </ul>
                    </div>
                </div>
                <div class="feature-info">
                    <p><strong>🎨 Individuelle Anpassung!</strong></p>
                    <p>Jede Karte kann jetzt individuell in Helligkeit, Kontrast, Sättigung und Schärfe angepasst werden!</p>
                </div>
            </div>
            <div class="update-actions">
                <button class="btn-primary" id="explore-maps-btn">
                    🚀 Karten erkunden!
                </button>
                <button class="btn-secondary" id="close-popup-btn">
                    Später
                </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('#close-popup-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);
        }
    });
}

function createLayerControl(layerId, name, isGroup = false) {
    const container = document.createElement('div');
    container.className = isGroup ? 'layer-group' : 'layer-control';
    
    if (isGroup) {
        const header = document.createElement('div');
        header.className = 'group-header';
        header.innerHTML = `<span class="group-title">${name}</span>`;
        container.appendChild(header);
        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
    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 = '1';
    opacitySlider.step = '0.1';
    opacitySlider.value = layer.opacity || DEFAULT_OPACITY;
    opacitySlider.addEventListener('input', () => {
        const value = parseFloat(opacitySlider.value);
        layer.setOpacity(value);
        opacityValue.textContent = Math.round(value * 100) + '%';
        saveSettings(); // Speichern nach Änderung
    });
    
    opacityContainer.appendChild(opacityLabel);
    opacityContainer.appendChild(opacitySlider);
    opacityContainer.appendChild(opacityValue);
    
    // Z-Index-Slider
    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';
    zIndexValue.textContent = layer.getZIndex() || DEFAULT_ZINDEX;
    
    const zIndexSlider = document.createElement('input');
    zIndexSlider.type = 'range';
    zIndexSlider.min = '5';
    zIndexSlider.max = '2500';
    zIndexSlider.step = '5';
    zIndexSlider.value = layer.getZIndex() || DEFAULT_ZINDEX;
    zIndexSlider.addEventListener('input', () => {
        const zIndex = parseInt(zIndexSlider.value);
        layer.setZIndex(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 = document.createElement('div');
    container.className = 'layer-group';
    
    const header = document.createElement('div');
    header.className = 'group-header';
    header.innerHTML = `<span class="group-title">🎨 Karten Einstellungen</span>`;
    container.appendChild(header);
    
    // 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);
    
    container.appendChild(brightnessContainer);
    container.appendChild(contrastContainer);
    container.appendChild(saturationContainer);
    container.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
        };
        
        // 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 gewünschten Reihenfolge hinzu
        const groupOrder = ['basic', 'germany', 'austria', 'switzerland', 'unofficial'];
        
        groupOrder.forEach(groupKey => {
            if (layerGroups[groupKey]) {
                const group = layerGroups[groupKey];
                const groupContainer = createLayerControl(null, group.name, true);
                
                group.layers.forEach(layerConfig => {
                    if (layers[layerConfig.id]) {
                        groupContainer.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');
                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;
            }
            
            .group-title {
                font-size: 14px;
            }
            
            .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;
            }
        `;
        
        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 });
}

})();

QingJ © 2025

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