Files
geograsim/App/php/refresh-waypoints-from-osm.php
T
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- 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>
2026-07-08 02:27:02 +02:00

242 lines
9.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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.310 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>