83 lines
4.6 KiB
Markdown
83 lines
4.6 KiB
Markdown
# 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. |