Frankreich, Niederlande, Belgien als weitere Quellen (noch im Test)
- Quellen fr (SNCF), nl (OVapi), be (GTFS.be), Kacheln für alle 5 Länder - Landesgrenzen für 5 Länder (borders.json), Gitter-Cache für die Prüfung - Echtzeit-Abgleich auch über Haltestellen-ID (FR, NL), über den Bahnhof bei Gleiswechsel - SNCF: Gattung aus der Trip-ID, Zugnummer aus dem Headsign; NL-Linien gekürzt - calendar.txt optional (SNCF hat nur calendar_dates) - OSM je Land zugeschnitten vor pfaedle; beim Zusammenführen je Objekt nur die neueste Version (time-filter) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
+45
-15
@@ -59,8 +59,15 @@ def travelled(length: float, duration: float, t: float) -> float:
|
||||
|
||||
|
||||
# Gattungen des Fernverkehrs; alles andere (RE, RB, S, R, TER, …) gilt als Nahverkehr
|
||||
LONG_DISTANCE = {"ICE", "IC", "ICN", "EC", "ECE", "ECM", "EN", "NJ", "RJ", "RJX", "TGV", "THA", "EST", "FLX",
|
||||
"IR", "PE", "D"}
|
||||
LONG_DISTANCE = {"ICE", "IC", "ICN", "ICD", "EC", "ECE", "ECM", "EN", "NJ", "RJ", "RJX", "TGV", "OUIGO", "LYRIA",
|
||||
"THA", "EST", "EUROSTAR", "EUROCITY", "NIGHTJET", "EUROPEAN", "FLX", "IR", "PE", "D"}
|
||||
# Frankreich (SNCF): Gattung steht nur in der Trip-ID ("…_F:OUI:…"), die Linie heisst oft "INCONNU"
|
||||
FR_CATEGORY = {"OUI": "TGV", "OGO": "OUIGO", "LYR": "Lyria", "ICE": "ICE", "IC": "IC", "ICN": "IC",
|
||||
"TER": "TER", "CTE": "TER", "NAV": "Navette", "TRN": "TER", "CRE": "TER"}
|
||||
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"
|
||||
|
||||
|
||||
def long_distance(cat: str, line: str) -> bool:
|
||||
@@ -76,7 +83,11 @@ class Feed:
|
||||
self.version = self.meta["feed_version"]
|
||||
|
||||
self.stop_index, self.stop_name, self.stop_lat, self.stop_lon, self.stop_platform = {}, [], [], [], []
|
||||
self.stop_group = array("i") # Bahnhof eines Halts (alle Gleise eines Bahnhofs -> gleiche Zahl)
|
||||
groups = {}
|
||||
for h, r in read(path / "stops.txt"):
|
||||
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_lat.append(float(r[h["stop_lat"]]))
|
||||
@@ -86,13 +97,12 @@ class Feed:
|
||||
agencies = {r[h["agency_id"]]: r[h["agency_name"]] for h, r in read(path / "agency.txt")}
|
||||
self.routes = {}
|
||||
for h, r in read(path / "routes.txt"):
|
||||
line = LINE_PREFIX.sub("", r[h["route_short_name"]] or r[h.get("route_long_name", 0)])
|
||||
self.routes[r[h["route_id"]]] = {
|
||||
"line": r[h["route_short_name"]] or r[h.get("route_long_name", 0)],
|
||||
"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 "",
|
||||
}
|
||||
route = self.routes[r[h["route_id"]]]
|
||||
route["fv"] = long_distance(route["cat"], route["line"])
|
||||
|
||||
self.trip_index, self.trip_ids, self.trip_route, self.trip_service = {}, [], [], []
|
||||
self.trip_headsign, self.trip_number, self.trip_shape = [], [], []
|
||||
@@ -104,6 +114,15 @@ class Feed:
|
||||
self.trip_headsign.append(r[h["trip_headsign"]] if "trip_headsign" in h else "")
|
||||
self.trip_number.append(r[h["trip_short_name"]] if "trip_short_name" in h else "")
|
||||
self.trip_shape.append(r[h["shape_id"]] if "shape_id" in h else "")
|
||||
route = self.routes[r[h["route_id"]]]
|
||||
m = FR_TRIP.search(r[h["trip_id"]]) if not route["cat"] else None
|
||||
if m:
|
||||
route["cat"] = FR_CATEGORY.get(m.group(1), m.group(1))
|
||||
if self.trip_headsign[-1].isdigit() and not self.trip_number[-1]: # SNCF: Ziel = Zugnummer
|
||||
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"])
|
||||
|
||||
# Haltezeiten flach in Arrays; off[i]..off[i+1] sind die Halte von Fahrt i
|
||||
n = len(self.trip_route)
|
||||
@@ -136,7 +155,7 @@ class Feed:
|
||||
self.shape_data = memoryview(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)).cast("f")
|
||||
|
||||
self.calendar = {}
|
||||
for h, r in read(path / "calendar.txt"):
|
||||
for h, r in read(path / "calendar.txt") if (path / "calendar.txt").exists() else (): # SNCF: nur Ausnahmen
|
||||
self.calendar[r[h["service_id"]]] = (
|
||||
[r[h[d]] == "1" for d in ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday")],
|
||||
r[h["start_date"]], r[h["end_date"]])
|
||||
@@ -226,22 +245,33 @@ class Feed:
|
||||
arr, dep = list(self.st_arr[o0:o1]), list(self.st_dep[o0:o1])
|
||||
if not upd:
|
||||
return arr, dep
|
||||
seqs = self.st_seq[o0:o1]
|
||||
by_i = {}
|
||||
seqs, stops = self.st_seq[o0:o1], self.st_stop[o0:o1]
|
||||
by_i, j = {}, 0
|
||||
for u in upd:
|
||||
i = bisect_right(seqs, u[0]) - 1
|
||||
if i >= 0 and seqs[i] == u[0]:
|
||||
by_i[i] = u
|
||||
if u[0] is not None: # Schweiz, Deutschland: über die Halt-Nummer
|
||||
i = bisect_right(seqs, u[0]) - 1
|
||||
if i >= 0 and seqs[i] == u[0]:
|
||||
by_i[i] = u
|
||||
continue
|
||||
# Frankreich, Niederlande: nur Haltestellen-ID; über den Bahnhof abgleichen, damit es
|
||||
# auch bei Gleiswechsel passt, und der Reihe nach (eine Fahrt kann einen Bahnhof zweimal haben)
|
||||
k = self.stop_index.get(u[1])
|
||||
if k is None:
|
||||
continue
|
||||
for i in range(j, len(stops)):
|
||||
if self.stop_group[stops[i]] == self.stop_group[k]:
|
||||
by_i[i], j = u, i + 1
|
||||
break
|
||||
cur, prev = None, -10 ** 9
|
||||
for i in range(len(arr)):
|
||||
u = by_i.get(i)
|
||||
if u and u[1] is not None:
|
||||
a = u[1] - ds if u[2] else arr[i] + u[1]
|
||||
if u and u[2] is not None:
|
||||
a = u[2] - ds if u[3] else arr[i] + u[2]
|
||||
cur = a - arr[i]
|
||||
else:
|
||||
a = arr[i] + (cur or 0)
|
||||
if u and u[3] is not None:
|
||||
d = u[3] - ds if u[4] else dep[i] + u[3]
|
||||
if u and u[4] is not None:
|
||||
d = u[4] - ds if u[5] else dep[i] + u[4]
|
||||
cur = d - dep[i]
|
||||
else:
|
||||
d = dep[i] + (cur or 0)
|
||||
|
||||
Reference in New Issue
Block a user