Richtung aus dem Gleisverlauf statt aus der Bewegung, sanftes Bremsen/Anfahren an Halten

- Server berechnet den Kurs aus der Strecke (±25 m) an der Zugposition;
  stimmt dadurch auch im Stillstand (vorher blieb der Pfeil verdreht stehen)
- Trapezprofil: ~150 m vor/nach jedem Halt bremst bzw. beschleunigt der Zug,
  Ankunfts- und Abfahrtszeiten bleiben gleich

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
Claude
2026-10-21 20:45:12 +02:00
parent 71d71dabcb
commit a064fccb23
4 changed files with 46 additions and 14 deletions
+26 -2
View File
@@ -38,6 +38,25 @@ def xy(lon, lat, lat0): # lokale Projektion in Metern, reicht für Abstände en
return lon * 111320 * math.cos(math.radians(lat0)), lat * 110540
BRAKE_M = 150 # so viele Meter vor/nach einem Halt bremst bzw. beschleunigt der Zug
def travelled(length: float, duration: float, t: float) -> float:
"""Zurückgelegte Meter auf einem Abschnitt: sanft anfahren, konstant fahren, sanft bremsen
(trapezförmiges Geschwindigkeitsprofil), statt mit voller Geschwindigkeit in den Halt zu fahren."""
if length <= 0 or duration <= 0:
return 0.0
t = max(0.0, min(t, duration))
ta = min(duration / 2, 2 * BRAKE_M * duration / (length + 2 * BRAKE_M)) # Zeit fürs Anfahren/Bremsen
vmax = length / (duration - ta)
a = vmax / ta
if t < ta:
return 0.5 * a * t * t
if t > duration - ta:
return length - 0.5 * a * (duration - t) ** 2
return 0.5 * a * ta * ta + vmax * (t - ta)
class Feed:
def __init__(self, path: Path, today: dt.date | None = None):
self.path = path
@@ -240,9 +259,13 @@ class Feed:
if t >= arr[i]: # steht am Halt i
dist, delay = sd[i], dep[i] - self.st_dep[o + i]
else:
f = (t - dep[i - 1]) / max(1, arr[i] - dep[i - 1])
dist, delay = sd[i - 1] + f * (sd[i] - sd[i - 1]), arr[i] - self.st_arr[o + i]
dist = sd[i - 1] + travelled(sd[i] - sd[i - 1], arr[i] - dep[i - 1], t - dep[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
(x1, y1), (x2, y2) = self.point_at(g, dist - 25), self.point_at(g, dist + 25)
dx, dy = (x2 - x1) * math.cos(math.radians(lat)), y2 - y1
heading = round(math.degrees(math.atan2(dx, dy)), 1) if dx or dy else None
route = self.routes[self.trip_route[ti]]
feats.append({
"type": "Feature",
@@ -251,6 +274,7 @@ class Feed:
"id": f"{dstr}/{ti}", "line": route["line"], "cat": route["cat"],
"nr": self.trip_number[ti], "to": self.trip_headsign[ti],
"delay": delay if r else None, "next": self.stop_name[self.st_stop[o + i]],
"heading": heading,
},
})
return feats
+2 -1
View File
@@ -84,7 +84,8 @@ def compute_positions():
return
now = time.time()
feats = S.feed.vehicles(now, S.rt)
future = [{f["properties"]["id"]: f["geometry"]["coordinates"] for f in S.feed.vehicles(now + STEP * k, S.rt)}
future = [{f["properties"]["id"]: f["geometry"]["coordinates"] + [f["properties"]["heading"]]
for f in S.feed.vehicles(now + STEP * k, S.rt)}
for k in range(1, STEPS + 1)]
for f in feats:
path = []