57 lines
4.0 KiB
Markdown
57 lines
4.0 KiB
Markdown
---
|
||
name: gtfs-timetable-routing
|
||
description: "Use when: echte Zugfahrzeiten (Nahverkehr) braucht."
|
||
version: 1.0.0
|
||
author: mehlzoerwer
|
||
platforms: [linux]
|
||
models:
|
||
default: mimo-v2-flash
|
||
related_skills: [web-hosting-deployment, pocketbase-deployment]
|
||
license: MIT
|
||
---
|
||
|
||
# 🚂 GTFS-Timetable-Routing — echte Zugverbindungen aus Fahrplandaten
|
||
|
||
## When to Use
|
||
|
||
Nutze diesen Skill, wenn **echte, verlässliche Nahverkehrs-Fahrzeiten** (von→nach per Regional-/S-Bahn, kein ICE) benötigt werden — z.B. für eine Zugverbindungs-Webseite, Produkt-Planung oder Fahrzeit-Angaben. **Kein** Luftlinien-Raten, **kein** Erfinden von Zeiten.
|
||
|
||
**Anlass (Nutzer-Korrektur):** Die Urlaub-Webseite (urlaub.perfekt-ohne-en.de) zeigte Fahrzeiten aus Luftlinien-Schätzung — komplett falsch (2. Ebene halbe Zeit, bis 3× daneben). Der Nutzer verlangte reale Zeiten (max ~1h daneben). Lösung: GTFS-Fahrplandatei lokal verarbeiten.
|
||
|
||
## Die harte Wahrheit: öffentliche Fahrplan-APIs sind zu
|
||
|
||
Bevor Zeit mit Scraping/APIs verbrannt wird — getestet (2026-08), alle scheitern programmatisch:
|
||
|
||
| Quelle | Ergebnis |
|
||
|--------|----------|
|
||
| DB-API / bahn.de | Bot-Block / OPS_BLOCKED / 403 |
|
||
| api.transport.rest | DNS/000 |
|
||
| transport.rest v6 | 503 |
|
||
| reiseauskunft.de | toter Pfad |
|
||
| VRR-EFA (efa.vrr.de V2) | 405 (nginx blockt GET+POST) |
|
||
| WebSearch-Tool | nicht konfiguriert (Firecrawl-Key fehlt) |
|
||
|
||
**Verlässlicher Weg = eine GTFS-Datei** (Regional+S-Bahn, kein ICE ist ideal: genau Deutschlandticket-Nahverkehr). Datei lokal in SQLite → Dijkstra-Router → echte Zeiten. Kosten 0 $, schnell (<1s/Verbindung nach Build).
|
||
|
||
## Arbeitsablauf
|
||
|
||
1. **GTFS-Datei beschaffen.** Nutzer liefert `.zip` (z.B. `r.und.s.bahnen.zip`, „ohne ICE/IC"). Enthält `stops.txt`, `trips.txt`, `stop_times.txt`, `calendar*.txt`, `routes.txt`. Aus /user-cache-Dokument-Pfad kopieren.
|
||
2. **Extrahieren + in SQLite laden** (`scripts/build_gtfs_db.py`). 3 Tabellen: `stops(stop_id,stop_name,lat,lon)`, `trips(trip_id,route_id,service_id)`, `st(stop_id,trip_id,seq,arr,dep)` + Indizes auf st(stop_id), st(trip_id), st(trip_id,seq).
|
||
3. **Router bauen/ausführen** (`scripts/gtfs_suche.py`, zeitbasierter Dijkstra): vorladen `deps_by_stop` und `stops_by_trip`, dann Dijkstra mit `dist[stop]=beste Ankunft` + Ziel-Abbruch.
|
||
4. **Verifizieren** gegen eine bekannte echte Strecke (z.B. Aachen Hbf→Köln Hbf ≈ 1h 12min, 1 Umstieg). Wenn plausibel → alle Ziele neu berechnen.
|
||
5. **Website/DB aktualisieren** mit den echten Zeiten (nicht erfinden).
|
||
|
||
## ⚠️ Kritische Pitfalls (alle beim echten Lauf getroffen)
|
||
|
||
1. **stops.txt-Spaltenreihenfolge** ist `stop_name, parent_station, stop_id, stop_lat, stop_lon, ...` — **stop_id ist Spalte 2, NICHT Spalte 0**; `parent_station` ist Spalte 1 (echte Gruppen-ID). Bei falschem Mapping kommen Stops mit leeren IDs / falschen Namen.
|
||
2. **trips.txt-Header** ist `route_id, service_id, trip_id` — trip_id ist Spalte 2. Import NICHT an Spalte 0 hängen.
|
||
3. **Bahnhof hat mehrere stop_ids** (z.B. Köln Hbf = 222681 UND 452576), aber nur **eine ist aktiv in stop_times**. `stops.txt`-ID ≠ notwendigerweise `stop_times`-ID. → Beim Auflösen: die ID wählen, die `COUNT(*) IN st > 0` hat.
|
||
4. **`st`-Tabelle kann mehr eindeutige trips enthalten als `trips.txt`** (110k vs 964 war ok — `st` ist Wahrheit). Zähler-Ausgaben prüfen, nicht dem trips.txt-Import trauen.
|
||
5. **Zeit-Parser**: HH:MM und HH:MM:SS tolerant parsen (`split(":")`, defaults).
|
||
6. **Dijkstra-Effizienz**: Bei vielen Trips (110k) explodiert naive Expansion → nutze `dist[stop]` + Ziel-Abbruch + `visited`. Erst nach Effizienz-Fix: Aachen→Köln in Sekunden statt Hänger.
|
||
|
||
## Bauen
|
||
|
||
Die Referenzdatei `references/gtfs-build-notes.md` enthält Schema + Dijkstra-Kern + SQL. Die Skripte `scripts/build_gtfs_db.py` und `scripts/gtfs_suche.py` sind direkt ausführbar (Fahrzeit von→nach, `<von_stop_id> <nach_stop_id> [HH:MM]`).
|
||
|
||
**Verwandt:** Bei PocketBase als Ziel echte Zeiten in DB schreiben → `pocketbase-deployment` (Schema + Filter-Quirks). Bei Web-Frontend-Einbindung → `web-hosting-deployment`. |