WIP: Deutschland als zweite Datenquelle (gtfs.de)

- mehrere Quellen (ch, de) mit je eigenem Fahrplan und Echtzeit-Feed
- OSM CH+DE, mit osmium auf Schienennetz gefiltert, für pfaedle
- Landesgrenze CH gegen doppelte grenzüberschreitende Züge
- Import robust gegen andere Spaltenreihenfolge (gtfs.de)
- Kacheln für CH+DE (Zoom 14)

Noch nicht vollständig getestet (pfaedle auf Deutschland läuft).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
Claude
2026-10-21 21:27:54 +02:00
parent 2e3c10a6c4
commit 2a4962c379
11 changed files with 370 additions and 168 deletions
+32 -5
View File
@@ -1,5 +1,7 @@
"""Selbsttest mit einem Mini-Fahrplan: python -m pytest tests (oder: python tests/test_core.py)"""
import csv
import datetime as dt
import io
import json
import sys
import tempfile
@@ -36,14 +38,29 @@ SHAPE = "shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_travele
DAY = dt.date(2026, 10, 21)
def make_feed(tmp: Path, shapes=True) -> Feed:
def german_style(files: dict) -> dict:
"""Wie gtfs.de: Spalten in anderer Reihenfolge, kein Fahrtziel/keine Zugnummer, Version 'latest-free'."""
out = {}
for n, c in files.items():
rows = list(csv.reader(io.StringIO(c)))
if n == "trips.txt":
rows = [r[:3] for r in rows] # route_id, service_id, trip_id
if n == "feed_info.txt":
rows[1][1] = "latest-free"
rows = [r[::-1] for r in rows] # Spalten rückwärts
out[n] = "".join(",".join(r) + "\n" for r in rows)
return out
def make_feed(tmp: Path, shapes=True, files=FILES) -> Feed:
tmp.mkdir(parents=True, exist_ok=True)
zp = tmp / "in.zip"
with zipfile.ZipFile(zp, "w") as z:
for n, c in FILES.items():
z.writestr(n, "" + c) # wie die echten Schweizer Dateien: mit BOM
meta = prepare.filter_rail(zp, tmp / "rail")
assert meta["feed_version"] == "20261017" and meta["trips"] == 1 and meta["stop_times"] == 3, meta
for n, c in files.items():
z.writestr(n, "\ufeff" + c) # wie die echten Schweizer Dateien: mit BOM
meta = prepare.filter_rail(zp, tmp / "rail", "20261020")
assert meta["feed_version"] == ("20261017" if files is FILES else "20261020"), meta
assert meta["trips"] == 1 and meta["stop_times"] == 3, meta
if shapes:
(tmp / "rail" / "shapes.txt").write_text(SHAPE)
prepare.pack_shapes(tmp / "rail")
@@ -127,6 +144,16 @@ def test_all():
g = make_feed(Path(d) / "noshape", shapes=False) # ohne shapes: Luftlinie
assert g.vehicles(at(8, 5), {})[0]["geometry"]["coordinates"] == [8.05, 47.0]
de = make_feed(Path(d) / "de", shapes=False, files=german_style(FILES)) # Format von gtfs.de
v = de.vehicles(at(8, 5), {})
assert len(v) == 1 and v[0]["properties"]["to"] == "Gamma" and v[0]["properties"]["nr"] == ""
assert de.vehicles(dt.datetime(2026, 10, 25, 8, 5, tzinfo=TZ).timestamp(), {}) == [] # calendar_dates
from trainpulse.border import SHOW, in_switzerland
assert in_switzerland(8.540, 47.378) and not in_switzerland(9.177, 47.659) # Zürich HB / Konstanz
assert not in_switzerland(8.690, 47.697) # Büsingen (deutsche Exklave)
assert SHOW["ch"](8.54, 47.378) and not SHOW["de"](8.54, 47.378)
print("alle Tests ok")