Filter: alle / Nahverkehr / Fernverkehr
Einteilung nach Gattung (Schweiz: route_desc, Deutschland: Präfix der Linie): ICE, IC, EC, ECE, EN, NJ, RJ(X), TGV, IR, … = Fernverkehr. Auswahl bleibt im Browser gespeichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
+6
-1
@@ -110,7 +110,7 @@ def test_all():
|
||||
assert len(v) == 1 and v[0]["properties"]["delay"] is None
|
||||
lon, lat = v[0]["geometry"]["coordinates"]
|
||||
assert lat < 47.0 and abs(lon - 8.05) < 0.01, (lon, lat) # halbe Fahrzeit = Umweg-Scheitelpunkt
|
||||
assert v[0]["properties"]["next"] == "Beta"
|
||||
assert v[0]["properties"]["next"] == "Beta" and v[0]["properties"]["fv"] is True # IC = Fernverkehr
|
||||
assert abs(v[0]["properties"]["heading"] - 90) < 15, v[0]["properties"]["heading"] # Scheitel: Richtung Osten
|
||||
assert f.vehicles(at(8, 11), {})[0]["properties"]["heading"] is not None # auch im Stillstand
|
||||
|
||||
@@ -150,6 +150,11 @@ def test_all():
|
||||
assert len(v) == 1 and v[0]["properties"]["to"] == "Gamma" and v[0]["properties"]["nr"] == ""
|
||||
assert de.vehicles(dt.datetime(2026, 10, 25, 8, 5, tzinfo=TZ).timestamp(), {}) == [] # calendar_dates
|
||||
|
||||
from trainpulse.feed import long_distance
|
||||
assert long_distance("", "ICE 75") and long_distance("IR", "IR36") and long_distance("", "ECE 20")
|
||||
assert not long_distance("S", "S5") and not long_distance("", "RE6") and not long_distance("", "IRE 3")
|
||||
assert not long_distance("TER", "K1") and not long_distance("", "")
|
||||
|
||||
from trainpulse.border import SHOW, in_switzerland
|
||||
assert in_switzerland(8.540, 47.378) and not in_switzerland(9.177, 47.659) # Zürich HB / Konstanz
|
||||
assert not in_switzerland(8.690, 47.697) # Büsingen (deutsche Exklave)
|
||||
|
||||
+15
-1
@@ -5,6 +5,7 @@ import datetime as dt
|
||||
import json
|
||||
import math
|
||||
import mmap
|
||||
import re
|
||||
from array import array
|
||||
from bisect import bisect_right
|
||||
from pathlib import Path
|
||||
@@ -57,6 +58,17 @@ def travelled(length: float, duration: float, t: float) -> float:
|
||||
return 0.5 * a * ta * ta + vmax * (t - ta)
|
||||
|
||||
|
||||
# Gattungen des Fernverkehrs; alles andere (RE, RB, S, R, TER, …) gilt als Nahverkehr
|
||||
LONG_DISTANCE = {"ICE", "IC", "ICN", "EC", "ECE", "ECM", "EN", "NJ", "RJ", "RJX", "TGV", "THA", "EST", "FLX",
|
||||
"IR", "PE", "D"}
|
||||
|
||||
|
||||
def long_distance(cat: str, line: str) -> bool:
|
||||
# Schweiz: Gattung in route_desc; gtfs.de: nur in der Linie ("ICE 75", "IC 3")
|
||||
m = re.match(r"[A-Za-z]+", cat or line or "")
|
||||
return bool(m) and m.group(0).upper() in LONG_DISTANCE
|
||||
|
||||
|
||||
class Feed:
|
||||
def __init__(self, path: Path, today: dt.date | None = None):
|
||||
self.path = path
|
||||
@@ -79,6 +91,8 @@ class Feed:
|
||||
"cat": r[h["route_desc"]] if "route_desc" in h else "",
|
||||
"agency": agencies.get(r[h["agency_id"]], "") if "agency_id" in h else "",
|
||||
}
|
||||
route = self.routes[r[h["route_id"]]]
|
||||
route["fv"] = long_distance(route["cat"], route["line"])
|
||||
|
||||
self.trip_index, self.trip_ids, self.trip_route, self.trip_service = {}, [], [], []
|
||||
self.trip_headsign, self.trip_number, self.trip_shape = [], [], []
|
||||
@@ -274,7 +288,7 @@ class Feed:
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "Point", "coordinates": [lon, lat]},
|
||||
"properties": {
|
||||
"id": f"{dstr}/{ti}", "line": route["line"], "cat": route["cat"],
|
||||
"id": f"{dstr}/{ti}", "line": route["line"], "cat": route["cat"], "fv": route["fv"],
|
||||
"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,
|
||||
|
||||
+14
@@ -108,6 +108,17 @@ function setMode(mode) {
|
||||
}
|
||||
for (const m of MODES) document.getElementById("btn-" + m).onclick = () => setMode(m);
|
||||
|
||||
// ---------- Filter: alle / Nahverkehr / Fernverkehr ----------
|
||||
|
||||
const FILTERS = { all: null, local: ["!", ["get", "fv"]], long: ["get", "fv"] };
|
||||
function setFilter(name) {
|
||||
if (!(name in FILTERS)) name = "all";
|
||||
for (const id of ["trains", "train-labels"]) map.setFilter(id, FILTERS[name]);
|
||||
for (const n of Object.keys(FILTERS)) document.getElementById("flt-" + n).setAttribute("aria-pressed", String(n === name));
|
||||
try { localStorage.setItem("filter", name); } catch (e) { /* privater Modus */ }
|
||||
}
|
||||
for (const n of Object.keys(FILTERS)) document.getElementById("flt-" + n).onclick = () => setFilter(n);
|
||||
|
||||
// ---------- Züge ----------
|
||||
|
||||
// Server liefert je Zug die Position jetzt und alle `step` Sekunden danach (entlang der Strecke);
|
||||
@@ -328,6 +339,9 @@ map.on("load", () => {
|
||||
let mode = "light";
|
||||
try { mode = localStorage.getItem("mode") || (localStorage.getItem("sat") === "1" ? "sat" : "light"); } catch (e) { /* egal */ }
|
||||
setMode(mode);
|
||||
let filter = "all";
|
||||
try { filter = localStorage.getItem("filter") || "all"; } catch (e) { /* egal */ }
|
||||
setFilter(filter);
|
||||
statusLoop();
|
||||
setInterval(refreshTrains, 5000);
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
<button id="btn-dark" aria-pressed="false">Dunkel</button>
|
||||
<button id="btn-sat" aria-pressed="false">Satellit</button>
|
||||
</div>
|
||||
<div class="seg" role="group" aria-label="Züge">
|
||||
<button id="flt-all" aria-pressed="true">Alle</button>
|
||||
<button id="flt-local" aria-pressed="false">Nahverkehr</button>
|
||||
<button id="flt-long" aria-pressed="false">Fernverkehr</button>
|
||||
</div>
|
||||
<ul class="legend">
|
||||
<li><i style="--c:var(--ok)"></i>pünktlich</li>
|
||||
<li><i style="--c:var(--d1)"></i>3–6′</li>
|
||||
|
||||
@@ -18,6 +18,7 @@ html, body { margin: 0; height: 100%; }
|
||||
h1 { margin: 0 0 8px; font-size: 16px; letter-spacing: .02em; }
|
||||
h1::before { content: ""; display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: var(--accent); margin-right: 7px; }
|
||||
|
||||
.seg + .seg { margin-top: 6px; }
|
||||
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
|
||||
.seg button { border: 0; background: transparent; color: var(--fg); padding: 6px 14px; font: inherit; cursor: pointer; }
|
||||
.seg button[aria-pressed="true"] { background: var(--fg); color: var(--bg); }
|
||||
|
||||
Reference in New Issue
Block a user