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:
+21
-13
@@ -123,10 +123,10 @@ for (const n of Object.keys(FILTERS)) document.getElementById("flt-" + n).onclic
|
||||
|
||||
// ---------- Züge ----------
|
||||
|
||||
// Server liefert je Zug die Position jetzt und alle `step` Sekunden danach (entlang der Strecke);
|
||||
// dazwischen wird gefahren, und die Spitze dreht sich weich in die jeweilige Richtung.
|
||||
// Server liefert je Zug die Gleispunkte der nächsten ~10 s, jeweils mit der Zeit, zu der der Zug dort ist;
|
||||
// der Browser fährt sie ab, die Spitze dreht sich weich in die Richtung des aktuellen Gleisstücks.
|
||||
let trains = { type: "FeatureCollection", features: [] };
|
||||
let ts = 0, step = 2, clockOffset = 0;
|
||||
let ts = 0, clockOffset = 0;
|
||||
|
||||
let ready = false; // erst Züge zeigen, wenn der Server fertige Daten (inkl. Echtzeit) hat
|
||||
|
||||
@@ -140,19 +140,26 @@ async function refreshTrains() {
|
||||
if (now) clockOffset = Date.now() / 1000 - now;
|
||||
const old = new Map(trains.features.map((f) => [f.properties.id, f.properties.bearing]));
|
||||
for (const f of d.features) {
|
||||
f.pts = [[...f.geometry.coordinates, f.properties.heading], ...(f.path || [])];
|
||||
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;
|
||||
}
|
||||
trains = d;
|
||||
ts = d.ts;
|
||||
step = d.step || 2;
|
||||
render(); // sofort auf die aktuelle Position, nicht erst beim nächsten Bild
|
||||
} catch (e) { /* nächster Versuch in ein paar Sekunden */ }
|
||||
}
|
||||
|
||||
// kürzester Drehwinkel von a nach b in Grad (-180..180)
|
||||
const turn = (a, b) => ((((b - a) % 360) + 540) % 360) - 180;
|
||||
|
||||
// Richtung von a nach b in Grad (0 = Norden), in Mercator wie die Karte; null wenn (fast) gleicher Punkt
|
||||
function bearing(a, b) {
|
||||
const y = (lat) => Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360));
|
||||
const dx = ((b[0] - a[0]) * Math.PI) / 180, dy = y(b[1]) - y(a[1]);
|
||||
return Math.hypot(dx, dy) < 1e-7 ? null : (Math.atan2(dx, dy) * 180) / Math.PI;
|
||||
}
|
||||
|
||||
// SDF-Icons, damit MapLibre sie nach Verspätung einfärben und mit Rand zeichnen kann
|
||||
const S = 64, R = 8;
|
||||
function sdfIcon(mask) {
|
||||
@@ -209,21 +216,22 @@ map.addImage("tp-dot", sdfIcon(shapeMask((ctx) => {
|
||||
|
||||
let lastRender = 0;
|
||||
function render() {
|
||||
const t = Date.now() / 1000 - clockOffset - ts;
|
||||
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
|
||||
lastRender = performance.now();
|
||||
const ease = 1 - Math.exp(-dtFrame * 6); // Richtung folgt in ~0,3 s
|
||||
for (const f of trains.features) {
|
||||
const p = f.pts;
|
||||
if (!p) continue;
|
||||
const x = Math.max(0, t / step);
|
||||
const i = Math.min(Math.floor(x), p.length - 1);
|
||||
const a = p[i], b = p[Math.min(i + 1, p.length - 1)], k = i + 1 < p.length ? x - i : 0;
|
||||
// Gleispunkte mit Zeitstempel: Abschnitt suchen, in dem der Zug gerade ist, und darauf fahren
|
||||
let i = 0;
|
||||
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;
|
||||
f.geometry.coordinates = [a[0] + (b[0] - a[0]) * k, a[1] + (b[1] - a[1]) * k];
|
||||
// Richtung kommt vom Server (Gleisverlauf an der Position); dazwischen interpolieren und weich nachdrehen
|
||||
const ha = a[2] ?? b[2], hb = b[2] ?? a[2];
|
||||
if (ha === null || ha === undefined) continue;
|
||||
const target = ha + turn(ha, hb) * k;
|
||||
// Richtung aus dem aktuellen Gleisstück; steht der Zug, die vom Server (Gleis am Halt)
|
||||
const target = bearing(a, b) ?? f.properties.heading;
|
||||
if (target === null || target === undefined) continue;
|
||||
const cur = f.properties.bearing;
|
||||
f.properties.bearing = cur === undefined ? target : cur + turn(cur, target) * ease;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user