Trams und U-Bahnen (CH, DE, NL): eigener Filter, ab Stadtzoom im Ausschnitt; NL-Echtzeit auch für Tram/Metro; Vorausschau ohne 7-fache Neuberechnung; deutsche Steig-Zusätze aus Haltestellennamen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
+30
-7
@@ -68,6 +68,20 @@ FR_TRIP = re.compile(r"_F:([A-Z]+):")
|
||||
# Niederlande (OVapi): lange Gattungsnamen kürzen, damit die Beschriftung auf der Karte passt
|
||||
LINE_ALIAS = {"Intercity": "IC", "Intercity direct": "ICD", "Sprinter": "SPR", "Nightjet": "NJ", "Eurocity": "EC"}
|
||||
LINE_PREFIX = re.compile(r"^(Stoptrein|Sneltrein) (?=\S)") # "Stoptrein RS4" -> "RS4"
|
||||
# Steige im deutschen Fahrplan: "S+U Rathaus Spandau Bstggl.2", "Zingster Str., Ausstieg Gl.3-5"
|
||||
STOP_SUFFIX = re.compile(r",? (Ausstieg\b.*|Bstg\S*|Gl\.\s?\S+)$")
|
||||
|
||||
|
||||
def route_mode(route_type: str) -> str | None:
|
||||
"""rail, tram, subway – oder None (Bus, Schiff, Seilbahn …). GTFS-Grundtypen und "extended route types"."""
|
||||
t = int(route_type)
|
||||
if t == 2 or 100 <= t <= 199:
|
||||
return "rail"
|
||||
if t == 0 or 900 <= t <= 906:
|
||||
return "tram"
|
||||
if t == 1 or 400 <= t <= 404:
|
||||
return "subway"
|
||||
return None
|
||||
|
||||
|
||||
def long_distance(cat: str, line: str) -> bool:
|
||||
@@ -131,7 +145,7 @@ class Feed:
|
||||
parent = r[h["parent_station"]] if "parent_station" in h else ""
|
||||
self.stop_group.append(groups.setdefault(parent or r[h["stop_id"]], len(groups)))
|
||||
self.stop_index[r[h["stop_id"]]] = len(self.stop_name)
|
||||
self.stop_name.append(r[h["stop_name"]])
|
||||
self.stop_name.append(STOP_SUFFIX.sub("", r[h["stop_name"]]))
|
||||
self.stop_lat.append(float(r[h["stop_lat"]]))
|
||||
self.stop_lon.append(float(r[h["stop_lon"]]))
|
||||
self.stop_platform.append(r[h["platform_code"]] if "platform_code" in h else "")
|
||||
@@ -144,6 +158,7 @@ class Feed:
|
||||
"line": LINE_ALIAS.get(line, "" if line == "INCONNU" else line),
|
||||
"cat": r[h["route_desc"]] if "route_desc" in h else "",
|
||||
"agency": agencies.get(r[h["agency_id"]], "") if "agency_id" in h else "",
|
||||
"mode": route_mode(r[h["route_type"]]) or "rail",
|
||||
}
|
||||
|
||||
self.trip_index, self.trip_ids, self.trip_route, self.trip_service = {}, [], [], []
|
||||
@@ -164,7 +179,7 @@ class Feed:
|
||||
self.trip_number[-1], self.trip_headsign[-1] = self.trip_headsign[-1], ""
|
||||
for route in self.routes.values():
|
||||
route["line"] = route["line"] or route["cat"]
|
||||
route["fv"] = long_distance(route["cat"], route["line"])
|
||||
route["fv"] = route["mode"] == "rail" and long_distance(route["cat"], route["line"])
|
||||
|
||||
# Haltezeiten flach in Arrays; off[i]..off[i+1] sind die Halte von Fahrt i
|
||||
n = len(self.trip_route)
|
||||
@@ -344,7 +359,9 @@ class Feed:
|
||||
|
||||
# ---------- Positionen ----------
|
||||
|
||||
def vehicles(self, now: float, rt: dict):
|
||||
def vehicles(self, now: float, rt: dict, ahead: tuple = ()):
|
||||
"""Fahrzeuge zur Zeit now. Mit ahead (Sekunden) zusätzlich "_ahead": Strecken-Meter zu now+s,
|
||||
solange der Zug fährt – ohne den Fahrplan dafür jedes Mal neu durchzurechnen."""
|
||||
feats = []
|
||||
today = dt.datetime.fromtimestamp(now, TZ).date()
|
||||
for d in (today - dt.timedelta(days=1), today):
|
||||
@@ -365,15 +382,20 @@ class Feed:
|
||||
if not live or not (dep[live[0]] <= t <= arr[live[-1]]):
|
||||
continue
|
||||
pa, pd = with_min_dwell(arr, dep) # nur für die Position; Zeiten/Verspätung bleiben echt
|
||||
i = min(bisect_right(pd, t), len(pd) - 1) # Zug ist zwischen Halt i-1 und i (oder steht an Halt i)
|
||||
g = self.geometry(ti)
|
||||
sd = g[2]
|
||||
o = self.off[ti]
|
||||
|
||||
def at(tt): # -> (Halt i, Strecken-Meter); Zug zwischen Halt i-1 und i oder steht an i
|
||||
i = min(bisect_right(pd, tt), len(pd) - 1)
|
||||
if tt >= pa[i]:
|
||||
return i, sd[i]
|
||||
return i, sd[i - 1] + travelled(sd[i] - sd[i - 1], pa[i] - pd[i - 1], tt - pd[i - 1])
|
||||
|
||||
i, dist = at(t)
|
||||
if t >= pa[i]: # steht am Halt i
|
||||
dist = sd[i]
|
||||
delay = dep[i] - self.st_dep[o + i] if t >= arr[i] else arr[i] - self.st_arr[o + i]
|
||||
else:
|
||||
dist = sd[i - 1] + travelled(sd[i] - sd[i - 1], pa[i] - pd[i - 1], t - pd[i - 1])
|
||||
delay = arr[i] - self.st_arr[o + i]
|
||||
lon, lat = self.point_at(g, dist)
|
||||
# Richtung aus dem Gleisverlauf (±25 m), stimmt auch im Stillstand
|
||||
@@ -385,12 +407,13 @@ class Feed:
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "Point", "coordinates": [lon, lat]},
|
||||
"properties": {
|
||||
"id": f"{dstr}/{ti}", "line": route["line"], "cat": route["cat"], "fv": route["fv"],
|
||||
"id": f"{dstr}/{ti}", "line": route["line"], "cat": route["cat"], "fv": route["fv"], "mode": route["mode"],
|
||||
"nr": self.trip_number[ti], "from": self.stop_name[self.st_stop[o]], "to": self.trip_headsign[ti],
|
||||
"delay": delay if r else None, "next": self.stop_name[self.st_stop[o + i]],
|
||||
"heading": heading,
|
||||
},
|
||||
"_ti": ti, "_dist": dist, # intern, für track(); werden vor dem Versand entfernt
|
||||
"_ahead": [at(t + s)[1] for s in ahead if t + s <= arr[live[-1]]],
|
||||
})
|
||||
return feats
|
||||
|
||||
|
||||
Reference in New Issue
Block a user