61 lines
3.4 KiB
Markdown
61 lines
3.4 KiB
Markdown
# GTFS Echt-Fahrplan-Routing (Nahverkehr, kein ICE)
|
||
|
||
Erfahrungen aus urlaub.perfekt-ohne-en.de (3-Ebenen-Zugsuche, Nutzer lieferte
|
||
`r.und.s.bahnen.zip` = Regional + S-Bahn, Deutschlandticket-tauglich).
|
||
Ziel war: echte Fahrzeiten statt Luftlinien-Schätzung, max ~1h Abweichung.
|
||
|
||
## Modellierung & GTFS-Gotchas
|
||
|
||
- **stops.txt kann Spalten in unerwarteter Reihenfolge/CSV haben.** Hier war der
|
||
Header: `stop_name,parent_station,stop_id,stop_lat,stop_lon,...` und `parent_station`
|
||
ist die **echte ID**, `stop_id` die Sekundär-ID. Immer per Header/Index lösen,
|
||
nie fest annehmen — die Spaltenreihenfolge weicht von der GTFS-Konvention ab.
|
||
- **Ein Bahnhof = viele stop_ids, nur EINE aktiv in stop_times.** Beispiel:
|
||
"Saarbrücken Hbf" hat 6 IDs, "Köln Hbf" 2, nur jeweils 1 davon taucht in `st`
|
||
(den Routen) auf. Löse Stops über Geo-Cluster (<350 m) zu einer Gruppe zusammen
|
||
(`parent_station` nutzen, wenn befüllt).
|
||
- **Orte ohne Bahnhof** (Radevormwald) haben keine Trips → über den nächstgelegenen
|
||
Bahnhof (Remscheid-Lennep) routen + Bus-Vorsatz (20 min) als eigenes Hinweis-Segment.
|
||
- **Namens-Matching ist fragil:** DB-Name "Freiburg im Breisgau" ≠ GTFS
|
||
"Freiburg Hauptbahnhof". Robust: kleinsten gemeinsamen Teilstring + von den Kandidaten
|
||
den mit den meisten Trips (größter Stop) wählen.
|
||
|
||
## Algorithmus: RAPTOR statt Dijkstra
|
||
|
||
Einfacher **zeit-basierter Dijkstra ist bei großen Knoten instabil** und liefert
|
||
inkonsistente Zeiten (Mainz→Berlin 11h vs. Nachbar Wiesbaden 3h). Standard-Lösung:
|
||
**RAPTOR** (Round-Based) — erweitert Reichweite Runde um Runde über echte Trips.
|
||
|
||
Kern-Ablauf RAPTOR:
|
||
1. `earliest[stop] = dep0` an allen Start-Stops (eigene Anfangszeit, KEIN Umstiegsbuffer am Einstieg).
|
||
2. Je Runde: von markierten Stops alle abfahrenden Trips nach `earliest` nehmen,
|
||
entlangfahren, `earliest[sid]` verbessern.
|
||
3. Ziel erreicht → abbrechen; sonst neue markierte Menge = neue Runde.
|
||
4. **Umstiegsbuffer** (4 min) nur bei Umstieg > dep0, nicht am Start.
|
||
|
||
### Performance
|
||
- Pro-Paar-Routing mit 17–33 Profil-Abfahrtszeiten ist langsam (Sekunden bis Minuten).
|
||
- **2x Beschleunigung:** ein einziger "Ausbreitungs"-Dijkstra/RAPTOR je **Start-Bahnhof**
|
||
erreicht alle Ziele zugleich (5000 Paare in Minuten statt Stunden).
|
||
- **Pfad-Cache:** einmalig berechnete `(von|nach) -> Segmente` als JSON cachen
|
||
(`pfade_cache.json`), damit die Web-Route/PDF nur noch liest (0,5 s statt 504-Timeout).
|
||
|
||
## Pfad-Rekonstruktion für Haltestellenliste
|
||
|
||
RAPTOR so ertüchtigen, dass er `parent[stop] = (trip_id, einstieg_stop)` merkt und
|
||
rückwärts den kompletten Weg (alle Zwischenhalte je Zugsegment) aufbaut. Benötigt,
|
||
um in der PDF die vollständige Haltestellenliste zu zeigen (Nutzerwunsch: nicht nur
|
||
Start/Ziel, sondern alle Halte aus der GTFS-Datei).
|
||
|
||
## Fallstricke & Lektionen
|
||
|
||
- **Kein Vertrauen in eine einzige Verifikation:** Zeiten immer gegen 2–3 bekannte
|
||
Referenzstrecken prüfen (Trier→Saarbrücken ~1 h, Köln→Aachen ~50 min, Köln→Frankfurt
|
||
Regional ~2,5–3 h). "Skurrile" Werte (11 h für Nachbarstädte) deuten auf einen
|
||
Routing-/Auflösungs-Fehler, nicht auf echte Fahrzeit.
|
||
- **Fehlende/fehlerhafte Werte nicht erfinden,** sondern ehrlich als
|
||
"vor Fahrt prüfen" markieren. Eine Webseite mit "prüfen"-Labels ist besser als eine
|
||
mit falschen Zahlen (Ehrlichkeits-Regel, siehe deutsche-bahn-timetable-data.md).
|
||
- **Batch-Schreibroutinen:** erst validieren/löschen, dann neu einfügen (idempotent);
|
||
DB vor jedem Lauf sichern (Backup-Regel).
|