1e51ef7def
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/ - 12 code-workspace-Files - STATUS-*.md - viele M/D/R-Änderungen an bereits getrackten Files - .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
242 lines
9.6 KiB
PHP
242 lines
9.6 KiB
PHP
<?php
|
||
/**
|
||
* Komplett-Reset der Waypoint-Koordinaten aus OSM/Nominatim.
|
||
*
|
||
* Modi:
|
||
* ?mode=preview (default) — zeigt Tabelle, schreibt nicht
|
||
* ?mode=apply&confirm=ja — schreibt alle "safe changes" (Δ 0.3–10 km)
|
||
* ?mode=apply&confirm=ja&include_susp=1 — inkl. aller "suspicious" (Δ > 10 km)
|
||
*
|
||
* Sicherheitsnetz:
|
||
* Δ < 0.3 km → keep (zu nah, Ortszentrum-Toleranz)
|
||
* 0.3 km ≤ Δ < 10 km → auto-apply (safe)
|
||
* Δ ≥ 10 km → suspicious (nur manuell per Flag)
|
||
* OSM nichts gefunden → keep + flag
|
||
*
|
||
* Targets (fiktive Einsatzorte) nutzen hart fixierte Koords, keine Suche.
|
||
*/
|
||
require_once __DIR__ . '/config/app.php';
|
||
require_once __DIR__ . '/config/db.php';
|
||
|
||
set_time_limit(600);
|
||
$mode = $_GET['mode'] ?? 'preview';
|
||
$confirm = $_GET['confirm'] ?? '';
|
||
$includeSusp = !empty($_GET['include_susp']);
|
||
|
||
// ---------------------------------------------------------------
|
||
// Konfiguration: feste Queries pro key, wo Nominatim vom Namen
|
||
// allein falsch greifen würde
|
||
// ---------------------------------------------------------------
|
||
$OVERRIDE_QUERY = [
|
||
'nenzing' => 'Christophorus 8 Nenzing',
|
||
'hohenems' => 'Flugplatz Hohenems-Dornbirn',
|
||
'innsbruck' => 'Flughafen Innsbruck',
|
||
'zams_c5' => 'Krankenhaus St. Vinzenz Zams',
|
||
'kitzbuehel_c4' => 'Christophorus 4 Reith',
|
||
'lienz_c7' => 'Bezirkskrankenhaus Lienz',
|
||
'brenner' => 'Brennerpass',
|
||
// OSM-Fehltreffer vermeiden:
|
||
'oetz' => 'Oetz, Ötztal, Tirol',
|
||
'stuben' => 'Stuben am Arlberg',
|
||
];
|
||
|
||
// Manuell gesetzte Koords für fiktive Einsatzorte (targets)
|
||
$TARGET_COORDS = [
|
||
'bergrettung_muels' => [47.29325, 9.88957, 'Uga-Express Damüls (Talstation Skigebiet)'],
|
||
'skiunfall_soelden' => [46.97564, 10.97486, 'Giggijochbahn Sölden (Skigebiet)'],
|
||
'verkehr_a14' => [47.27063, 9.62238, 'A14 Rheintalautobahn bei Rankweil/Brederis'],
|
||
];
|
||
|
||
// ---------------------------------------------------------------
|
||
function haversine_km($lat1, $lon1, $lat2, $lon2) {
|
||
$R = 6371.0;
|
||
$dLat = deg2rad($lat2 - $lat1);
|
||
$dLon = deg2rad($lon2 - $lon1);
|
||
$a = sin($dLat/2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2) ** 2;
|
||
return $R * 2 * atan2(sqrt($a), sqrt(1 - $a));
|
||
}
|
||
|
||
function clean_name($name) {
|
||
$n = preg_replace('/^C\d+\s+/', '', $name);
|
||
$n = preg_replace('/\s*\(.*?\)\s*/', '', $n);
|
||
return trim($n);
|
||
}
|
||
|
||
function nominatim_lookup($query) {
|
||
$url = 'https://nominatim.openstreetmap.org/search?'
|
||
. http_build_query(['q' => $query, 'format' => 'json', 'countrycodes' => 'at', 'limit' => 1]);
|
||
$ctx = stream_context_create(['http' => [
|
||
'header' => "User-Agent: GeoGraSim-Waypoint-Refresh/1.0 (lokal, Bildungsprojekt)\r\n",
|
||
'timeout' => 10,
|
||
]]);
|
||
$resp = @file_get_contents($url, false, $ctx);
|
||
if ($resp === false) return null;
|
||
$j = json_decode($resp, true);
|
||
if (!is_array($j) || count($j) === 0) return null;
|
||
return ['lat' => (float)$j[0]['lat'], 'lon' => (float)$j[0]['lon'], 'display' => $j[0]['display_name']];
|
||
}
|
||
|
||
// ---------------------------------------------------------------
|
||
$db = getDB();
|
||
$rows = $db->query("SELECT wp_key, name, lat, lon, region, wp_type
|
||
FROM geo_waypoints ORDER BY wp_type, region, name")->fetchAll();
|
||
|
||
$proposals = [];
|
||
|
||
foreach ($rows as $w) {
|
||
$p = [
|
||
'wp_key' => $w['wp_key'],
|
||
'name' => $w['name'],
|
||
'region' => $w['region'],
|
||
'wp_type' => $w['wp_type'],
|
||
'old_lat' => (float)$w['lat'],
|
||
'old_lon' => (float)$w['lon'],
|
||
'new_lat' => null, 'new_lon' => null,
|
||
'distance_km' => null,
|
||
'source' => null,
|
||
'query' => null,
|
||
'osm_display' => null,
|
||
'status' => 'keep',
|
||
'note' => '',
|
||
];
|
||
|
||
// Target: hart gesetzte Koords
|
||
if ($w['wp_type'] === 'target' && isset($TARGET_COORDS[$w['wp_key']])) {
|
||
[$lat, $lon, $note] = $TARGET_COORDS[$w['wp_key']];
|
||
$p['new_lat'] = $lat;
|
||
$p['new_lon'] = $lon;
|
||
$p['source'] = 'manual';
|
||
$p['note'] = $note;
|
||
$p['distance_km'] = round(haversine_km($p['old_lat'], $p['old_lon'], $lat, $lon), 3);
|
||
if ($p['distance_km'] < 0.3) $p['status'] = 'keep';
|
||
elseif ($p['distance_km'] < 10.0) $p['status'] = 'safe_change';
|
||
else $p['status'] = 'suspicious';
|
||
$proposals[] = $p;
|
||
continue;
|
||
}
|
||
|
||
// OSM-Query bestimmen
|
||
$query = $OVERRIDE_QUERY[$w['wp_key']]
|
||
?? (clean_name($w['name']) . ', ' . ucfirst($w['region']) . ', Austria');
|
||
$p['query'] = $query;
|
||
|
||
$osm = nominatim_lookup($query);
|
||
usleep(1_100_000);
|
||
|
||
if (!$osm) {
|
||
$p['status'] = 'not_found';
|
||
$proposals[] = $p;
|
||
continue;
|
||
}
|
||
|
||
$p['new_lat'] = $osm['lat'];
|
||
$p['new_lon'] = $osm['lon'];
|
||
$p['osm_display'] = $osm['display'];
|
||
$p['source'] = 'osm';
|
||
$p['distance_km'] = round(haversine_km($p['old_lat'], $p['old_lon'], $osm['lat'], $osm['lon']), 3);
|
||
|
||
if ($p['distance_km'] < 0.3) $p['status'] = 'keep';
|
||
elseif ($p['distance_km'] < 10.0) $p['status'] = 'safe_change';
|
||
else $p['status'] = 'suspicious';
|
||
|
||
$proposals[] = $p;
|
||
}
|
||
|
||
// ---------------------------------------------------------------
|
||
// APPLY
|
||
// ---------------------------------------------------------------
|
||
$applied = [];
|
||
$applyDone = false;
|
||
if ($mode === 'apply' && $confirm === 'ja') {
|
||
$stmt = $db->prepare("UPDATE geo_waypoints SET lat=?, lon=? WHERE wp_key=?");
|
||
foreach ($proposals as &$p) {
|
||
$doApply = ($p['status'] === 'safe_change')
|
||
|| ($includeSusp && $p['status'] === 'suspicious');
|
||
if ($doApply && $p['new_lat'] !== null) {
|
||
$stmt->execute([$p['new_lat'], $p['new_lon'], $p['wp_key']]);
|
||
$p['applied'] = true;
|
||
$applied[] = $p;
|
||
} else {
|
||
$p['applied'] = false;
|
||
}
|
||
}
|
||
unset($p);
|
||
$applyDone = true;
|
||
}
|
||
|
||
// ---------------------------------------------------------------
|
||
// OUTPUT
|
||
// ---------------------------------------------------------------
|
||
$counts = array_count_values(array_column($proposals, 'status'));
|
||
?><!DOCTYPE html>
|
||
<html lang="de"><head>
|
||
<meta charset="UTF-8"><title>Waypoints — Refresh aus OSM</title>
|
||
<style>
|
||
body{font-family:system-ui,sans-serif;padding:1rem;background:#f5efe0;color:#1f2a24}
|
||
h1{color:#2d5434;margin-bottom:.3rem}
|
||
.sum{background:#fff;padding:.8rem 1rem;border-radius:8px;margin-bottom:1rem;display:flex;gap:1rem;flex-wrap:wrap;align-items:center}
|
||
.sum b{font-weight:700}
|
||
table{border-collapse:collapse;width:100%;background:#fff;font-size:.8rem}
|
||
th,td{padding:.3rem .5rem;border-bottom:1px solid #eee;text-align:left;vertical-align:top}
|
||
th{background:#2d5434;color:#f5efe0;position:sticky;top:0}
|
||
tr.keep td{color:#888}
|
||
tr.safe_change td{background:#e9f4ec}
|
||
tr.suspicious td{background:#fde0dc}
|
||
tr.not_found td{background:#f0e6f5}
|
||
tr.applied td{box-shadow:inset 4px 0 0 #2d5434}
|
||
code{font-family:ui-monospace,monospace;font-size:.76rem}
|
||
.dist{text-align:right;font-weight:700}
|
||
.btn{display:inline-block;padding:.5rem 1rem;background:#2d5434;color:#fff;border-radius:6px;text-decoration:none;margin-right:.5rem;font-weight:700}
|
||
.btn.danger{background:#c85c4a}
|
||
.btn.muted{background:#888}
|
||
.banner{background:#fff6d9;padding:.8rem 1rem;border-left:4px solid #8a7020;margin-bottom:1rem;border-radius:4px}
|
||
</style>
|
||
</head><body>
|
||
<h1>Waypoints — Komplett-Refresh aus OSM</h1>
|
||
<p style="color:#666;margin-bottom:1rem">Pro Eintrag: OSM/Nominatim-Abfrage, Vergleich mit DB, Vorschlag. Targets mit manuellen Koords.</p>
|
||
|
||
<?php if ($applyDone): ?>
|
||
<div class="banner" style="background:#e9f4ec;border-left-color:#2d5434">
|
||
<b>✓ Apply ausgeführt:</b> <?= count($applied) ?> UPDATEs in <code>geo_waypoints</code>.
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="sum">
|
||
<div>Gesamt: <b><?= count($proposals) ?></b></div>
|
||
<div style="color:#888">keep: <b><?= $counts['keep'] ?? 0 ?></b></div>
|
||
<div style="color:#2d5434">safe change: <b><?= $counts['safe_change'] ?? 0 ?></b></div>
|
||
<div style="color:#c85c4a">suspicious: <b><?= $counts['suspicious'] ?? 0 ?></b></div>
|
||
<div style="color:#7a4a8a">not found: <b><?= $counts['not_found'] ?? 0 ?></b></div>
|
||
<?php if ($mode === 'preview'): ?>
|
||
<a class="btn" href="?mode=apply&confirm=ja">Safe Changes anwenden</a>
|
||
<a class="btn danger" href="?mode=apply&confirm=ja&include_susp=1">Alle (inkl. Suspicious)</a>
|
||
<?php else: ?>
|
||
<a class="btn muted" href="?mode=preview">← Zurück zur Preview</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<table><thead><tr>
|
||
<th>Status</th><th>Typ</th><th>Key</th><th>Name</th>
|
||
<th>Alt</th><th>Neu</th><th class="dist">Δ km</th>
|
||
<th>Quelle</th><th>OSM-Treffer / Note</th>
|
||
</tr></thead><tbody>
|
||
<?php foreach ($proposals as $p):
|
||
$cls = $p['status'] . (!empty($p['applied']) ? ' applied' : '');
|
||
?>
|
||
<tr class="<?= $cls ?>">
|
||
<td><?= htmlspecialchars($p['status']) ?><?= !empty($p['applied']) ? ' ✓' : '' ?></td>
|
||
<td><?= htmlspecialchars($p['wp_type']) ?></td>
|
||
<td><code><?= htmlspecialchars($p['wp_key']) ?></code></td>
|
||
<td><?= htmlspecialchars($p['name']) ?></td>
|
||
<td><code><?= number_format($p['old_lat'],5) ?>, <?= number_format($p['old_lon'],5) ?></code></td>
|
||
<td><?= $p['new_lat'] !== null
|
||
? '<code>'.number_format($p['new_lat'],5).', '.number_format($p['new_lon'],5).'</code>'
|
||
: '—' ?></td>
|
||
<td class="dist"><?= $p['distance_km'] !== null ? number_format($p['distance_km'],2) : '—' ?></td>
|
||
<td><?= htmlspecialchars($p['source'] ?? '—') ?></td>
|
||
<td style="font-size:.72rem;color:#666"><?= htmlspecialchars($p['osm_display'] ?? $p['note']) ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody></table>
|
||
</body></html>
|