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