Files
geograsim/App/pages/heli.php
T
Adminator eac686ee1a Atlas: Tile-Proxy + Leaflet lokal — DSGVO-Sanierung fuer Kartendienste
- Leaflet 1.9.4 (CSS + JS + 5 Marker-Images) lokal unter
  App/assets/vendor/leaflet/ statt via unpkg.com/Cloudflare
- Neuer PHP-Endpunkt App/php/tile-proxy.php mit Disk-Cache, liefert
  OSM- und CARTO-Kacheln serverseitig aus; Browser kontaktiert keinen
  Drittanbieter mehr
- Whitelist (osm, carto), Zoom 3-18, 30 Tage Browser-Cache, ETag +
  If-Modified-Since, sauberer User-Agent fuer OSM-Policy
- Cache-Pfad: APP_ROOT/data/tiles lokal, via .env TILE_CACHE_DIR
  auf Produktions-Volume /var/www/html/geograsim/data/tiles
- Heli (heli.php + heli-game.php), Logistik (logistik.php), Routes-
  Dev-Tool und admin-levels.html alle auf Proxy umgestellt
- Datenschutzerklaerung: alter Google-AdSense-Passus (falsch, nicht
  im Einsatz) entfernt, neuer Abschnitt zur serverseitigen
  Tile-Auslieferung mit Rechtsgrundlage Art. 6 Abs. 1 lit. f DSGVO
- .gitignore: App/data/tiles ausgeschlossen
- .env.example: TILE_CACHE_DIR + OpenAI/ElevenLabs dokumentiert

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 00:53:43 +02:00

122 lines
6.0 KiB
PHP

<?php
/**
* Heli-Übersichtskarte — Waypoints aus der DB
*/
require_once __DIR__ . '/../php/config/app.php';
require_once __DIR__ . '/../php/config/db.php';
$db = getDB();
$waypoints = $db->query("SELECT wp_key, name, lat, lon, region, wp_type, info FROM geo_waypoints ORDER BY region, name")->fetchAll(PDO::FETCH_ASSOC);
// Als JSON für JavaScript
$wpJson = json_encode($waypoints, JSON_UNESCAPED_UNICODE);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GeoGraSim — Helikopter-Übersichtskarte</title>
<link rel="icon" type="image/png" href="favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="<?= BASE_PATH ?>/assets/vendor/leaflet/leaflet.css">
<link rel="stylesheet" href="assets/fonts/inter.css">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',system-ui,sans-serif;overflow:hidden;height:100vh;background:#e8e4d8}
#map{width:100%;height:100vh}
.marker-circle{width:26px;height:26px;background:#fff;border:2.5px solid #1f4e5a;border-radius:50%;box-shadow:0 2px 6px rgba(0,0,0,.3);display:flex;align-items:center;justify-content:center;font-size:13px;line-height:1}
.marker-circle.capital{border-color:#e8833a;width:30px;height:30px;font-size:15px}
.marker-circle.peak{border-color:#5a8a5e;width:24px;height:24px;font-size:12px}
.marker-circle.base{border-color:#d4a017;width:30px;height:30px;font-size:16px}
.marker-circle.base-inactive{border-color:#888;width:22px;height:22px;font-size:11px;opacity:.7}
.wp-pill{font-family:'Inter',sans-serif;font-weight:700;font-size:11px;white-space:nowrap;padding:3px 9px;border-radius:5px;box-shadow:0 1px 4px rgba(0,0,0,.12);display:flex;align-items:center;height:20px}
.wp-pill.city{background:rgba(218,232,236,.94);color:#1f4e5a;border-bottom:2.5px solid #4a7c8a}
.wp-pill.capital{background:rgba(232,200,170,.92);color:#e8833a;border-bottom:2.5px solid #e8833a}
.wp-pill.peak{background:rgba(220,234,221,.94);color:#3a6b3e;border-bottom:2.5px solid #5a8a5e}
.wp-pill.base{background:rgba(255,255,255,.92);color:#9a7800;border-left:3px solid #d4a017;font-size:10px}
.wp-pill .pop{font-size:8px;font-weight:500;color:#8a8a8a;margin-left:4px}
</style>
</head>
<body>
<div id="map"></div>
<script src="<?= BASE_PATH ?>/assets/vendor/leaflet/leaflet.js"></script>
<script>
// Waypoints direkt aus der DB (PHP-injiziert)
var DB_WAYPOINTS = <?= $wpJson ?>;
var map = L.map('map', {zoomControl:false, maxBoundsViscosity:1.0, minZoom:7, maxZoom:15})
.setView([47.4, 13.2], 8);
L.tileLayer('<?= BASE_PATH ?>/php/tile-proxy.php?z={z}&x={x}&y={y}&p=osm', {
attribution:'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>', maxZoom:18
}).addTo(map);
L.control.zoom({position:'topright'}).addTo(map);
L.control.scale({position:'bottomleft', metric:true, imperial:false, maxWidth:200}).addTo(map);
// Render alle Waypoints nach Typ
DB_WAYPOINTS.forEach(function(w) {
var lat = parseFloat(w.lat), lon = parseFloat(w.lon);
var icon, pillClass, pillHtml, popupHtml, size;
if (w.wp_type === 'base') {
size = 30;
icon = '<div class="marker-circle base">🚁</div>';
pillClass = 'wp-pill base';
pillHtml = w.name;
popupHtml = '<div style="font-family:Inter,sans-serif;text-align:center;padding:4px">'
+'<div style="font-size:2rem">🚁</div>'
+'<div style="font-size:14px;font-weight:800;color:#1f4e5a">'+w.name+'</div>'
+(w.info?'<div style="font-size:11px;color:#8a8a8a">'+w.info+'</div>':'')
+'</div>';
} else if (w.wp_type === 'city') {
size = 26;
icon = '<div class="marker-circle">🏙️</div>';
pillClass = 'wp-pill city';
pillHtml = w.name;
popupHtml = '<div style="font-family:Inter,sans-serif;text-align:center;padding:4px">'
+'<div style="font-size:16px;font-weight:900;color:#1f4e5a">'+w.name+'</div>'
+'<div style="font-size:10px;color:#8a8a8a">'+w.region+'</div>'
+(w.info?'<div style="font-size:11px;color:#4a4a4a;margin-top:4px">'+w.info+'</div>':'')
+'</div>';
} else if (w.wp_type === 'peak') {
size = 24;
icon = '<div class="marker-circle peak">🏔️</div>';
pillClass = 'wp-pill peak';
pillHtml = w.name + (w.info?' <span class="pop">'+w.info+'</span>':'');
popupHtml = '<div style="font-family:Inter,sans-serif;text-align:center">'
+'<div style="font-size:2rem">🏔️</div>'
+'<div style="font-size:15px;font-weight:900;color:#3a6b3e">'+w.name+'</div>'
+(w.info?'<div style="font-size:13px;color:#1f4e5a;font-weight:800">'+w.info+'</div>':'')
+'</div>';
} else if (w.wp_type === 'village') {
size = 20;
icon = '<div style="width:16px;height:16px;background:#fff;border:2px solid #8a8a8a;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.2)"></div>';
pillClass = null; // Kein Pill für kleine Dörfer
popupHtml = '<div style="font-family:Inter,sans-serif"><strong>'+w.name+'</strong>'
+(w.info?'<br><span style="font-size:11px;color:#8a8a8a">'+w.info+'</span>':'')+'</div>';
} else {
size = 18;
icon = '<div style="width:14px;height:14px;background:#c85c4a;border:2px solid #fff;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.2)"></div>';
pillClass = null;
popupHtml = '<div style="font-family:Inter,sans-serif"><strong>'+w.name+'</strong>'
+(w.info?'<br><span style="font-size:11px;color:#8a8a8a">'+w.info+'</span>':'')+'</div>';
}
// Marker
L.marker([lat, lon], {
icon: L.divIcon({className:'', html:icon, iconSize:[size,size], iconAnchor:[size/2,size/2]}),
zIndexOffset: w.wp_type==='base'?1000:w.wp_type==='city'?500:w.wp_type==='peak'?300:100
}).addTo(map).bindPopup(popupHtml, {maxWidth:260});
// Pill (nur für Basen, Städte, Gipfel)
if (pillClass) {
L.marker([lat, lon], {
icon: L.divIcon({className:pillClass, html:pillHtml, iconSize:[140,20], iconAnchor:[-14,10]}),
interactive:false, zIndexOffset: w.wp_type==='base'?900:w.wp_type==='city'?400:200
}).addTo(map);
}
});
</script>
</body>
</html>