Files
mesh-configs/skills/devops/web-hosting-deployment/references/gtfs-raptor-routing.md
T

83 lines
4.6 KiB
Markdown
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.
# GTFS-RAPTOR-Verbindungssuche: Praxis & Pitfalls (2026-08-19, urlaub.perfekt-ohne-en.de)
Echte Nahverkehrs-Verbindungs-Suche (RE/RB/S-Bahn, kein ICE) aus einer GTFS-Datei
(Nutzer lieferte `r.und.s.bahnen.zip`). Löst die Aufgabe "komplette Haltestellenliste
in einer PDF + konsistente Fahrzeiten für viele Zielverbindungen".
## Warum RAPTOR statt Dijkstra
Einfacher Dijkstra auf einem statischen Graphen ist bei großen Knoten **instabil**:
benachbarte Ziele ergaben absurde Differenzen (Mainz→Berlin 11h vs. Nachbar Wiesbaden
3h). **RAPTOR** (Round-Based Public Transit Optimized Route) erweitert die Reichweite
Runde um Runde über echte Trips und behandelt "Zug nehmen → aussteigen → umsteigen"
natürlich. Für Zeit-abhängiges Transit-Routing ist RAPTOR der Standard.
## GTFS-Import — Spaltenreihenfolge ist NICHT selbsterklärend!
Vor dem Import IMMER `head -1 <datei>.txt` lesen, nie annehmen:
- `stops.txt`: `stop_name, parent_station, stop_id, stop_lat, stop_lon, …`
**stop_id ist Spalte 3**, parent_station Spalte 2. Ein Bahnhof hat oft ZWEI Zeilen
(parent-'Hbf' + stop_id-Knoten); die `stop_id` ist fürs Joinen zu `stop_times` gedacht.
- `stop_times.txt`: `trip_id, arrival_time, departure_time, stop_id, stop_sequence, …`
**stop_id Spalte 4**.
- `trips.txt`: `route_id, service_id, trip_id` — **trip_id Spalte 3**, nicht 0.
Import-Bug hier: Spalte 0 (route_id) genommen → nur 964 statt 110.087 Trips.
- `stop_times.txt` ist riesig (82MB); als SQLite laden mit Index auf `(trip_id, seq)`.
## Bahnhof = mehrere stop_ids, nur 1 aktiv in `st`
Große Knoten haben MEHRERE stop_ids (Saarbrücken Hbf = 6, Köln Hbf = 2), aber nur
einige kommen in `stop_times` vor. `SELECT stop_id WHERE stop_name='Köln Hbf'` kann eine
"tote" ID mit 0 Trips liefern. **Lösung: Geo-Merge** — Stops <350m Abstand (Haversine)
in eine Gruppe fassen; Routing löst Start/Ziel über die GANZE Gruppe auf und nimmt MIN
über alle Ziel-IDs. Sonst schlägt die Verbindung fehl oder wird absurd.
## `resolve("Berlin")`-Substring-Falle
`name.lower() in stop_name.lower()` matcht ALLE Stops mit "berlin" im Namen — auch
kleine Orte (z.B. ein Ort "Berlin" unweit Trier → unter 5h statt quer durchs Land!).
Für mehrdeutige Städte IMMER eindeutigen Bahnhof angeben (`"Berlin Hbf"`) und die
Site-DB-`nach`-Werte darauf normieren. Umlaute: Name-basiertes Matching braucht
Umlaut-Rücktranskription (ue/oe/ae/ss ↔ ü/ö/ä/ß).
## RAPTOR: Dauer UND Pfad (für PDF-Haltestellenliste)
Nur Dauer reicht nicht — die PDF soll die komplette Haltestellenliste zeigen. RAPTOR
muss pro Stop den **parent** speichern (mit welchem trip + Einstieg-Stop erreicht) und
nach Ziel-Erreichen den Pfad rückwärts rekonstruieren: je Segment alle Stops von
Einstieg bis Ausstieg → echte GTFS-Haltefolge (Trier→Konz→Saarburg→…→Saarbrücken).
Für Lesbarkeit `[von] + halteliste + [nach]` als Rahmen.
## PDF-Route: Pfad-Cache statt Live-RAPTOR (sonst 504)
Live-RAPTOR bei jedem PDF-Aufruf ist zu langsam (bis 200s → nginx 504 Gateway Timeout).
Lösung: **Pfad-Cache vorab bauen** (`pfade_cache.json`: key `von|nach` → seg + dauer_h)
und die PDF-Route liest nur JSON → PDF <1s.
⚠️ Cache auf die Kern-Ebenen (1+2) begrenzen: alle Paare wären ~7h Bauzeit, die
Kernmenge ~35min.
## Start ohne Bahnhof (Radevormwald) → Remap + Bus-Vorsatz
Ein Startort ohne Schienenanschluss hat KEINEN Stop im Feed → Routing fehlt für die
wichtigste Ebene 1. Lösung: auf nächstgelegenen Bahnhof remappen (Radevormwald →
Remscheid-Lennep Bf) und einen Bus-Vorsatz (~20min) als erstes "Segment" + zur Dauer
addieren. Solche Fälle explizit als `START_REMAP = {ort: (bahnhof, bus_min)}` führen,
sonst fehlt die Ebene-1-PDF komplett.
## Unsichere/absurde Fernzeiten → "prüfen", nie falsch anzeigen
Auch RAPTOR liefert auf manchen komplexen Fernstrecken falsche Werte (z.B. >10h, wenn
Routing fehlschlug; real Regional 6–9h). Ehrlich handeln: plausible Werte behalten,
unsichere (>10h / 0.0h / leer) als **`'prüfen'` mit Kategorie `unsicher (vor Fahrt
prüfen)`** markieren, und das Template zeigt „vor Fahrt prüfen" statt einer Zahl. Das
Template muss jede unsichere Kategorie in seiner Gruppen-Map führen, sonst
verschwinden die Zeilen still.
## Empirische Referenz-Anker (Plausibilitäts-Check, Regional only)
- Trier→Saarbrücken ~55–60min (RE 1 direkt)
- Köln→Aachen ~35–50min (RE 1/RE 9)
- Köln→Frankfurt regional ~2,5–3h MIT Umstiegen (~1,5h wäre nur ICE = NICHT korrekt)
- Radevormwald→Frankfurt ~5,5h (Bus + Remscheid-Lennep + Regional)
- Verifizieren mit diesen Ankern, NIE dem Algorithmus blind vertrauen.