| 
									
										
										
										
											2025-08-30 13:59:12 +02:00
										 |  |  |  | <!DOCTYPE html> | 
					
						
							|  |  |  |  | <html lang="de"> | 
					
						
							|  |  |  |  | <head> | 
					
						
							|  |  |  |  |   <meta charset="utf-8"/> | 
					
						
							|  |  |  |  |   <title>Arenos – Map</title> | 
					
						
							|  |  |  |  |   <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | 
					
						
							|  |  |  |  |   <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/> | 
					
						
							|  |  |  |  |   <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> | 
					
						
							|  |  |  |  |   <style> | 
					
						
							|  |  |  |  |     html,body,#map {height:100%;margin:0} | 
					
						
							|  |  |  |  |     .coords{position:absolute;left:6px;bottom:6px;z-index:1000;background:rgba(255,255,255,.85); | 
					
						
							|  |  |  |  |       padding:3px 6px;font-size:12px;border-radius:4px;font-family:system-ui,Segoe UI,Arial} | 
					
						
							| 
									
										
										
										
											2025-09-14 15:25:37 +02:00
										 |  |  |  |       .center-dot{position:absolute;top:50%;left:50%;width:5px;height:5px;background:red;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 2px rgba(255,255,255,.95),0 0 2px rgba(0,0,0,.35);pointer-events:none;z-index:1001} | 
					
						
							| 
									
										
										
										
											2025-08-30 13:59:12 +02:00
										 |  |  |  |     .copy-btn{position:absolute;left:6px;bottom:36px;z-index:1000;background:#ef4444;color:#fff;border:0;border-radius:6px;padding:6px 10px;font:600 12px/1 system-ui,Segoe UI,Arial;cursor:pointer;box-shadow:0 1px 2px rgba(0,0,0,.25)} | 
					
						
							|  |  |  |  |     .copy-btn:active{transform:translateY(1px)} | 
					
						
							|  |  |  |  |   </style> | 
					
						
							|  |  |  |  | </head> | 
					
						
							|  |  |  |  | <body> | 
					
						
							|  |  |  |  |   <div id="map"></div> | 
					
						
							|  |  |  |  |   <div class="center-dot" aria-hidden="true" title="Kartenmitte"></div> | 
					
						
							|  |  |  |  |   <div class="coords">—</div> | 
					
						
							|  |  |  |  |   <button class="copy-btn" title="Aktuelle Kartenmitte als Marker-Code kopieren">Marker Koords</button> | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   <script> | 
					
						
							|  |  |  |  |     // === Parameter (wie Export) === | 
					
						
							|  |  |  |  |     const ORIG_W = 49152;   // px | 
					
						
							|  |  |  |  |     const ORIG_H = 32768;   // px | 
					
						
							|  |  |  |  |     const TILE   = 256;     // px | 
					
						
							|  |  |  |  |     const MAX_Z  = 7;       // 0..7, 0=weit, 7=Originalgröße | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // === CRS an Export anpassen: z=7 → scale=1 (1px = 1 Einheits-Pixel), z=0 → 1/128 === | 
					
						
							|  |  |  |  |     const CRS_TILES = L.Util.extend({}, L.CRS.Simple, { | 
					
						
							|  |  |  |  |       transformation: new L.Transformation(1, 0, 1, 0), // y nach unten positiv | 
					
						
							|  |  |  |  |       scale: z => Math.pow(2, z - MAX_Z),              // 7→1, 6→1/2, …, 0→1/128 | 
					
						
							|  |  |  |  |       zoom: s => Math.log(s) / Math.LN2 + MAX_Z | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // Karte | 
					
						
							|  |  |  |  |     const bounds = [[0, 0], [ORIG_H, ORIG_W]]; // Pixelkoordinaten (oben/links → unten/rechts) | 
					
						
							|  |  |  |  |     const map = L.map('map', { | 
					
						
							|  |  |  |  |       crs: CRS_TILES, | 
					
						
							|  |  |  |  |       minZoom: 0, | 
					
						
							|  |  |  |  |       maxZoom: MAX_Z, | 
					
						
							|  |  |  |  |       preferCanvas: true, | 
					
						
							|  |  |  |  |       zoomControl: true, | 
					
						
							|  |  |  |  |       inertia: false, | 
					
						
							|  |  |  |  |       keepBuffer: 0 | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  |     map.fitBounds(bounds); | 
					
						
							|  |  |  |  |     map.setMaxBounds(bounds); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // Hilfsfunktion: Tile-Grid je Zoom (wie Exporter) | 
					
						
							|  |  |  |  |     function gridForZoom(z){ | 
					
						
							|  |  |  |  |       const scale = Math.pow(2, MAX_Z - z); // z=7 → 1 | 
					
						
							|  |  |  |  |       const wZ    = Math.ceil(ORIG_W / scale); | 
					
						
							|  |  |  |  |       const hZ    = Math.ceil(ORIG_H / scale); | 
					
						
							|  |  |  |  |       const tilesX= Math.ceil(wZ / TILE); | 
					
						
							|  |  |  |  |       const tilesY= Math.ceil(hZ / TILE); | 
					
						
							|  |  |  |  |       const offX  = Math.floor(tilesX / 2); | 
					
						
							|  |  |  |  |       const offY  = Math.floor(tilesY / 2); | 
					
						
							|  |  |  |  |       return { tilesX, tilesY, offX, offY }; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // Debug: zeige beim ersten Tile die berechneten Werte | 
					
						
							|  |  |  |  |     let first = true; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     const CenteredTiles = L.GridLayer.extend({ | 
					
						
							|  |  |  |  |       createTile: function (coords, done) { | 
					
						
							|  |  |  |  |         const img = document.createElement('img'); | 
					
						
							|  |  |  |  |         img.decoding = 'async'; | 
					
						
							|  |  |  |  |         img.referrerPolicy = 'no-referrer'; | 
					
						
							|  |  |  |  |         img.loading = 'lazy'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         const { tilesX, tilesY, offX, offY } = gridForZoom(coords.z); | 
					
						
							|  |  |  |  |         const xC = coords.x - offX;   // zentrierte Namen | 
					
						
							|  |  |  |  |         const yC = coords.y - offY; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         const minX = -offX, maxX = tilesX - offX - 1; | 
					
						
							|  |  |  |  |         const minY = -offY, maxY = tilesY - offY - 1; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         if (first) { | 
					
						
							|  |  |  |  |           console.log(`[z=${coords.z}] tilesX=${tilesX} tilesY=${tilesY} offX=${offX} offY=${offY}`); | 
					
						
							|  |  |  |  |           console.log('tile coords →', coords, '→ centered', xC, yC, 'validX', minX, maxX, 'validY', minY, maxY); | 
					
						
							|  |  |  |  |           first = false; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         // Außerhalb? → transparent (keine HTTP-Requests) | 
					
						
							|  |  |  |  |         if (xC < minX || xC > maxX || yC < minY || yC > maxY) { | 
					
						
							|  |  |  |  |           img.width = img.height = TILE; | 
					
						
							|  |  |  |  |           img.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='; | 
					
						
							|  |  |  |  |           img.onload = () => done(null, img); | 
					
						
							|  |  |  |  |           return img; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         img.onload  = () => done(null, img); | 
					
						
							|  |  |  |  |         img.onerror = () => { | 
					
						
							|  |  |  |  |           img.src = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256">\ | 
					
						
							|  |  |  |  |             <rect width="100%" height="100%" fill="%23ddd"/>\ | 
					
						
							|  |  |  |  |             <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle"\ | 
					
						
							|  |  |  |  |                   font-family="monospace" font-size="14">no tile</text></svg>'; | 
					
						
							|  |  |  |  |           done(null, img); | 
					
						
							|  |  |  |  |         }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         img.src = `tiles/${coords.z}/${xC}/${yC}.png`; | 
					
						
							|  |  |  |  |         return img; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 12:42:12 +02:00
										 |  |  |  | // === check URL hash === | 
					
						
							| 
									
										
										
										
											2025-09-02 09:18:48 +02:00
										 |  |  |  | // Format: #X,Y,Z   (z.B. #22884,8249,5) | 
					
						
							|  |  |  |  | const hash = window.location.hash.slice(1).split(','); | 
					
						
							|  |  |  |  | if (hash.length >= 2) { | 
					
						
							|  |  |  |  |   const x = parseFloat(hash[0]); | 
					
						
							|  |  |  |  |   const y = parseFloat(hash[1]); | 
					
						
							|  |  |  |  |   const z = hash.length >= 3 ? parseInt(hash[2]) : 5; | 
					
						
							| 
									
										
										
										
											2025-09-02 12:42:12 +02:00
										 |  |  |  |   if (!isNaN(x) && !isNaN(y)) { | 
					
						
							| 
									
										
										
										
											2025-09-02 09:22:38 +02:00
										 |  |  |  |     map.setView([y, x], isNaN(z) ? 5 : z); | 
					
						
							| 
									
										
										
										
											2025-09-02 12:42:12 +02:00
										 |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-09-02 09:22:38 +02:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-09-02 09:18:48 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 13:59:12 +02:00
										 |  |  |  |     new CenteredTiles({ | 
					
						
							|  |  |  |  |       tileSize: TILE, | 
					
						
							|  |  |  |  |       noWrap: true, | 
					
						
							|  |  |  |  |       bounds: bounds, | 
					
						
							|  |  |  |  |       minNativeZoom: 0, | 
					
						
							|  |  |  |  |       maxNativeZoom: MAX_Z | 
					
						
							|  |  |  |  |     }).addTo(map); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // Koordinatenanzeige | 
					
						
							|  |  |  |  |     const box = document.querySelector('.coords'); | 
					
						
							|  |  |  |  |     function show(){ | 
					
						
							|  |  |  |  |       const c = map.getCenter(); | 
					
						
							|  |  |  |  |       box.textContent = `X: ${c.lng.toFixed(0)}  Y: ${c.lat.toFixed(0)}  Z: ${map.getZoom()}`; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |     map.on('move zoom', show); show(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // Button: Marker-Code mit aktuellen (Mitte-)Koordinaten in die Zwischenablage | 
					
						
							|  |  |  |  |     const copyBtn = document.querySelector('.copy-btn'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     function copyText(text){ | 
					
						
							|  |  |  |  |       if (navigator.clipboard && window.isSecureContext){ | 
					
						
							|  |  |  |  |         return navigator.clipboard.writeText(text); | 
					
						
							|  |  |  |  |       } else { | 
					
						
							|  |  |  |  |         // Fallback für HTTP/unsichere Kontexte: verstecktes Textfeld + execCommand | 
					
						
							|  |  |  |  |         const ta = document.createElement('textarea'); | 
					
						
							|  |  |  |  |         ta.value = text; | 
					
						
							|  |  |  |  |         ta.setAttribute('readonly',''); | 
					
						
							|  |  |  |  |         ta.style.position = 'fixed'; | 
					
						
							|  |  |  |  |         ta.style.left = '-9999px'; | 
					
						
							|  |  |  |  |         ta.style.top = '-9999px'; | 
					
						
							|  |  |  |  |         document.body.appendChild(ta); | 
					
						
							|  |  |  |  |         ta.focus(); | 
					
						
							|  |  |  |  |         ta.select(); | 
					
						
							|  |  |  |  |         let ok = false; | 
					
						
							|  |  |  |  |         try { ok = document.execCommand('copy'); } catch(e) { ok = false; } | 
					
						
							|  |  |  |  |         document.body.removeChild(ta); | 
					
						
							|  |  |  |  |         return ok ? Promise.resolve() : Promise.reject(new Error('copy failed')); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     copyBtn.addEventListener('click', () => { | 
					
						
							|  |  |  |  |       const c = map.getCenter(); | 
					
						
							|  |  |  |  |       const X = Math.round(c.lng); // Anzeige links unten: X zuerst | 
					
						
							|  |  |  |  |       const Y = Math.round(c.lat); // dann Y | 
					
						
							|  |  |  |  |       const snippet = `  L.marker([${Y},${X}])  | 
					
						
							|  |  |  |  |   .addTo(map) | 
					
						
							|  |  |  |  |   .bindTooltip("NAME")  | 
					
						
							|  |  |  |  |   .on('click',()=>window.open( | 
					
						
							|  |  |  |  |     'WIKI_LINK','_blank'  | 
					
						
							|  |  |  |  |   ));`; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       copyText(snippet).then(() => { | 
					
						
							|  |  |  |  |         const old = copyBtn.textContent; copyBtn.textContent = 'Kopiert!'; | 
					
						
							|  |  |  |  |         setTimeout(()=> copyBtn.textContent = old, 1200); | 
					
						
							|  |  |  |  |       }).catch(err => { | 
					
						
							|  |  |  |  |         console.error(err); | 
					
						
							|  |  |  |  |         // Letzter Ausweg: Prompt anzeigen, manuell STRG+C drücken | 
					
						
							|  |  |  |  |         window.prompt('Kopieren fehlgeschlagen. Bitte STRG+C drücken und mit OK schließen:', snippet); | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  |   </script> | 
					
						
							| 
									
										
										
										
											2025-10-14 15:41:39 +02:00
										 |  |  |  |   <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> | 
					
						
							| 
									
										
										
										
											2025-10-14 15:50:03 +02:00
										 |  |  |  |   <script src="markers.js"></script> | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 13:59:12 +02:00
										 |  |  |  | </body> | 
					
						
							|  |  |  |  | </html> |