Züge folgen dem Gleis statt gerader Linien, Mindesthaltezeit 40 s, osmium renumber
- Server schickt pro Zug die Gleispunkte der nächsten 12 s mit Zeitstempel (gerade Stücke auf ~3 m vereinfacht); Browser fährt sie ab, Richtung aus dem aktuellen Gleisstück - Halte mit Ankunft = Abfahrt werden für die Animation auf 40 s verlängert - OSM-IDs durchnummerieren: osmium extract braucht sonst ~4 GB RAM Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
+66
-4
@@ -76,6 +76,48 @@ def long_distance(cat: str, line: str) -> bool:
|
||||
return bool(m) and m.group(0).upper() in LONG_DISTANCE
|
||||
|
||||
|
||||
MIN_DWELL = 40 # s; im Fahrplan steht oft Ankunft = Abfahrt, dann würde der Zug nie sichtbar halten
|
||||
|
||||
|
||||
def with_min_dwell(arr, dep):
|
||||
"""Halte mit weniger als MIN_DWELL Haltezeit für die Animation verlängern: etwas früher ankommen,
|
||||
etwas später abfahren – höchstens ein Viertel der Fahrzeit davor/danach."""
|
||||
pa, pd = list(arr), list(dep)
|
||||
for k in range(1, len(arr) - 1):
|
||||
short = MIN_DWELL - (dep[k] - arr[k])
|
||||
if short > 0:
|
||||
h = min(short / 2, (arr[k] - dep[k - 1]) / 4, (arr[k + 1] - dep[k]) / 4)
|
||||
if h > 0:
|
||||
pa[k], pd[k] = arr[k] - h, dep[k] + h
|
||||
return pa, pd
|
||||
|
||||
|
||||
def simplify(p: list, tol_m: float, fixed=()) -> list:
|
||||
"""Douglas-Peucker auf [lon, lat, t]-Punkten (Toleranz in Metern, grob über die Breite umgerechnet).
|
||||
Punkte in `fixed` bleiben immer erhalten."""
|
||||
if len(p) < 3:
|
||||
return p
|
||||
kx = 111320 * math.cos(math.radians(p[0][1]))
|
||||
keep = {0, len(p) - 1, *fixed}
|
||||
cuts = sorted(keep)
|
||||
stack = list(zip(cuts, cuts[1:]))
|
||||
while stack:
|
||||
a, b = stack.pop()
|
||||
(x1, y1), (x2, y2) = (p[a][0] * kx, p[a][1] * 110540), (p[b][0] * kx, p[b][1] * 110540)
|
||||
dx, dy = x2 - x1, y2 - y1
|
||||
n = math.hypot(dx, dy)
|
||||
best, idx = 0.0, -1
|
||||
for i in range(a + 1, b):
|
||||
x, y = p[i][0] * kx, p[i][1] * 110540
|
||||
d = abs(dy * x - dx * y + x2 * y1 - y2 * x1) / n if n else math.hypot(x - x1, y - y1)
|
||||
if d > best:
|
||||
best, idx = d, i
|
||||
if best > tol_m:
|
||||
keep.add(idx)
|
||||
stack += [(a, idx), (idx, b)]
|
||||
return [p[i] for i in sorted(keep)]
|
||||
|
||||
|
||||
class Feed:
|
||||
def __init__(self, path: Path, today: dt.date | None = None):
|
||||
self.path = path
|
||||
@@ -227,6 +269,23 @@ class Feed:
|
||||
self._geom_cache[key] = g
|
||||
return g
|
||||
|
||||
def track(self, ti: int, dists: list, step: float, tolerance: float = 3.0) -> list:
|
||||
"""Gleispunkte von der aktuellen Position bis zur Position in len(dists)-1 Schritten, je mit der
|
||||
Zeit (s ab jetzt), zu der der Zug dort ist. Gerade Stücke werden auf ~3 m vereinfacht."""
|
||||
pts, cum, _ = self.geometry(ti)
|
||||
out, fixed = [], []
|
||||
for k in range(len(dists)):
|
||||
d0 = dists[k]
|
||||
fixed.append(len(out)) # Zeitpunkte alle `step` s behalten, sonst ginge das Bremsen verloren
|
||||
out.append((d0, k * step))
|
||||
if k + 1 < len(dists) and dists[k + 1] > d0:
|
||||
d1 = dists[k + 1]
|
||||
for j in range(bisect_right(cum, d0), bisect_right(cum, d1)):
|
||||
if d0 < cum[j] < d1:
|
||||
out.append((cum[j], (k + (cum[j] - d0) / (d1 - d0)) * step))
|
||||
res = [[*self.point_at((pts, cum, None), d), round(t, 2)] for d, t in out]
|
||||
return simplify(res, tolerance, fixed)
|
||||
|
||||
@staticmethod
|
||||
def point_at(g, d: float):
|
||||
pts, cum, _ = g
|
||||
@@ -299,14 +358,16 @@ class Feed:
|
||||
arr, dep = self.times(ti, ds, r[1] if r else None)
|
||||
if not (dep[0] <= t <= arr[-1]):
|
||||
continue
|
||||
i = min(bisect_right(dep, t), len(dep) - 1) # Zug ist zwischen Halt i-1 und i (oder steht an Halt i)
|
||||
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]
|
||||
if t >= arr[i]: # steht am Halt i
|
||||
dist, delay = sd[i], dep[i] - self.st_dep[o + i]
|
||||
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], arr[i] - dep[i - 1], t - dep[i - 1])
|
||||
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
|
||||
@@ -323,6 +384,7 @@ class Feed:
|
||||
"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
|
||||
})
|
||||
return feats
|
||||
|
||||
|
||||
Reference in New Issue
Block a user