您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
WME PTSM für Deutschland Österreich Schweiz
- // ==UserScript==
- // @name WME Permalink to several Maps DACH
- // @description WME PTSM für Deutschland Österreich Schweiz
- // @namespace https://greasyfork.org/de/users/863740-horst-wittlich
- // @version 2025.06.12
- // @match https://*.waze.com/editor*
- // @match https://*.waze.com/*/editor*
- // @match https://beta.waze.com/editor*
- // @match https://beta.waze.com/*/editor*
- // @icon https://i.ibb.co/ckSvk59/waze-icon.png
- // @grant none
- // @license MIT
- // ==/UserScript==
- /* global OpenLayers, W, proj4 */
- var ptsmVersion = '2025.06.12';
- var ptsmInitialized = false;
- var ptsmUpdateKey = 'wme-ptsm-update-shown-' + ptsmVersion;
- // Einstellungen laden/speichern - MUSS VOR anderen Funktionen definiert werden
- function loadSettings() {
- try {
- const saved = localStorage.getItem('wme-ptsm-settings');
- return saved ? JSON.parse(saved) : { freePosition: false };
- } catch (e) {
- return { freePosition: false };
- }
- }
- function saveSettings(settings) {
- try {
- localStorage.setItem('wme-ptsm-settings', JSON.stringify(settings));
- } catch (e) {
- console.log('PTSM: localStorage not available');
- }
- }
- function saveDropdownStates() {
- const states = {};
- document.querySelectorAll('.ptsm-category').forEach(category => {
- const className = category.className.match(/ptsm-category-(\w+)/);
- if (className) {
- states[className[1]] = category.classList.contains('open');
- }
- });
- try {
- localStorage.setItem('wme-ptsm-dropdown-states', JSON.stringify(states));
- } catch (e) {
- console.log('PTSM: localStorage not available');
- }
- }
- function loadDropdownStates() {
- try {
- const saved = localStorage.getItem('wme-ptsm-dropdown-states');
- return saved ? JSON.parse(saved) : {};
- } catch (e) {
- return {};
- }
- }
- function saveCompactMode(isCompact) {
- try {
- localStorage.setItem('wme-ptsm-compact-mode', isCompact ? 'true' : 'false');
- } catch (e) {
- console.log('PTSM: localStorage not available');
- }
- }
- function loadCompactMode() {
- try {
- const saved = localStorage.getItem('wme-ptsm-compact-mode');
- return saved === 'true';
- } catch (e) {
- return false;
- }
- }
- // HYBRIDE Drag & Drop Funktionalität - Desktop-weit ODER Container-intern
- function enableDragAndDrop() {
- const container = document.querySelector('.ptsm-container');
- const divs = ['.ptsm-category-allgem', '.ptsm-category-baustell', '.ptsm-category-blitzer', '.ptsm-category-bilder', '.ptsm-category-geoportal', '.ptsm-category-misc', '.ptsm-category-settings'];
- // Gespeicherte Desktop-Positionen laden
- function loadPositions() {
- try {
- const saved = localStorage.getItem('wme-ptsm-drag-positions');
- return saved ? JSON.parse(saved) : {};
- } catch (e) {
- return {};
- }
- }
- // Desktop-Positionen speichern
- function savePositions() {
- const positions = {};
- divs.forEach(selector => {
- const element = document.querySelector(selector);
- if (element && element.style.position === 'fixed') {
- positions[selector] = {
- left: element.style.left,
- top: element.style.top,
- zIndex: element.style.zIndex
- };
- }
- });
- try {
- localStorage.setItem('wme-ptsm-drag-positions', JSON.stringify(positions));
- } catch (e) {
- console.log('PTSM: localStorage not available');
- }
- }
- // Container-Reihenfolge speichern
- function saveCategoryOrder() {
- if (!container) return;
- const order = Array.from(container.querySelectorAll('.ptsm-category')).map(cat => {
- const className = cat.className.match(/ptsm-category-(\w+)/);
- return className ? className[1] : null;
- }).filter(Boolean);
- try {
- localStorage.setItem('wme-ptsm-category-order', JSON.stringify(order));
- } catch (e) {
- console.log('PTSM: localStorage not available');
- }
- }
- // Gespeicherte Desktop-Positionen wiederherstellen
- function restorePositions() {
- const positions = loadPositions();
- Object.keys(positions).forEach(selector => {
- const element = document.querySelector(selector);
- if (element) {
- const pos = positions[selector];
- element.style.position = 'fixed';
- element.style.left = pos.left;
- element.style.top = pos.top;
- element.style.zIndex = pos.zIndex || '10000';
- element.style.width = '280px';
- element.style.maxWidth = '280px';
- // Element an document.body anhängen für Tab-Unabhängigkeit
- if (element.parentElement !== document.body) {
- element.setAttribute('data-original-parent', 'container');
- document.body.appendChild(element);
- }
- }
- });
- }
- divs.forEach(selector => {
- const element = document.querySelector(selector);
- if (!element) return;
- const header = element.querySelector('.ptsm-category-header');
- if (!header || header.hasAttribute('data-drag-enabled')) return;
- header.setAttribute('data-drag-enabled', 'true');
- let isDragging = false;
- let dragStarted = false;
- let startX, startY, startTime;
- let originalPosition = { left: '', top: '', position: '', width: '', zIndex: '' };
- let placeholder = null;
- let clickBlocked = false; // Neue Variable für Click-Blockierung
- // Ursprüngliche Position merken
- function saveOriginalPosition() {
- originalPosition = {
- left: element.style.left,
- top: element.style.top,
- position: element.style.position,
- width: element.style.width,
- zIndex: element.style.zIndex
- };
- }
- header.addEventListener('mousedown', function(e) {
- // Nur Linksklick
- if (e.button !== 0) return;
- isDragging = true;
- dragStarted = false;
- clickBlocked = false;
- startX = e.clientX;
- startY = e.clientY;
- startTime = Date.now();
- saveOriginalPosition();
- // WICHTIG: preventDefault() IMMER bei mousedown um Click-Konflikte zu vermeiden
- e.preventDefault();
- e.stopPropagation();
- });
- document.addEventListener('mousemove', function(e) {
- if (!isDragging) return;
- const deltaX = Math.abs(e.clientX - startX);
- const deltaY = Math.abs(e.clientY - startY);
- const deltaTime = Date.now() - startTime;
- // Drag erst nach Mindestbewegung oder Zeit starten
- if (!dragStarted && (deltaX > 5 || deltaY > 5 || deltaTime > 200)) {
- dragStarted = true;
- clickBlocked = true; // Click blockieren sobald Drag gestartet wird
- const settings = loadSettings();
- if (settings.freePosition) {
- // DESKTOP-WEITES DRAG
- const rect = element.getBoundingClientRect();
- element.style.position = 'fixed';
- element.style.left = rect.left + 'px';
- element.style.top = rect.top + 'px';
- element.style.zIndex = '10000';
- element.style.width = '280px';
- element.style.maxWidth = '280px';
- element.style.opacity = '0.8';
- element.style.boxShadow = '0 8px 25px rgba(0,0,0,0.3)';
- element.style.transform = 'rotate(1deg)';
- // Element an document.body anhängen für Tab-Unabhängigkeit
- if (element.parentElement !== document.body) {
- element.setAttribute('data-original-parent', 'container');
- document.body.appendChild(element);
- }
- } else {
- // CONTAINER-INTERNES DRAG
- if (!container) return;
- // Element aus normalem Flow nehmen
- const rect = element.getBoundingClientRect();
- // Placeholder erstellen
- placeholder = document.createElement('div');
- placeholder.className = 'ptsm-drag-placeholder';
- placeholder.style.cssText = `
- height: ${element.offsetHeight}px;
- background: #f0f0f0;
- border: 2px dashed #ccc;
- border-radius: 6px;
- margin-bottom: 8px;
- opacity: 0.5;
- transition: all 0.2s ease;
- `;
- // Container-Drag-Styling
- element.style.position = 'fixed';
- element.style.left = rect.left + 'px';
- element.style.top = rect.top + 'px';
- element.style.width = '280px';
- element.style.maxWidth = '280px';
- element.style.opacity = '0.8';
- element.style.transform = 'rotate(1deg)';
- element.style.boxShadow = '0 8px 25px rgba(0,0,0,0.3)';
- element.style.zIndex = '10000';
- element.style.pointerEvents = 'none';
- // Placeholder an der ursprünglichen Position einfügen
- element.parentNode.insertBefore(placeholder, element);
- }
- // Header für Drag-Zeit blockieren
- header.style.pointerEvents = 'none';
- }
- if (dragStarted) {
- const settings = loadSettings();
- if (settings.freePosition) {
- // DESKTOP-WEITE BEWEGUNG
- const newLeft = Math.max(0, Math.min(e.clientX - startX + parseInt(element.style.left), window.innerWidth - 300));
- const newTop = Math.max(0, Math.min(e.clientY - startY + parseInt(element.style.top), window.innerHeight - 100));
- element.style.left = newLeft + 'px';
- element.style.top = newTop + 'px';
- // Update startX/Y für smooth dragging
- startX = e.clientX;
- startY = e.clientY;
- } else {
- // CONTAINER-INTERNE REORDERING
- if (!container || !placeholder) return;
- // Vertikale Mausbewegung für bessere Sortierung
- const containerRect = container.getBoundingClientRect();
- const mouseY = e.clientY;
- // Alle Kategorien außer dem gezogenen Element
- const allCategories = Array.from(container.querySelectorAll('.ptsm-category')).filter(cat => cat !== element);
- let insertBefore = null;
- let minDistance = Infinity;
- // Finde das Element mit der geringsten Y-Distanz oberhalb der Maus
- for (let otherCategory of allCategories) {
- if (otherCategory === placeholder) continue;
- const otherRect = otherCategory.getBoundingClientRect();
- const otherCenterY = otherRect.top + otherRect.height / 2;
- if (mouseY < otherCenterY) {
- const distance = otherCenterY - mouseY;
- if (distance < minDistance) {
- minDistance = distance;
- insertBefore = otherCategory;
- }
- }
- }
- // Placeholder neu positionieren
- if (insertBefore) {
- container.insertBefore(placeholder, insertBefore);
- } else {
- // Ans Ende setzen wenn keine Kategorie oberhalb gefunden
- container.appendChild(placeholder);
- }
- // Element visuell an Mausposition folgen lassen (nur Y-Achse im Container)
- const constrainedY = Math.max(containerRect.top, Math.min(mouseY - 20, containerRect.bottom - 50));
- element.style.top = constrainedY + 'px';
- }
- }
- });
- document.addEventListener('mouseup', function(e) {
- if (!isDragging) return;
- isDragging = false;
- if (dragStarted) {
- dragStarted = false;
- const settings = loadSettings();
- if (settings.freePosition) {
- // DESKTOP-DRAG beenden
- element.style.opacity = '1';
- element.style.boxShadow = '0 1px 4px rgba(0,0,0,0.08)';
- element.style.transform = 'none';
- element.style.zIndex = '10000'; // Höher für Desktop-Modus
- // Element dauerhaft an document.body lassen für Tab-Unabhängigkeit
- if (element.parentElement !== document.body) {
- element.setAttribute('data-original-parent', 'container');
- document.body.appendChild(element);
- }
- // Desktop-Position speichern
- savePositions();
- } else {
- // CONTAINER-DRAG beenden
- if (placeholder && placeholder.parentNode && container) {
- // Element an Placeholder-Position einfügen
- placeholder.parentNode.insertBefore(element, placeholder);
- placeholder.remove();
- // Container-Styling KOMPLETT zurücksetzen
- element.style.position = '';
- element.style.left = '';
- element.style.top = '';
- element.style.width = '';
- element.style.maxWidth = '';
- element.style.opacity = '';
- element.style.transform = '';
- element.style.boxShadow = '';
- element.style.zIndex = '';
- element.style.pointerEvents = '';
- // Container-Reihenfolge speichern
- saveCategoryOrder();
- } else {
- // Fallback: Element zurück zur ursprünglichen Position
- element.style.position = originalPosition.position;
- element.style.left = originalPosition.left;
- element.style.top = originalPosition.top;
- element.style.width = originalPosition.width;
- element.style.zIndex = originalPosition.zIndex;
- element.style.opacity = '';
- element.style.transform = '';
- element.style.boxShadow = '';
- element.style.pointerEvents = '';
- if (placeholder && placeholder.parentNode) {
- placeholder.remove();
- }
- }
- }
- placeholder = null;
- // Header wieder aktivieren (mit Verzögerung um Click zu blockieren)
- setTimeout(() => {
- header.style.pointerEvents = '';
- // Click-Blockierung nach kurzer Zeit aufheben
- setTimeout(() => {
- clickBlocked = false;
- }, 100);
- }, 50);
- // Event verhindern
- e.preventDefault();
- e.stopPropagation();
- } else {
- // Kein Drag - ursprüngliche Position wiederherstellen + Cleanup
- element.style.left = originalPosition.left;
- element.style.top = originalPosition.top;
- element.style.position = originalPosition.position;
- element.style.width = originalPosition.width;
- element.style.zIndex = originalPosition.zIndex;
- element.style.opacity = '';
- element.style.transform = '';
- element.style.boxShadow = '';
- element.style.pointerEvents = '';
- // Placeholder entfernen falls vorhanden
- if (placeholder && placeholder.parentNode) {
- placeholder.remove();
- placeholder = null;
- }
- // Header wieder aktivieren
- header.style.pointerEvents = '';
- // Kurze Verzögerung bevor Click wieder erlaubt wird
- setTimeout(() => {
- clickBlocked = false;
- }, 10);
- }
- });
- // NEUER Click Event Handler mit Blockierung-Check
- header.addEventListener('click', function(e) {
- // Click blockieren wenn gerade gedraggt wurde oder wird
- if (clickBlocked || isDragging || dragStarted) {
- e.preventDefault();
- e.stopPropagation();
- e.stopImmediatePropagation();
- return false;
- }
- // Normaler Toggle nur wenn kein Drag
- const category = element;
- category.classList.toggle('open');
- setTimeout(saveDropdownStates, 100);
- }, true); // useCapture = true für höhere Priorität
- // Doppelklick zum Zurücksetzen (nur bei Desktop-Modus)
- header.addEventListener('dblclick', function(e) {
- // Click blockieren
- e.preventDefault();
- e.stopPropagation();
- const settings = loadSettings();
- if (settings.freePosition) {
- // Desktop-Position zurücksetzen - KORREKTUR
- element.style.position = '';
- element.style.left = '';
- element.style.top = '';
- element.style.zIndex = '';
- element.style.width = '';
- element.style.maxWidth = '';
- element.style.transform = '';
- element.style.opacity = '';
- element.style.boxShadow = '';
- // WICHTIG: Element zurück in den ursprünglichen Container bringen
- const container = document.querySelector('.ptsm-container');
- if (container && element.parentElement !== container) {
- // Element wieder in Container einfügen
- container.appendChild(element);
- // Falls es eine gespeicherte Reihenfolge gibt, diese wiederherstellen
- setTimeout(() => {
- applyCategoryOrder();
- }, 50);
- }
- // Position aus localStorage entfernen
- const positions = loadPositions();
- delete positions[selector];
- try {
- localStorage.setItem('wme-ptsm-drag-positions', JSON.stringify(positions));
- } catch (e) {
- console.log('PTSM: localStorage not available');
- }
- }
- });
- // Cursor-Stil setzen
- function updateCursor() {
- const settings = loadSettings();
- if (settings.freePosition) {
- header.style.cursor = 'move';
- header.title = 'Ziehen zum Verschieben (Doppelklick zum Zurücksetzen)';
- } else {
- header.style.cursor = 'move';
- header.title = 'Ziehen zum Umsortieren';
- }
- }
- updateCursor();
- // Event Listener für Settings-Änderungen
- document.addEventListener('ptsm-settings-changed', updateCursor);
- });
- // Gespeicherte Desktop-Positionen beim Start wiederherstellen (nur wenn freie Position aktiv)
- const settings = loadSettings();
- if (settings.freePosition) {
- setTimeout(restorePositions, 100);
- }
- }
- // Kategorie-Reihenfolge speichern und laden
- function saveCategoryOrder() {
- const container = document.querySelector('.ptsm-container');
- if (!container) return;
- const order = Array.from(container.querySelectorAll('.ptsm-category')).map(cat => {
- const className = cat.className.match(/ptsm-category-(\w+)/);
- return className ? className[1] : null;
- }).filter(Boolean);
- try {
- localStorage.setItem('wme-ptsm-category-order', JSON.stringify(order));
- } catch (e) {
- console.log('PTSM: localStorage not available');
- }
- }
- function loadCategoryOrder() {
- try {
- const saved = localStorage.getItem('wme-ptsm-category-order');
- return saved ? JSON.parse(saved) : null;
- } catch (e) {
- return null;
- }
- }
- function applyCategoryOrder() {
- const savedOrder = loadCategoryOrder();
- if (!savedOrder) return;
- const container = document.querySelector('.ptsm-container');
- if (!container) return;
- // Kategorien nach gespeicherter Reihenfolge sortieren
- savedOrder.forEach(categoryKey => {
- const category = container.querySelector('.ptsm-category-' + categoryKey);
- if (category) {
- container.appendChild(category);
- }
- });
- }
- function showUpdatePopup() {
- try {
- // Prüfen ob Update-Info bereits angezeigt wurde
- if (localStorage.getItem(ptsmUpdateKey) === 'true') {
- return;
- }
- } catch (e) {
- // Falls localStorage nicht verfügbar, trotzdem anzeigen
- }
- // Popup erstellen
- var overlay = document.createElement('div');
- overlay.style.cssText = `
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- z-index: 999999;
- display: flex;
- align-items: center;
- justify-content: center;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
- `;
- var popup = document.createElement('div');
- popup.style.cssText = `
- background: white;
- border-radius: 12px;
- padding: 24px;
- max-width: 400px;
- margin: 20px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
- text-align: center;
- position: relative;
- `;
- popup.innerHTML = `
- <div style="font-size: 24px; margin-bottom: 16px;">🎉 PTSM DACH Update! 🗺️</div>
- <div style="font-size: 18px; font-weight: bold; color: #007bff; margin-bottom: 16px;">
- Version ${ptsmVersion}
- </div>
- <div style="text-align: left; margin-bottom: 20px; line-height: 1.6;">
- <div style="font-size: 16px; font-weight: bold; margin-bottom: 12px; color: #333;">
- ✨ Was ist neu:
- </div>
- <div style="margin-bottom: 8px;">
- 🔧 <strong>Neu Drag & Drop Funktionalität</strong><br>Sortiere wie es dir gefällt: Doppelklick auf die Überschrift = zurück zur ursprünglichen Position.<br>Neuer Modus: <b>Major Tom</b> meldet <u>Völlig losgelöst.</u> Tabs haben bei bedarf nun einen Fliegenden Modus. Alle Einstellungen werden lokal gespeichert und können bei bedarf wieder Rückgängig gemacht werden.
- </div>
- </div>
- <button id="ptsm-close-popup" style="
- background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
- color: white;
- border: none;
- border-radius: 8px;
- padding: 12px 24px;
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s ease;
- ">
- 🚀 Los geht's!
- </button>
- `;
- overlay.appendChild(popup);
- document.body.appendChild(overlay);
- // Close button event
- var closeBtn = popup.querySelector('#ptsm-close-popup');
- closeBtn.addEventListener('click', function() {
- overlay.remove();
- try {
- localStorage.setItem(ptsmUpdateKey, 'true');
- } catch (e) {
- // localStorage not available
- }
- });
- closeBtn.addEventListener('mouseover', function() {
- this.style.transform = 'translateY(-2px)';
- this.style.boxShadow = '0 4px 12px rgba(0, 123, 255, 0.3)';
- });
- closeBtn.addEventListener('mouseout', function() {
- this.style.transform = 'translateY(0)';
- this.style.boxShadow = 'none';
- });
- // Schließen bei Klick auf Overlay
- overlay.addEventListener('click', function(e) {
- if (e.target === overlay) {
- overlay.remove();
- try {
- localStorage.setItem(ptsmUpdateKey, 'true');
- } catch (e) {
- // localStorage not available
- }
- }
- });
- }
- function getCenterZoom() {
- var map = W.map.getOLMap();
- var zoom = map.getZoom();
- var center = map.getCenter().transform(new OpenLayers.Projection('EPSG:900913'), new OpenLayers.Projection('EPSG:4326'));
- center.zoom = zoom;
- return center;
- }
- function createCategory(title, className, defaultOpen = false) {
- const savedStates = loadDropdownStates();
- const categoryKey = className.replace('ptsm-category-', '');
- const isOpen = savedStates.hasOwnProperty(categoryKey) ? savedStates[categoryKey] : defaultOpen;
- var category = document.createElement('div');
- category.className = 'ptsm-category ' + className + (isOpen ? ' open' : '');
- var header = document.createElement('button');
- header.className = 'ptsm-category-header';
- header.innerHTML = title + '<div class="ptsm-dropdown-arrow"></div>';
- var content = document.createElement('div');
- content.className = 'ptsm-category-content';
- // ENTFERNT: Der alte click handler wird durch den neuen in enableDragAndDrop ersetzt
- category.appendChild(header);
- category.appendChild(content);
- return { category, content };
- }
- function createMapButton(text, className, clickHandler) {
- var btn = document.createElement('button');
- btn.textContent = text;
- btn.className = 'ptsm-map-btn ' + className;
- btn.addEventListener('click', clickHandler);
- return btn;
- }
- function createCompactButton() {
- var btn = document.createElement('button');
- btn.className = 'ptsm-compact-btn';
- btn.innerHTML = '🔧 Kompakt-Modus';
- const isCompact = loadCompactMode();
- if (isCompact) {
- btn.innerHTML = '🔧 Normal-Modus';
- btn.classList.add('active');
- }
- btn.addEventListener('click', function() {
- const container = document.querySelector('.ptsm-container');
- const isCurrentlyCompact = container.classList.contains('compact');
- if (isCurrentlyCompact) {
- container.classList.remove('compact');
- btn.innerHTML = '🔧 Kompakt-Modus';
- btn.classList.remove('active');
- saveCompactMode(false);
- } else {
- container.classList.add('compact');
- btn.innerHTML = '🔧 Normal-Modus';
- btn.classList.add('active');
- saveCompactMode(true);
- }
- });
- return btn;
- }
- function addButtons() {
- if (ptsmInitialized) {
- return;
- }
- if (!document.getElementById('user-info')) {
- setTimeout(addButtons, 500);
- return;
- }
- if (!W.loginManager.user) {
- if (!ptsmInitialized) {
- W.loginManager.events.register('login', null, function() {
- if (!ptsmInitialized) addButtons();
- });
- W.loginManager.events.register('loginStatus', null, function() {
- if (!ptsmInitialized) addButtons();
- });
- }
- if (!W.loginManager.user) {
- return;
- }
- }
- ptsmInitialized = true;
- if (typeof proj4 === "undefined") {
- var script = document.createElement('script');
- script.src = 'https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.4.4/proj4.js';
- document.head.appendChild(script);
- }
- // Add CSS
- var style = document.createElement('style');
- style.textContent = `
- .ptsm-container {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
- padding: 8px;
- background: #fff;
- font-size: 12px;
- position: relative;
- overflow: hidden;
- }
- .ptsm-category {
- margin-bottom: 8px;
- border-radius: 6px;
- overflow: hidden;
- background: #fff;
- box-shadow: 0 1px 4px rgba(0,0,0,0.08);
- border: 1px solid #e1e5e9;
- position: relative;
- }
- .ptsm-category-header {
- width: 100%;
- padding: 8px 12px;
- background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
- color: white;
- border: none;
- cursor: pointer;
- font-weight: 600;
- font-size: 11px;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- transition: all 0.2s ease;
- user-select: none;
- }
- .ptsm-category-header:hover {
- background: linear-gradient(135deg, #5a6268 0%, #343a40 100%);
- }
- .ptsm-dropdown-arrow {
- width: 0;
- height: 0;
- border-left: 4px solid transparent;
- border-right: 4px solid transparent;
- border-top: 6px solid white;
- transition: transform 0.2s ease;
- }
- .ptsm-category.open .ptsm-dropdown-arrow {
- transform: rotate(180deg);
- }
- .ptsm-category-content {
- max-height: 0;
- overflow: hidden;
- padding: 0 8px;
- transition: max-height 0.3s ease, padding 0.2s ease;
- }
- .ptsm-category.open .ptsm-category-content {
- max-height: 500px;
- padding: 8px;
- }
- .ptsm-map-btn {
- width: calc(33.333% - 4px);
- height: 28px;
- margin: 2px;
- padding: 4px 6px 4px 24px;
- background: #f8f9fa;
- border: 1px solid #dee2e6;
- border-radius: 4px;
- font-size: 10px;
- font-weight: 500;
- color: #495057;
- cursor: pointer;
- position: relative;
- display: inline-flex;
- align-items: center;
- justify-content: flex-start;
- transition: all 0.15s ease;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- }
- .ptsm-map-btn::before {
- content: '';
- position: absolute;
- left: 4px;
- top: 50%;
- transform: translateY(-50%);
- width: 12px;
- height: 12px;
- background-size: contain;
- background-repeat: no-repeat;
- background-position: center;
- flex-shrink: 0;
- }
- .ptsm-map-btn:hover {
- transform: translateY(-1px);
- background: linear-gradient(145deg, #ffffff 0%, #f1f3f4 100%);
- border-color: #007bff;
- color: #0056b3;
- box-shadow: 0 2px 8px rgba(0, 123, 255, 0.12);
- }
- .ptsm-compact-btn {
- width: calc(100% - 4px);
- height: 32px;
- margin: 2px;
- padding: 8px 12px;
- background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%);
- border: none;
- border-radius: 4px;
- font-size: 11px;
- font-weight: 600;
- color: white;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.15s ease;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
- text-transform: uppercase;
- letter-spacing: 0.5px;
- }
- .ptsm-compact-btn:hover {
- transform: translateY(-1px);
- background: linear-gradient(135deg, #1e7e34 0%, #155724 100%);
- box-shadow: 0 2px 8px rgba(40, 167, 69, 0.25);
- }
- .ptsm-compact-btn.active {
- background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
- }
- .ptsm-compact-btn.active:hover {
- background: linear-gradient(135deg, #c82333 0%, #a02834 100%);
- box-shadow: 0 2px 8px rgba(220, 53, 69, 0.25);
- }
- /* Kompakt-Modus Styles */
- .ptsm-container.compact .ptsm-category {
- margin-bottom: 4px;
- }
- .ptsm-container.compact .ptsm-category-header {
- padding: 6px 10px;
- font-size: 10px;
- }
- .ptsm-container.compact .ptsm-category.open .ptsm-category-content {
- padding: 4px;
- }
- .ptsm-container.compact .ptsm-map-btn {
- width: calc(33.333% - 3px);
- height: 24px;
- margin: 1.5px;
- padding: 3px 5px 3px 20px;
- font-size: 9px;
- }
- .ptsm-container.compact .ptsm-map-btn::before {
- width: 10px;
- height: 10px;
- left: 3px;
- }
- .ptsm-container.compact .ptsm-compact-btn {
- height: 28px;
- font-size: 10px;
- padding: 6px 10px;
- }
- /* Drag Placeholder */
- .ptsm-drag-placeholder {
- background: #f0f0f0;
- border: 2px dashed #ccc;
- border-radius: 6px;
- margin-bottom: 8px;
- opacity: 0.5;
- }
- /* Category colors */
- .ptsm-category-allgem .ptsm-category-header { background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); }
- .ptsm-category-allgem .ptsm-category-header:hover { background: linear-gradient(135deg, #0056b3 0%, #004085 100%); }
- .ptsm-category-baustell .ptsm-category-header { background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%); }
- .ptsm-category-baustell .ptsm-category-header:hover { background: linear-gradient(135deg, #1e7e34 0%, #155724 100%); }
- .ptsm-category-blitzer .ptsm-category-header { background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); }
- .ptsm-category-blitzer .ptsm-category-header:hover { background: linear-gradient(135deg, #c82333 0%, #a02834 100%); }
- .ptsm-category-bilder .ptsm-category-header { background: linear-gradient(135deg, #17a2b8 0%, #138496 100%); }
- .ptsm-category-bilder .ptsm-category-header:hover { background: linear-gradient(135deg, #138496 0%, #0f6674 100%); }
- .ptsm-category-geoportal .ptsm-category-header { background: linear-gradient(135deg, #6f42c1 0%, #59359a 100%); }
- .ptsm-category-geoportal .ptsm-category-header:hover { background: linear-gradient(135deg, #59359a 0%, #4c2c85 100%); }
- .ptsm-category-misc .ptsm-category-header { background: linear-gradient(135deg, #fd7e14 0%, #e65100 100%); }
- .ptsm-category-misc .ptsm-category-header:hover { background: linear-gradient(135deg, #e65100 0%, #bf360c 100%); }
- .ptsm-category-settings .ptsm-category-header { background: linear-gradient(135deg, #6c757d 0%, #495057 100%); }
- .ptsm-category-settings .ptsm-category-header:hover { background: linear-gradient(135deg, #5a6268 0%, #343a40 100%); }
- /* Icons */
- .ptsm-google::before { background-image: url(https://i.ibb.co/d0zx6Pdt/google-maps.png); }
- .ptsm-f4map::before { background-image: url(https://i.ibb.co/5WxjKkLp/F-Logo.png); }
- .ptsm-apple::before { background-image: url(https://i.ibb.co/WsH15zC/Apple-Jetzt.png); }
- .ptsm-bing::before { background-image: url(https://i.ibb.co/0LF74p6/bing.png); }
- .ptsm-nrw::before { background-image: url(https://i.ibb.co/37q7H37/nrw.png); }
- .ptsm-osm::before { background-image: url(https://i.ibb.co/20wtGrsL/osm.png); }
- .ptsm-autobahn::before { background-image: url(https://i.ibb.co/2Y3pT8v2/Autobahn-Logo.png); }
- .ptsm-poi-karte::before { background-image: url(https://i.ibb.co/nMRSSKKp/POI-Karte.jpg); }
- .ptsm-poi-base::before { background-image: url(https://i.ibb.co/xS7vJQr8/POI-Base.jpg); }
- .ptsm-viamichelin::before { background-image: url(https://i.ibb.co/RTzJP87C/viamichelin.png); }
- .ptsm-here::before { background-image: url(https://i.ibb.co/MC9JF7T/h-logo.png); }
- .ptsm-mapillary::before { background-image: url(https://i.ibb.co/JWkZnh0X/mapillary.png); }
- .ptsm-osbrowser::before { background-image: url(https://i.ibb.co/RdQSgsY/osb.png); }
- .ptsm-mappy::before { background-image: url(https://i.ibb.co/wrhH7H95/mappy.png); }
- .ptsm-blitzer::before { background-image: url(https://i.ibb.co/gVKMwKS/blitzer.png); }
- .ptsm-bayernatlas::before { background-image: url(https://i.ibb.co/KxnBpv7J/bayernatlas.png); }
- .ptsm-tomtom::before { background-image: url(https://i.ibb.co/hDq5bys/tomtom-icon2.png); }
- .ptsm-basemap-de::before { background-image: url(https://i.ibb.co/V3jJJrb/de-map.png); }
- .ptsm-reporting::before { background-image: url(https://i.ibb.co/rZb76j2/pin.png); }
- .ptsm-kartaview::before { background-image: url(https://i.ibb.co/xgnTMFf/kartaview.png); }
- .ptsm-bayerninfo::before { background-image: url(https://i.ibb.co/R0K3SSs/bayerninfo.png); }
- .ptsm-timonline::before { background-image: url(https://i.ibb.co/bPJ4qRy/das-da2.png); }
- .ptsm-geoadmin::before { background-image: url(https://i.ibb.co/Np5chv4/CH-Icon-20.png); }
- .ptsm-basemap-at::before { background-image: url(https://i.ibb.co/MCKhDSH/AT-Icon.png); }
- .ptsm-adac::before { background-image: url(https://i.ibb.co/6YsGCFy/adac.png); }
- .ptsm-here-edit::before { background-image: url(https://i.ibb.co/VghMgy8/here.png); }
- .ptsm-umsehen::before { background-image: url(https://i.ibb.co/XYqjkYX/umsehen-icon.png); }
- .ptsm-hackintosh::before { background-image: url(https://i.ibb.co/8xP5RyC/Hackintosh.png); }
- .ptsm-archive::before { background-image: url(https://i.ibb.co/QHpvd85/Das-Logo.png); }
- .ptsm-state-info {
- font-size: 9px;
- color: #6c757d;
- font-style: italic;
- text-align: center;
- margin-top: 6px;
- margin-bottom: 4px;
- }
- .ptsm-warning {
- background: #fff8e1;
- border: 1px solid #ffcc02;
- border-radius: 4px;
- padding: 8px;
- margin-top: 8px;
- margin-bottom: 8px;
- display: flex;
- align-items: flex-start;
- }
- .ptsm-warning .w-icon {
- font-size: 14px;
- color: #f57c00;
- margin-right: 6px;
- flex-shrink: 0;
- }
- .ptsm-warning-text {
- font-size: 9px;
- color: #e65100;
- line-height: 1.3;
- }
- @media (max-width: 768px) {
- .ptsm-map-btn { width: calc(50% - 4px); }
- }
- @media (max-width: 480px) {
- .ptsm-map-btn { width: calc(100% - 4px); }
- }
- `;
- document.head.appendChild(style);
- // Create all map buttons
- var btn1 = createMapButton('Google', 'ptsm-google', () => {
- var cz = getCenterZoom();
- // Bessere Google Maps Zoom-Level Umrechnung
- var googleZoom;
- if (cz.zoom >= 20) googleZoom = 19; // Sehr nah
- else if (cz.zoom >= 18) googleZoom = 17; // Nah
- else if (cz.zoom >= 16) googleZoom = 15; // Mittel-nah
- else if (cz.zoom >= 14) googleZoom = 13; // Mittel
- else if (cz.zoom >= 12) googleZoom = 11; // Mittel-weit
- else if (cz.zoom >= 10) googleZoom = 9; // Weit
- else googleZoom = Math.max(1, cz.zoom - 2); // Sehr weit
- window.open('https://www.google.com/maps/@' + cz.lat + ',' + cz.lon + ',' + googleZoom + 'z/data=!5m1!1e1', '_blank');
- });
- var btn2 = createMapButton('Bing', 'ptsm-bing', () => {
- var cz = getCenterZoom();
- cz.zoom -= 1;
- window.open('https://www.bing.com/maps/traffic?cp=' + cz.lat + '~' + cz.lon + '&lvl=' + cz.zoom, '_blank');
- });
- var btn3 = createMapButton('Ver. NRW', 'ptsm-nrw', () => {
- var cz = getCenterZoom();
- window.open('https://www.verkehr.nrw/?center=' + cz.lat + ',' + cz.lon + '&zoom=' + cz.zoom + '&layer=Verkehrslage,Baustellen,Haltestellen,Parken,Webcams,Verkehrsmeldungen,ELadesaeulen,Tankstellen&highlightRoute=false', '_blank');
- });
- var btn3a = createMapButton('OSM', 'ptsm-osm', () => {
- var cz = getCenterZoom();
- window.open('https://www.openstreetmap.org/#map=' + cz.zoom + '/' + cz.lat + '/' + cz.lon, '_blank');
- });
- var btn4 = createMapButton('F4 3D Map', 'ptsm-f4map', () => {
- var cz = getCenterZoom();
- window.open('https://demo.f4map.com/#lat=' + cz.lat + '&lon=' + cz.lon + '&zoom=' + cz.zoom, '_blank');
- });
- var btn5 = createMapButton('Apple', 'ptsm-apple', () => {
- var cz = getCenterZoom();
- window.open('https://maps.apple.com/look-around?coordinate=' + cz.lat + '%2C' + cz.lon, '_blank');
- });
- var btn6 = createMapButton('Autobahn', 'ptsm-autobahn', () => {
- var cz = getCenterZoom();
- window.open('https://verkehr.vz-deutschland.de/?layer=raststellen,baustellen,stau,verkehrsmeldungen&zoom=' + cz.zoom + '&lat=' + cz.lat + '&lon=' + cz.lon, '_blank');
- });
- var btn7 = createMapButton('POI Karte', 'ptsm-poi-karte', () => {
- var cz = getCenterZoom();
- // Bessere Zoom-Level-Übertragung: Je höher das Waze-Zoom, desto kleiner der Radius
- var radius;
- if (cz.zoom >= 18) radius = 500; // Sehr nah - 500m Radius
- else if (cz.zoom >= 16) radius = 1000; // Nah - 1km Radius
- else if (cz.zoom >= 14) radius = 2500; // Mittel - 2.5km Radius
- else if (cz.zoom >= 12) radius = 5000; // Weit - 5km Radius
- else if (cz.zoom >= 10) radius = 10000; // Sehr weit - 10km Radius
- else radius = 25000; // Maximum - 25km Radius
- window.open('https://www.flosm.org/de/POI-Karte.html?lat=' + cz.lat + '&lon=' + cz.lon + '&r=' + radius + '&st=0&sw=speedcamera', '_blank');
- });
- var btn8 = createMapButton('POI Base', 'ptsm-poi-base', () => {
- var cz = getCenterZoom();
- window.open('https://www.poibase.com/de/karte/#/map/coords-' + cz.lon + ',' + cz.lat + '/zoom-' + cz.zoom, '_blank');
- });
- var btn9 = createMapButton('ViaM', 'ptsm-viamichelin', () => {
- var cz = getCenterZoom();
- // ViaMichelin moderne URL-Struktur mit bounds und center
- // Zoom-abhängige Bounds-Berechnung für bessere Darstellung
- var zoomFactor;
- if (cz.zoom >= 18) zoomFactor = 0.001; // Sehr nah
- else if (cz.zoom >= 16) zoomFactor = 0.002; // Nah
- else if (cz.zoom >= 14) zoomFactor = 0.005; // Mittel-nah
- else if (cz.zoom >= 12) zoomFactor = 0.01; // Mittel
- else if (cz.zoom >= 10) zoomFactor = 0.02; // Mittel-weit
- else if (cz.zoom >= 8) zoomFactor = 0.05; // Weit
- else zoomFactor = 0.1; // Sehr weit
- // Bounds berechnen (Rechteck um den Mittelpunkt)
- var latMin = (cz.lat - zoomFactor).toFixed(6);
- var latMax = (cz.lat + zoomFactor).toFixed(6);
- var lonMin = (cz.lon - zoomFactor).toFixed(6);
- var lonMax = (cz.lon + zoomFactor).toFixed(6);
- var viaMichelinUrl = 'https://www.viamichelin.de/karten-stadtplan/verkehr?bounds=' +
- lonMin + '%7E' + latMin + '%7E' + lonMax + '%7E' + latMax +
- '¢er=' + cz.lon.toFixed(6) + '%7E' + cz.lat.toFixed(6) +
- '&page=1&poiCategories=0&showPolandModal=false';
- window.open(viaMichelinUrl, '_blank');
- });
- var btn10 = createMapButton('Here', 'ptsm-here', () => {
- var cz = getCenterZoom();
- window.open('https://wego.here.com/?map=' + cz.lat + ',' + cz.lon + ',' + cz.zoom + ',normal', '_blank');
- });
- var btn11 = createMapButton('Mapillary', 'ptsm-mapillary', () => {
- var cz = getCenterZoom();
- cz.zoom -= 1;
- window.open('https://www.mapillary.com/app/?lat=' + cz.lat + '&lng=' + cz.lon + '&z=' + cz.zoom, '_blank');
- });
- var btn12 = createMapButton('OSBrowser', 'ptsm-osbrowser', () => {
- var cz = getCenterZoom();
- window.open('https://www.openstreetbrowser.org/#map=' + cz.zoom + '/' + cz.lat + '/' + cz.lon + '&categories=car_maxspeed', '_blank');
- });
- var btn13 = createMapButton('Mappy', 'ptsm-mappy', () => {
- var cz = getCenterZoom();
- window.open('https://en.mappy.com/plan#/' + cz.lat + ',' + cz.lon, '_blank');
- });
- var btn14 = createMapButton('Blitzer.de', 'ptsm-blitzer', () => {
- var cz = getCenterZoom();
- window.open('https://map.atudo.com/v5/?lat=' + cz.lat + '&lng=' + cz.lon + '&zoom=' + cz.zoom, '_blank');
- });
- var btn16 = createMapButton('BY Atlas', 'ptsm-bayernatlas', () => {
- var cz = getCenterZoom();
- cz.zoom -= 5;
- if (typeof proj4 === "undefined") {
- alert('proj4 library not loaded');
- return;
- }
- var firstProj = '+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs';
- var utm = proj4(firstProj, [cz.lon, cz.lat]);
- window.open('https://geoportal.bayern.de/bayernatlas/index.html?zoom=' + cz.zoom + '&E=' + utm[0] + '&N=' + utm[1], '_blank');
- });
- var btn18 = createMapButton('TomTom', 'ptsm-tomtom', () => {
- var cz = getCenterZoom();
- cz.zoom -= 1;
- window.open('https://plan.tomtom.com/de?p=' + cz.lat + ',' + cz.lon + ',' + cz.zoom + 'z', '_blank');
- });
- var btn19 = createMapButton('Basemap.de', 'ptsm-basemap-de', () => {
- var cz = getCenterZoom();
- cz.zoom = 0.985 * cz.zoom - 1.05;
- window.open('https://basemap.de/viewer?config=' + btoa('{"lat":' + cz.lat + ',"lon":' + cz.lon + ',"zoom":' + cz.zoom + ',"styleID":0,"pitch":0,"bearing":0,"saturation":0,"brightness":0,"hiddenControls":[],"hiddenLayers":[],"changedLayers":[],"hiddenSubGroups":[],"changedSubGroups":[],"externalStyleURL":""}'), '_blank');
- });
- var btn20 = createMapButton('Reporting', 'ptsm-reporting', () => {
- var cz = getCenterZoom();
- cz.zoom -= 1;
- window.open('https://www.waze.com/partnerhub/map-tool?lat=' + cz.lat + '&lon=' + cz.lon + '&zoom=' + cz.zoom, '_blank');
- });
- var btn21 = createMapButton('KartaView', 'ptsm-kartaview', () => {
- var cz = getCenterZoom();
- cz.zoom -= 1;
- window.open('https://kartaview.org/map/@' + cz.lat + ',' + cz.lon + ',' + cz.zoom + 'z', '_blank');
- });
- var btn22 = createMapButton('Bayerninfo', 'ptsm-bayerninfo', () => {
- var cz = getCenterZoom();
- cz.zoom -= 2;
- var now = new Date();
- var then = new Date();
- then.setDate(then.getDate() + 60);
- var mapsUrl = 'https://www.bayerninfo.de/de/baustellenkalender?geo=' + cz.lat + ',' + cz.lon + '&zoom=' + cz.zoom;
- mapsUrl += '&datetimeFrom=' + now.toISOString() + '&datetimeTo=' + then.toISOString();
- window.open(mapsUrl, '_blank');
- });
- var btn30 = createMapButton('TimOnline', 'ptsm-timonline', () => {
- var cz = getCenterZoom();
- cz.zoom = 454959671.96858*Math.exp(-0.693*cz.zoom);
- window.open('https://www.tim-online.nrw.de/tim-online2/?center=' + cz.lat + ',' + cz.lon + '&scale=' + cz.zoom, '_blank');
- });
- var btn31 = createMapButton('GeoAdmin', 'ptsm-geoadmin', () => {
- var cz = getCenterZoom();
- cz.zoom -= 7.2;
- var phi1 = ((cz.lat * 3600) - 169028.66) / 10000;
- var lmd1 = ((cz.lon * 3600) - 26782.5) / 10000;
- var x = 200147.07 + 308807.95 * phi1 + 3745.25 * lmd1 * lmd1 + 76.63 * phi1 * phi1 + 119.79 * phi1 * phi1 * phi1 - 194.56 * lmd1 * lmd1 * phi1;
- var y = 600072.37 + 211455.93 * lmd1 - 10938.51 * lmd1 * phi1 - 0.36 * lmd1 * phi1 * phi1 - 44.54 * lmd1 * lmd1 * lmd1;
- window.open('https://map.geo.admin.ch/?Y=' + y.toFixed(0) + '&X=' + x.toFixed(0) + '&zoom=' + cz.zoom + '&topic=ech&lang=de&bgLayer=ch.swisstopo.pixelkarte-farbe&layers=ch.bfs.gebaeude_wohnungs_register,ch.kantone.cadastralwebmap-farbe,ch.swisstopo.swissimage-product,ch.bfe.ladestellen-elektromobilitaet,ch.swisstopo.swissimage-product,ch.bfe.ladestellen-elektromobilitaet,ch.swisstopo.swissimage-product,ch.bfe.ladestellen-elektromobilitaet&catalogNodes=457,532,687,458,477,485,491,510,527,1743&layers_visibility=false,false,true,true&layers_timestamp=,,current', '_blank');
- });
- var btn32 = createMapButton('Basemap', 'ptsm-basemap-at', () => {
- var cz = getCenterZoom();
- var x = cz.lon / 180 * 20037508.34;
- var y = Math.log(Math.tan((90 + cz.lat * 1) * Math.PI / 360)) / Math.PI;
- y *= 20037508.34;
- window.open('https://basemap.at/bmapp/index.html#{"center":[' + x.toFixed(10) + ',' + y.toFixed(10) + '],"zoom":' + cz.zoom + ',"rotation":0,"layers":"1000000000"}', '_blank');
- });
- var btn34 = createMapButton('ADAC', 'ptsm-adac', () => {
- var cz = getCenterZoom();
- // ADAC Maps präzise Bounds-Berechnung basierend auf Bildvergleich
- var zoomFactor;
- if (cz.zoom >= 19) zoomFactor = 0.002; // Extrem nah - größerer Bereich
- else if (cz.zoom >= 18) zoomFactor = 0.003; // Sehr nah
- else if (cz.zoom >= 17) zoomFactor = 0.004; // Nah+
- else if (cz.zoom >= 16) zoomFactor = 0.006; // Nah
- else if (cz.zoom >= 15) zoomFactor = 0.008; // Mittel-nah+
- else if (cz.zoom >= 14) zoomFactor = 0.012; // Mittel-nah
- else if (cz.zoom >= 13) zoomFactor = 0.016; // Mittel+
- else if (cz.zoom >= 12) zoomFactor = 0.022; // Mittel
- else if (cz.zoom >= 11) zoomFactor = 0.030; // Mittel-weit+
- else if (cz.zoom >= 10) zoomFactor = 0.040; // Mittel-weit
- else if (cz.zoom >= 9) zoomFactor = 0.060; // Weit+
- else if (cz.zoom >= 8) zoomFactor = 0.080; // Weit
- else zoomFactor = 0.120; // Sehr weit
- // Korrektur der Verschiebung: ADAC zeigt zu weit östlich und leicht nördlich
- var correctedLat = cz.lat - (zoomFactor * 0.05); // Leichte Südverschiebung
- var correctedLon = cz.lon - (zoomFactor * 0.15); // Leichte Westverschiebung
- var latMin = (correctedLat - zoomFactor).toFixed(6);
- var latMax = (correctedLat + zoomFactor).toFixed(6);
- var lonMin = (correctedLon - zoomFactor).toFixed(6);
- var lonMax = (correctedLon + zoomFactor).toFixed(6);
- var adacUrl = 'https://maps.adac.de/?bounds=' +
- latMin + ',' + lonMin + '-' + latMax + ',' + lonMax +
- '&traffic=construction,announcements,flow';
- window.open(adacUrl, '_blank');
- });
- var btn36 = createMapButton('Here Edit', 'ptsm-here-edit', () => {
- var cz = getCenterZoom();
- window.open('https://mapcreator.here.com/?l=' + cz.lat + ',' + cz.lon + ',' + cz.zoom + ',autoselect', '_blank');
- });
- var btn37 = createMapButton('Umsehen', 'ptsm-umsehen', () => {
- var cz = getCenterZoom();
- cz.zoom += 1.0;
- window.open('https://maps.apple.com/?ll=' + cz.lat + ',' + cz.lon + '&z=' + cz.zoom + '&t=m', '_blank');
- });
- var btn39 = createMapButton('Hackintosh', 'ptsm-hackintosh', () => {
- var cz = getCenterZoom();
- window.open('https://lookmap.eu.pythonanywhere.com/#c=' + cz.zoom + '/' + cz.lat + '/' + cz.lon + '/', '_blank');
- });
- var btn40 = createMapButton('Archive', 'ptsm-archive', () => {
- window.open('https://archive.is/', '_blank');
- });
- // Create container
- var container = document.createElement('div');
- container.className = 'ptsm-container';
- // Kompakt-Modus beim Laden anwenden falls gespeichert
- if (loadCompactMode()) {
- container.classList.add('compact');
- }
- // Create categories
- var allgemCategory = createCategory('Allgemeine Karten', 'ptsm-category-allgem', true);
- var baustellCategory = createCategory('Baustellen & Verkehr', 'ptsm-category-baustell', false);
- var blitzerCategory = createCategory('Blitzer & Geschwindigkeit', 'ptsm-category-blitzer', false);
- var bilderCategory = createCategory('Straßenbilder', 'ptsm-category-bilder', false);
- var geoportalCategory = createCategory('Geoportale', 'ptsm-category-geoportal', false);
- var miscCategory = createCategory('Sonstiges', 'ptsm-category-misc', false);
- // Add buttons to categories
- // Allgemeine Karten
- allgemCategory.content.appendChild(btn1); // Google
- allgemCategory.content.appendChild(btn2); // Bing
- allgemCategory.content.appendChild(btn3a); // OSM
- allgemCategory.content.appendChild(btn4); // F4 3D
- allgemCategory.content.appendChild(btn5); // Apple
- allgemCategory.content.appendChild(btn10); // Here
- allgemCategory.content.appendChild(btn13); // Mappy
- allgemCategory.content.appendChild(btn18); // TomTom
- allgemCategory.content.appendChild(btn9); // ViaM
- // Baustellen & Verkehr
- baustellCategory.content.appendChild(btn3); // Ver. NRW
- baustellCategory.content.appendChild(btn6); // Autobahn
- baustellCategory.content.appendChild(btn34); // ADAC
- // Blitzer & Geschwindigkeit
- blitzerCategory.content.appendChild(btn14); // Blitzer.de
- blitzerCategory.content.appendChild(btn7); // POI Karte
- blitzerCategory.content.appendChild(btn8); // POI Base
- // Straßenbilder
- bilderCategory.content.appendChild(btn11); // Mapillary
- bilderCategory.content.appendChild(btn21); // KartaView
- bilderCategory.content.appendChild(btn12); // OSBrowser
- // Geoportale
- geoportalCategory.content.appendChild(btn19); // Basemap DE
- geoportalCategory.content.appendChild(btn31); // GeoAdmin
- geoportalCategory.content.appendChild(btn32); // Basemap AT
- geoportalCategory.content.appendChild(btn30); // TimOnline
- geoportalCategory.content.appendChild(btn16); // BY Atlas
- geoportalCategory.content.appendChild(btn22); // Bayerninfo
- // Einstellungen-Kategorie erstellen
- var settingsCategory = createCategory('Einstellungen', 'ptsm-category-settings', false);
- // Settings-Inhalte erstellen
- function createSettingsContent() {
- const settings = loadSettings();
- var settingsContent = document.createElement('div');
- settingsContent.className = 'ptsm-settings-content';
- settingsContent.style.cssText = 'padding: 8px;';
- // Freie Position Checkbox
- var freePositionContainer = document.createElement('div');
- freePositionContainer.style.cssText = `
- margin-bottom: 12px;
- padding: 8px;
- background: #f8f9fa;
- border-radius: 4px;
- display: flex;
- align-items: center;
- border: 1px solid #e9ecef;
- `;
- var freePositionCheckbox = document.createElement('input');
- freePositionCheckbox.type = 'checkbox';
- freePositionCheckbox.id = 'ptsm-free-position';
- freePositionCheckbox.checked = settings.freePosition;
- freePositionCheckbox.style.cssText = 'margin-right: 10px; transform: scale(1.2);';
- var freePositionLabel = document.createElement('label');
- freePositionLabel.htmlFor = 'ptsm-free-position';
- freePositionLabel.innerHTML = '📌 Freie Position (Desktop-weit)';
- freePositionLabel.style.cssText = `
- font-size: 12px;
- font-weight: 500;
- cursor: pointer;
- flex: 1;
- user-select: none;
- `;
- freePositionContainer.appendChild(freePositionCheckbox);
- freePositionContainer.appendChild(freePositionLabel);
- // Kompakt-Modus Checkbox
- var compactContainer = document.createElement('div');
- compactContainer.style.cssText = `
- margin-bottom: 12px;
- padding: 8px;
- background: #f8f9fa;
- border-radius: 4px;
- display: flex;
- align-items: center;
- border: 1px solid #e9ecef;
- `;
- var compactCheckbox = document.createElement('input');
- compactCheckbox.type = 'checkbox';
- compactCheckbox.id = 'ptsm-compact-mode';
- compactCheckbox.checked = loadCompactMode();
- compactCheckbox.style.cssText = 'margin-right: 10px; transform: scale(1.2);';
- var compactLabel = document.createElement('label');
- compactLabel.htmlFor = 'ptsm-compact-mode';
- compactLabel.innerHTML = '🔧 Kompakt-Modus';
- compactLabel.style.cssText = `
- font-size: 12px;
- font-weight: 500;
- cursor: pointer;
- flex: 1;
- user-select: none;
- `;
- compactContainer.appendChild(compactCheckbox);
- compactContainer.appendChild(compactLabel);
- // Reset Button
- var resetButton = document.createElement('button');
- resetButton.className = 'ptsm-reset-settings-btn';
- resetButton.innerHTML = '🔄 Einstellungen zurücksetzen';
- resetButton.style.cssText = `
- width: 100%;
- height: 36px;
- padding: 8px 12px;
- background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
- border: none;
- border-radius: 4px;
- font-size: 12px;
- font-weight: 600;
- color: white;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.15s ease;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- margin-top: 4px;
- `;
- // Event Listeners
- freePositionCheckbox.addEventListener('change', function() {
- toggleFreePosition(this.checked);
- // Event für Cursor-Update senden
- document.dispatchEvent(new Event('ptsm-settings-changed'));
- });
- compactCheckbox.addEventListener('change', function() {
- const container = document.querySelector('.ptsm-container');
- if (this.checked) {
- container.classList.add('compact');
- saveCompactMode(true);
- } else {
- container.classList.remove('compact');
- saveCompactMode(false);
- }
- });
- resetButton.addEventListener('click', resetAllSettings);
- resetButton.addEventListener('mouseover', function() {
- this.style.transform = 'translateY(-1px)';
- this.style.background = 'linear-gradient(135deg, #c82333 0%, #a02834 100%)';
- this.style.boxShadow = '0 2px 8px rgba(220, 53, 69, 0.25)';
- });
- resetButton.addEventListener('mouseout', function() {
- this.style.transform = 'translateY(0)';
- this.style.background = 'linear-gradient(135deg, #dc3545 0%, #c82333 100%)';
- this.style.boxShadow = 'none';
- });
- settingsContent.appendChild(freePositionContainer);
- settingsContent.appendChild(compactContainer);
- settingsContent.appendChild(resetButton);
- return settingsContent;
- }
- // Settings-Inhalt zur Kategorie hinzufügen
- var settingsContent = createSettingsContent();
- settingsCategory.content.appendChild(settingsContent);
- // Sonstiges
- miscCategory.content.appendChild(btn20); // Reporting
- miscCategory.content.appendChild(btn36); // Here Edit
- miscCategory.content.appendChild(btn37); // Umsehen
- miscCategory.content.appendChild(btn39); // Hackintosh
- miscCategory.content.appendChild(btn40); // Archive
- // Add categories to container
- container.appendChild(allgemCategory.category);
- container.appendChild(baustellCategory.category);
- container.appendChild(blitzerCategory.category);
- container.appendChild(bilderCategory.category);
- container.appendChild(geoportalCategory.category);
- container.appendChild(miscCategory.category);
- container.appendChild(settingsCategory.category);
- // Add version info with update link
- var versionInfo = document.createElement('div');
- versionInfo.className = 'ptsm-state-info';
- var updateLink = document.createElement('a');
- updateLink.href = 'https://greasyfork.org/de/scripts/448378-wme-permalink-to-several-maps-dach';
- updateLink.target = '_blank';
- updateLink.textContent = 'Auf Updates überprüfen / V' + ptsmVersion;
- updateLink.style.cssText = 'color: #007bff; text-decoration: none; font-size: 9px;';
- updateLink.addEventListener('mouseover', function() {
- this.style.textDecoration = 'underline';
- });
- updateLink.addEventListener('mouseout', function() {
- this.style.textDecoration = 'none';
- });
- versionInfo.appendChild(updateLink);
- container.appendChild(versionInfo);
- // Add warning about external services
- var warning = document.createElement('div');
- warning.className = 'ptsm-warning';
- warning.innerHTML = '<div class="w-icon">⚠</div><div class="ptsm-warning-text">Hinweis: Einige der externen Karten sind als Informationsquelle zur Kartenbearbeitung nicht zulässig!</div>';
- container.appendChild(warning);
- // Try to use WME Userscript API first, fallback to manual insertion
- try {
- if (W.userscripts && W.userscripts.registerSidebarTab) {
- const result = W.userscripts.registerSidebarTab('wme-ptsm-dach');
- var tabLabel = result.tabLabel;
- var tabPane = result.tabPane;
- tabLabel.textContent = 'PTSM';
- tabLabel.title = 'WME Permalink to several Maps DACH';
- tabPane.appendChild(container);
- } else {
- // Fallback: Try to add to existing sidebar
- var sidebar = document.querySelector('#sidebar') || document.querySelector('.sidebar');
- if (sidebar) {
- // Create a collapsible section
- var section = document.createElement('div');
- section.style.cssText = 'border: 1px solid #ccc; margin: 10px 0; border-radius: 5px;';
- var header = document.createElement('div');
- header.style.cssText = 'background: #f5f5f5; padding: 10px; cursor: pointer; font-weight: bold; user-select: none;';
- header.textContent = 'PTSM DACH';
- var content = document.createElement('div');
- content.style.display = 'none';
- content.appendChild(container);
- header.addEventListener('click', function() {
- content.style.display = content.style.display === 'none' ? 'block' : 'none';
- });
- section.appendChild(header);
- section.appendChild(content);
- sidebar.appendChild(section);
- } else {
- // Last resort: Create floating panel
- var floatingPanel = document.createElement('div');
- floatingPanel.style.cssText = `
- position: fixed;
- top: 100px;
- right: 10px;
- width: 300px;
- max-height: 70vh;
- overflow-y: auto;
- background: white;
- border: 1px solid #ccc;
- border-radius: 8px;
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
- z-index: 10000;
- resize: both;
- `;
- var panelHeader = document.createElement('div');
- panelHeader.style.cssText = `
- background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
- color: white;
- padding: 10px;
- font-weight: bold;
- cursor: move;
- user-select: none;
- display: flex;
- justify-content: space-between;
- align-items: center;
- `;
- panelHeader.innerHTML = 'PTSM DACH <span style="cursor: pointer; font-size: 18px;">×</span>';
- var closeBtn = panelHeader.querySelector('span');
- closeBtn.addEventListener('click', function() {
- floatingPanel.style.display = 'none';
- });
- // Make panel draggable
- var isDragging = false;
- var startX, startY, startLeft, startTop;
- panelHeader.addEventListener('mousedown', function(e) {
- if (e.target === closeBtn) return;
- isDragging = true;
- startX = e.clientX;
- startY = e.clientY;
- startLeft = parseInt(window.getComputedStyle(floatingPanel).left, 10);
- startTop = parseInt(window.getComputedStyle(floatingPanel).top, 10);
- document.addEventListener('mousemove', drag);
- document.addEventListener('mouseup', stopDrag);
- });
- function drag(e) {
- if (!isDragging) return;
- var newLeft = startLeft + e.clientX - startX;
- var newTop = startTop + e.clientY - startY;
- floatingPanel.style.left = newLeft + 'px';
- floatingPanel.style.top = newTop + 'px';
- }
- function stopDrag() {
- isDragging = false;
- document.removeEventListener('mousemove', drag);
- document.removeEventListener('mouseup', stopDrag);
- }
- floatingPanel.appendChild(panelHeader);
- floatingPanel.appendChild(container);
- document.body.appendChild(floatingPanel);
- }
- }
- } catch (e) {
- console.error('PTSM: Error adding to sidebar:', e);
- }
- // Alle PTSM-Daten zurücksetzen
- function resetAllSettings() {
- if (confirm('Alle PTSM-Einstellungen zurücksetzen?\n\n- Menü-Positionen\n- Dropdown-Zustände\n- Kompakt-Modus\n- Freie Position\n\nSeite wird neu geladen.')) {
- try {
- // Alle PTSM localStorage Keys entfernen
- const keysToRemove = [];
- for (let i = 0; i < localStorage.length; i++) {
- const key = localStorage.key(i);
- if (key && key.startsWith('wme-ptsm-')) {
- keysToRemove.push(key);
- }
- }
- keysToRemove.forEach(key => localStorage.removeItem(key));
- // Seite neu laden
- location.reload();
- } catch (e) {
- alert('Fehler beim Zurücksetzen der Einstellungen');
- }
- }
- }
- // Freie Position ein/ausschalten
- function toggleFreePosition(enabled) {
- const settings = loadSettings();
- settings.freePosition = enabled;
- saveSettings(settings);
- const container = document.querySelector('.ptsm-container');
- if (!container) return;
- const categoryOrder = [
- '.ptsm-category-allgem',
- '.ptsm-category-baustell',
- '.ptsm-category-blitzer',
- '.ptsm-category-bilder',
- '.ptsm-category-geoportal',
- '.ptsm-category-misc',
- '.ptsm-category-settings'
- ];
- if (enabled) {
- // Freier Modus: Menüs können Desktop-weit gezogen werden
- categoryOrder.forEach(selector => {
- const category = document.querySelector(selector);
- if (category) {
- // Menüs für Desktop-weites Drag vorbereiten
- category.style.position = 'relative';
- category.style.zIndex = '1';
- }
- });
- } else {
- // Normaler Modus: Nur Desktop-Positionierung zurücksetzen
- categoryOrder.forEach(selector => {
- const category = document.querySelector(selector);
- if (category) {
- // Nur Desktop-Drag-Eigenschaften entfernen (wenn fixed positioniert)
- if (category.style.position === 'fixed') {
- category.style.position = '';
- category.style.left = '';
- category.style.top = '';
- category.style.zIndex = '';
- category.style.width = '';
- category.style.maxWidth = '';
- category.style.transform = '';
- category.style.opacity = '';
- category.style.boxShadow = '';
- // Nur zurück in Container wenn es außerhalb ist
- if (category.parentElement !== container) {
- container.appendChild(category);
- }
- }
- }
- });
- // Nur Version-Info und Warnung korrigieren falls sie verschoben wurden
- const versionInfo = container.querySelector('.ptsm-state-info');
- const warning = container.querySelector('.ptsm-warning');
- // Prüfen ob Version-Info nicht am Ende ist
- if (versionInfo && versionInfo.parentElement === container) {
- const allElements = Array.from(container.children);
- const versionIndex = allElements.indexOf(versionInfo);
- const warningIndex = warning ? allElements.indexOf(warning) : -1;
- // Nur neu positionieren wenn Version-Info nicht an vorletzter Stelle ist
- // (oder letzter Stelle wenn keine Warnung da ist)
- const shouldBeAtIndex = warning ? allElements.length - 2 : allElements.length - 1;
- if (versionIndex !== shouldBeAtIndex) {
- versionInfo.parentElement.removeChild(versionInfo);
- if (warning && warning.parentElement === container) {
- container.insertBefore(versionInfo, warning);
- } else {
- container.appendChild(versionInfo);
- }
- }
- }
- // Prüfen ob Warnung nicht am Ende ist
- if (warning && warning.parentElement === container) {
- const allElements = Array.from(container.children);
- const warningIndex = allElements.indexOf(warning);
- // Warnung sollte immer ganz am Ende sein
- if (warningIndex !== allElements.length - 1) {
- warning.parentElement.removeChild(warning);
- container.appendChild(warning);
- }
- }
- // Gespeicherte Desktop-Positionen löschen
- try {
- localStorage.removeItem('wme-ptsm-drag-positions');
- } catch (e) {
- console.log('PTSM: localStorage not available');
- }
- }
- // Drag & Drop Funktionalität neu initialisieren
- setTimeout(() => {
- enableDragAndDrop();
- // Event für Cursor-Update senden
- document.dispatchEvent(new Event('ptsm-settings-changed'));
- }, 100);
- }
- console.log('PTSM DACH v' + ptsmVersion + ' loaded successfully');
- // Update-Popup anzeigen (einmalig pro Version)
- setTimeout(showUpdatePopup, 1000);
- // Gespeicherte Kategorie-Reihenfolge wiederherstellen
- setTimeout(applyCategoryOrder, 100);
- // Drag & Drop aktivieren
- setTimeout(enableDragAndDrop, 500);
- // Initiale Einstellungen anwenden
- setTimeout(() => {
- const settings = loadSettings();
- if (settings.freePosition) {
- toggleFreePosition(true);
- }
- }, 1000);
- }
- // Initialize when page loads
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', addButtons);
- } else {
- setTimeout(addButtons, 1000);
- }
- // Also try to initialize when Waze editor loads
- if (typeof W !== 'undefined') {
- if (W.loginManager && W.loginManager.events) {
- W.loginManager.events.register('loginStateChanged', null, addButtons);
- }
- setTimeout(addButtons, 2000);
- } else {
- // Wait for Waze object to be available
- var checkForWaze = setInterval(function() {
- if (typeof W !== 'undefined' && W.loginManager) {
- clearInterval(checkForWaze);
- setTimeout(addButtons, 1000);
- }
- }, 500);
- }