// ==UserScript==
// @name WME Multi Map Overlay
// @namespace https://greasyfork.org/de/users/863740-horst-wittlich
// @version 2025.06.12
// @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 = 2010;
const STORAGE_KEY = 'wme-overlay-settings';
const COLLAPSED_STATE_KEY = 'wme-overlay-collapsed-groups';
// Layer storage
const layers = {};
// Collapsed state storage
let collapsedGroups = {};
// 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 saveCollapsedState() {
localStorage.setItem(COLLAPSED_STATE_KEY, JSON.stringify(collapsedGroups));
}
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() {
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>📁 Zusammenklappbare Kategorien:</h4>
<ul>
<li>✅ Alle Kategorien können ein-/ausgeklappt werden</li>
<li>✅ Zustand wird automatisch gespeichert</li>
<li>✅ Übersichtlichere Navigation</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>
<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, 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
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 = '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);
opacityValue.textContent = opacitySlider.value + '%';
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 = '1900';
zIndexSlider.max = '2600';
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 = 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 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, 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);
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;
}
.group-header:hover {
background: #e8e8e8;
}
.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;
}
`;
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 });
}
})();