175 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
		
		
			
		
	
	
			175 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
|  | <!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} | |||
|  |       .center-dot{position:absolute;top:50%;left:50%;width:10px;height:10px;background:red;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 2px rgba(255,255,255,.95),0 0 6px rgba(0,0,0,.35);pointer-events:none;z-index:1001} | |||
|  |       .center-dot{position:absolute;top:50%;left:50%;width:10px;height:10px;background:red;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 2px rgba(255,255,255,.95),0 0 6px rgba(0,0,0,.35);pointer-events:none;z-index:1001} | |||
|  |     .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; | |||
|  |       } | |||
|  |     }); | |||
|  | 
 | |||
|  |     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> | |||
|  | 
 | |||
|  |   <script src="markers.js"></script> | |||
|  | </body> | |||
|  | </html> |