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