Züge springen nicht mehr zurück bei steigender Verspätung; Ausfälle und Teilausfälle (SKIPPED) berücksichtigt; POI-Symbole ausgeblendet

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-22 09:18:50 +02:00
parent 1dd39c08db
commit 811480b4d3
5 changed files with 51 additions and 18 deletions
+17 -7
View File
@@ -12,7 +12,7 @@ function centerPlace(l) {
const { "icon-image": _i, "text-variable-anchor": _a, "text-radial-offset": _o, ...rest } = l.layout;
return { ...rest, "text-anchor": "center" };
}
const prefixed = (layers, pre, extra = {}) => layers.filter((l) => l.id !== "roads_shields").map((l) => ({ ...l, id: pre + l.id,
const prefixed = (layers, pre, extra = {}) => layers.filter((l) => !["roads_shields", "pois"].includes(l.id)).map((l) => ({ ...l, id: pre + l.id,
layout: { ...centerPlace(l), visibility: "none" }, paint: { ...l.paint, ...(l.type === "symbol" ? extra : {}) } }));
const LAYERS = {
light: prefixed(basemaps.layers("osm", basemaps.namedFlavor("light"), { lang: "de" }), "l-"),
@@ -147,11 +147,15 @@ async function refreshTrains() {
const now = Number(r.headers.get("X-Now"));
const d = await r.json();
if (now) clockOffset = Date.now() / 1000 - now;
const old = new Map(trains.features.map((f) => [f.properties.id, f.properties.bearing]));
const old = new Map(trains.features.map((f) => [f.properties.id, f]));
for (const f of d.features) {
f.pts = f.path && f.path.length ? f.path : [[...f.geometry.coordinates, 0]]; // [lon, lat, s ab ts]
const b = old.get(f.properties.id);
if (b !== undefined) f.properties.bearing = b;
f.pts = f.path && f.path.length ? f.path : [[...f.geometry.coordinates, 0]]; // [lon, lat, s ab ts, m ab Fahrtbeginn]
const o = old.get(f.properties.id);
if (o) {
f.properties.bearing = o.properties.bearing;
f.geometry.coordinates = o.geometry.coordinates; // für "nicht rückwärts", siehe render()
f.shownD = o.shownD;
}
}
trains = d;
ts = d.ts;
@@ -224,6 +228,7 @@ map.addImage("tp-dot", sdfIcon(shapeMask((ctx) => {
}, 26)), { sdf: true, pixelRatio: 2 });
let lastRender = 0;
const MAX_HOLD_M = 5000; // ~1 min eines schnellen Zuges
function render() {
const t = Date.now() / 1000 - clockOffset - ts; // s seit Berechnung auf dem Server
const dtFrame = Math.min(0.2, (performance.now() - lastRender) / 1000); // nach Ruckler nicht schlagartig drehen
@@ -237,6 +242,11 @@ function render() {
while (i + 1 < p.length && p[i + 1][2] <= t) i++;
const a = p[i], b = p[Math.min(i + 1, p.length - 1)];
const k = b[2] > a[2] ? Math.max(0, Math.min(1, (t - a[2]) / (b[2] - a[2]))) : 0;
// Steigt die Verspätung (Echtzeit kommt in Minutenschritten), läge der Zug plötzlich hinter der
// gezeigten Stelle: dann warten statt zurückspringen, bis er wieder dort ist (grosse Korrekturen ausgenommen)
const d = a[3] === undefined ? undefined : a[3] + (b[3] - a[3]) * k;
if (d !== undefined && f.shownD !== undefined && d < f.shownD && f.shownD - d < MAX_HOLD_M) continue;
f.shownD = d;
f.geometry.coordinates = [a[0] + (b[0] - a[0]) * k, a[1] + (b[1] - a[1]) * k];
// Richtung aus dem aktuellen Gleisstück; steht der Zug, die vom Server (Gleis am Halt)
const target = bearing(a, b) ?? f.properties.heading;
@@ -320,10 +330,10 @@ async function showTrip(id) {
<h2>${esc(t.line)} ${esc(t.nr)} ${t.canceled ? '<span class="badge" style="background:var(--d3)">fällt aus</span>' : delayBadge(curDelay)}</h2>
<p class="sub">nach ${esc(t.to)} · ${esc(t.agency)}</p>
<ul class="stops">${t.stops.map((s) => `
<li class="${s.passed ? "passed" : ""}">
<li class="${s.passed ? "passed" : ""} ${s.skipped ? "skipped" : ""}">
<span class="t">${esc(s.arr ?? s.dep)}</span>
<span>${esc(s.name)}${s.platform ? ` <span class="pl">Gl. ${esc(s.platform)}</span>` : ""}</span>
${delayText(s.arr !== null ? s.arr_delay : s.dep_delay)}
${s.skipped ? '<span class="d" style="color:var(--d3)">fällt aus</span>' : delayText(s.arr !== null ? s.arr_delay : s.dep_delay)}
</li>`).join("")}
</ul>`;
document.getElementById("panel").hidden = false;