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

3.4 KiB
Raw Blame History

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).