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:
+28
-38
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user