Zugsuche: Linie, Zugnummer oder Ort; Treffer mit Start → Ziel und nächstem Halt, nächstgelegene zuerst
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
+54
@@ -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 `<li role="option" data-i="${i}"><div class="r1"><b>${esc(p.line)}${p.nr ? " " + esc(p.nr) : ""}</b>${delayBadge(p.delay)}</div>
|
||||
<div class="r2">${esc(p.from)} → ${esc(p.to)}</div><div class="r2">nächster Halt: ${esc(p.next)}</div></li>`;
|
||||
}).join("") : '<li class="r2">kein fahrender Zug gefunden</li>';
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user