From 4d70cae37248b1d2538f3f6a5896a4d7b3311073 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 09:50:44 +0200 Subject: [PATCH] =?UTF-8?q?Zugsuche:=20Linie,=20Zugnummer=20oder=20Ort;=20?= =?UTF-8?q?Treffer=20mit=20Start=20=E2=86=92=20Ziel=20und=20n=C3=A4chstem?= =?UTF-8?q?=20Halt,=20n=C3=A4chstgelegene=20zuerst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb --- trainpulse/feed.py | 2 +- web/app.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++ web/index.html | 5 +++++ web/style.css | 9 ++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/trainpulse/feed.py b/trainpulse/feed.py index 07754e7..beca1a0 100644 --- a/trainpulse/feed.py +++ b/trainpulse/feed.py @@ -386,7 +386,7 @@ class Feed: "geometry": {"type": "Point", "coordinates": [lon, lat]}, "properties": { "id": f"{dstr}/{ti}", "line": route["line"], "cat": route["cat"], "fv": route["fv"], - "nr": self.trip_number[ti], "to": self.trip_headsign[ti], + "nr": self.trip_number[ti], "from": self.stop_name[self.st_stop[o]], "to": self.trip_headsign[ti], "delay": delay if r else None, "next": self.stop_name[self.st_stop[o + i]], "heading": heading, }, diff --git a/web/app.js b/web/app.js index 1fa13f9..2109aae 100644 --- a/web/app.js +++ b/web/app.js @@ -342,6 +342,60 @@ async function showTrip(id) { { type: "Feature", properties: {}, geometry: { type: "Point", coordinates: [x.lon, x.lat] } })) }); } +// ---------- Suche ---------- +// Gleiche Linie fährt oft mehrfach gleichzeitig (S5 in Zürich, Bern, München …): darum je Treffer +// Start → Ziel und nächster Halt, die nächstgelegenen zuerst +const norm = (s) => String(s ?? "").toLowerCase().replace(/\s+/g, ""); +let hits = [], sel = -1; +function search() { + const q = norm(document.getElementById("q").value), list = document.getElementById("results"); + if (!q) { list.hidden = true; hits = []; return; } + const c = map.getCenter(), kx = Math.cos((c.lat * Math.PI) / 180); + const dist = (f) => Math.hypot((f.geometry.coordinates[0] - c.lng) * kx, f.geometry.coordinates[1] - c.lat); + hits = trains.features.map((f) => { + const p = f.properties, line = norm(p.line); + const rank = line === q || norm(p.nr) === q ? 0 : line.startsWith(q) ? 1 + : norm(p.to).includes(q) || norm(p.from).includes(q) ? 2 : -1; + return [rank, dist(f), f]; + }).filter((h) => h[0] >= 0).sort((a, b) => a[0] - b[0] || a[1] - b[1]).slice(0, 30).map((h) => h[2]); + sel = -1; + list.innerHTML = hits.length ? hits.map((f, i) => { + const p = f.properties; + return `
  • ${esc(p.line)}${p.nr ? " " + esc(p.nr) : ""}${delayBadge(p.delay)}
    +
    ${esc(p.from)} → ${esc(p.to)}
    nächster Halt: ${esc(p.next)}
  • `; + }).join("") : '
  • kein fahrender Zug gefunden
  • '; + list.hidden = false; +} +function pick(i) { + const f = hits[i]; + if (!f) return; + const now = trains.features.find((x) => x.properties.id === f.properties.id) || f; // aktuelle Position + const pressed = Object.keys(FILTERS).find((n) => document.getElementById("flt-" + n).getAttribute("aria-pressed") === "true"); + if (pressed === "local" && f.properties.fv || pressed === "long" && !f.properties.fv) setFilter("all"); // sonst unsichtbar + map.flyTo({ center: now.geometry.coordinates, zoom: Math.max(map.getZoom(), 12) }); + showTrip(f.properties.id); + document.getElementById("results").hidden = true; +} +const qEl = document.getElementById("q"); +qEl.addEventListener("input", search); +qEl.addEventListener("focus", search); +qEl.addEventListener("keydown", (e) => { + const items = document.querySelectorAll("#results li[data-i]"); + if (e.key === "ArrowDown" || e.key === "ArrowUp") { + e.preventDefault(); + if (!items.length) return; + sel = (sel + (e.key === "ArrowDown" ? 1 : -1) + items.length) % items.length; + items.forEach((li, i) => li.classList.toggle("sel", i === sel)); + items[sel].scrollIntoView({ block: "nearest" }); + } else if (e.key === "Enter") pick(sel >= 0 ? sel : 0); + else if (e.key === "Escape") { document.getElementById("results").hidden = true; qEl.blur(); } +}); +document.getElementById("results").addEventListener("click", (e) => { + const li = e.target.closest("li[data-i]"); + if (li) pick(Number(li.dataset.i)); +}); +map.on("click", () => { document.getElementById("results").hidden = true; }); + function closeTrip() { openId = null; document.getElementById("panel").hidden = true; diff --git a/web/index.html b/web/index.html index 9323168..3c2c585 100644 --- a/web/index.html +++ b/web/index.html @@ -26,6 +26,11 @@ +