Streckenverläufe kompakt speichern und nur bei Bedarf lesen

pfaedle erzeugt für die Schweiz eine 2.6-GB-shapes.txt; die wird jetzt
in shapes.bin (670 MB, mmap) umgewandelt. Halt-Positionen kommen aus
shape_dist_traveled. RAM-Empfehlung im README ergänzt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
Guido
2026-10-21 15:07:14 +02:00
co-authored by Claude Opus 5.5
parent efeba65fb1
commit 0cd2c3951a
5 changed files with 74 additions and 47 deletions
+28 -38
View File
@@ -4,6 +4,7 @@ import csv
import datetime as dt
import json
import math
import mmap
from array import array
from bisect import bisect_right
from pathlib import Path
@@ -75,28 +76,28 @@ class Feed:
n = len(self.trip_route)
self.off = array("i", [0]) * (n + 1)
self.st_stop, self.st_seq, self.st_arr, self.st_dep = array("i"), array("i"), array("i"), array("i")
self.st_dist = array("f") # Meter entlang des Streckenverlaufs (von pfaedle), -1 = unbekannt
rows = {}
for h, r in read(path / "stop_times.txt"):
dist = r[h["shape_dist_traveled"]] if "shape_dist_traveled" in h else ""
rows.setdefault(self.trip_index[r[h["trip_id"]]], []).append(
(int(r[h["stop_sequence"]]), self.stop_index[r[h["stop_id"]]], hms(r[h["arrival_time"]]), hms(r[h["departure_time"]])))
(int(r[h["stop_sequence"]]), self.stop_index[r[h["stop_id"]]], hms(r[h["arrival_time"]]),
hms(r[h["departure_time"]]), float(dist) if dist else -1.0))
for ti in range(n):
self.off[ti] = len(self.st_stop)
for seq, s, a, d in sorted(rows.pop(ti, ())):
for seq, s, a, d, m in sorted(rows.pop(ti, ())):
self.st_seq.append(seq)
self.st_stop.append(s)
self.st_arr.append(a)
self.st_dep.append(d)
self.st_dist.append(m)
self.off[n] = len(self.st_stop)
self.shapes = {}
if (path / "shapes.txt").exists():
pts = {}
for h, r in read(path / "shapes.txt"):
pts.setdefault(r[h["shape_id"]], []).append(
(int(r[h["shape_pt_sequence"]]), float(r[h["shape_pt_lon"]]), float(r[h["shape_pt_lat"]])))
for sid, p in pts.items():
p.sort()
self.shapes[sid] = [(lon, lat) for _, lon, lat in p]
self.shape_index, self.shape_data = {}, None
if (path / "shapes.bin").exists() and (path / "shapes.bin").stat().st_size:
self.shape_index = json.loads((path / "shapes.json").read_text())
with open(path / "shapes.bin", "rb") as f:
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"):
@@ -145,39 +146,28 @@ class Feed:
# ---------- Geometrie ----------
def geometry(self, ti: int):
"""Streckenverlauf einer Fahrt: Punkte, kumulierte Meter, Meter-Position jedes Halts."""
"""Streckenverlauf einer Fahrt: Punkte, kumulierte Meter, Meter-Position jedes Halts.
Ohne (brauchbaren) Streckenverlauf: Luftlinie zwischen den Halten."""
o0, o1 = self.off[ti], self.off[ti + 1]
stops = tuple(self.st_stop[o0:o1])
key = (self.trip_shape[ti], stops)
key = (self.trip_shape[ti], tuple(self.st_stop[o0:o1]))
g = self._geom_cache.get(key)
if g:
return g
shape = self.shapes.get(self.trip_shape[ti])
pts = shape or [(self.stop_lon[s], self.stop_lat[s]) for s in stops]
lat0 = pts[0][1]
P = [xy(lon, lat, lat0) for lon, lat in pts]
cum = [0.0]
for (x1, y1), (x2, y2) in zip(P, P[1:]):
cum.append(cum[-1] + math.hypot(x2 - x1, y2 - y1))
if shape:
stop_d, j = [], 0
for s in stops:
px, py = xy(self.stop_lon[s], self.stop_lat[s], lat0)
best = (float("inf"), cum[j], j)
for k in range(j, len(P) - 1):
(x1, y1), (x2, y2) = P[k], P[k + 1]
dx, dy = x2 - x1, y2 - y1
seg = dx * dx + dy * dy
t = 0.0 if seg == 0 else max(0.0, min(1.0, ((px - x1) * dx + (py - y1) * dy) / seg))
dist = math.hypot(x1 + t * dx - px, y1 + t * dy - py)
if dist < best[0]:
best = (dist, cum[k] + t * (cum[k + 1] - cum[k]), k)
elif best[0] < 100 and dist > best[0] + 1000:
break # nächster Halt gefunden, nicht bis ans Streckenende weitersuchen
stop_d.append(best[1])
j = best[2]
shp = self.shape_index.get(self.trip_shape[ti])
stop_d = list(self.st_dist[o0:o1])
if shp and shp[1] >= 2 and min(stop_d) >= 0:
v = self.shape_data[shp[0] * 3:(shp[0] + shp[1]) * 3].tolist()
pts, cum = list(zip(v[0::3], v[1::3])), v[2::3]
else:
pts = [(self.stop_lon[s], self.stop_lat[s]) for s in self.st_stop[o0:o1]]
lat0 = pts[0][1]
P = [xy(lon, lat, lat0) for lon, lat in pts]
cum = [0.0]
for (x1, y1), (x2, y2) in zip(P, P[1:]):
cum.append(cum[-1] + math.hypot(x2 - x1, y2 - y1))
stop_d = cum
if len(self._geom_cache) > 20000:
self._geom_cache.clear()
g = (pts, cum, stop_d)
self._geom_cache[key] = g
return g