From 9d1d93d113737e75207ccdae35c0075dc6f9d742 Mon Sep 17 00:00:00 2001 From: furyhawk Date: Mon, 1 Jun 2026 14:32:32 +0800 Subject: [PATCH] feat: implement nearby bus stop selection and display in the UI --- src/app.css | 38 +++++++++++++- src/routes/+page.svelte | 114 +++++++++++++++++++++++++++++----------- 2 files changed, 121 insertions(+), 31 deletions(-) diff --git a/src/app.css b/src/app.css index bf6573b..a94af16 100644 --- a/src/app.css +++ b/src/app.css @@ -197,6 +197,41 @@ button:hover { margin-bottom: 0.2rem; } +.nearby-stops { + margin-top: 0.85rem; +} + +.nearby-list { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.55rem; +} + +.stop-choice { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.6rem; + text-align: left; + width: 100%; + border: 1px solid #d7c3a6; + background: #fff8ea; + color: #3f3325; + padding: 0.55rem 0.7rem; +} + +.stop-choice small { + color: #846950; + font-weight: 600; + white-space: nowrap; +} + +.stop-choice.active { + border-color: #b14d14; + background: #ffe9d7; + box-shadow: inset 0 0 0 1px #d25f1f; +} + .arrivals-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto; @@ -300,7 +335,8 @@ button:hover { } .stop-meta, - .arrivals-actions { + .arrivals-actions, + .nearby-list { grid-template-columns: 1fr; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 32dd97b..0b7390b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -11,7 +11,8 @@ let loadingArrivals = false; let userLocation = null; - let nearestStop = null; + let selectedStop = null; + let nearbyStops = []; let locationMessage = "Allow location access to find your nearest bus stop."; let mapContainer; @@ -20,6 +21,7 @@ let userMarker; let stopMarker; let linkLine; + let nearbyStopMarkers = []; function setStatus(text, state = "") { statusText = text; @@ -104,9 +106,9 @@ return allStops; } - async function resolveNearestBusStop(position) { + async function resolveNearbyBusStops(position, limit = 8) { const stops = await loadAllBusStops(); - let closest = null; + const candidates = []; stops.forEach((stop) => { const lat = parseCoordinate(stop.Latitude); @@ -116,23 +118,21 @@ } const distanceMeters = haversineMeters(position.lat, position.lon, lat, lon); - if (!closest || distanceMeters < closest.distanceMeters) { - closest = { - code: stop.BusStopCode, - description: stop.Description || "Unnamed stop", - roadName: stop.RoadName || "", - lat, - lon, - distanceMeters - }; - } + candidates.push({ + code: stop.BusStopCode, + description: stop.Description || "Unnamed stop", + roadName: stop.RoadName || "", + lat, + lon, + distanceMeters + }); }); - if (!closest) { + if (candidates.length === 0) { throw new Error("No valid bus stop coordinates were returned."); } - return closest; + return candidates.sort((a, b) => a.distanceMeters - b.distanceMeters).slice(0, limit); } function getBrowserPosition() { @@ -188,9 +188,9 @@ userMarker.bindPopup("You are here"); } - if (nearestStop) { - const stopLatLng = [nearestStop.lat, nearestStop.lon]; - const stopLabel = `${nearestStop.description} (${nearestStop.code})`; + if (selectedStop) { + const stopLatLng = [selectedStop.lat, selectedStop.lon]; + const stopLabel = `${selectedStop.description} (${selectedStop.code})`; if (!stopMarker) { stopMarker = leaflet.marker(stopLatLng, { title: stopLabel }).addTo(map); } else { @@ -199,10 +199,31 @@ stopMarker.bindPopup(stopLabel); } - if (userLocation && nearestStop) { + nearbyStopMarkers.forEach((marker) => marker.remove()); + nearbyStopMarkers = []; + + nearbyStops.forEach((stop) => { + const marker = leaflet + .circleMarker([stop.lat, stop.lon], { + radius: selectedStop?.code === stop.code ? 8 : 5, + color: selectedStop?.code === stop.code ? "#d25f1f" : "#7f6a52", + fillColor: selectedStop?.code === stop.code ? "#d25f1f" : "#cbbca2", + fillOpacity: 0.85, + weight: 2 + }) + .addTo(map); + + marker.bindPopup(`${stop.description} (${stop.code})`); + marker.on("click", () => { + selectBusStop(stop, true); + }); + nearbyStopMarkers.push(marker); + }); + + if (userLocation && selectedStop) { const linePoints = [ [userLocation.lat, userLocation.lon], - [nearestStop.lat, nearestStop.lon] + [selectedStop.lat, selectedStop.lon] ]; if (!linkLine) { @@ -254,16 +275,16 @@ } async function runArrival() { - if (!nearestStop) { + if (!selectedStop) { await resolveLocationAndNearestStop(false); - if (!nearestStop) { + if (!selectedStop) { return; } } loadingArrivals = true; try { - await callArrivalEndpoint(nearestStop.code); + await callArrivalEndpoint(selectedStop.code); setStatus("Arrivals Loaded", "ok"); } catch (error) { setStatus("Arrival Error", "error"); @@ -281,6 +302,15 @@ } } + async function selectBusStop(stop, loadArrivals = false) { + selectedStop = stop; + locationMessage = `Selected stop: ${stop.description} (${stop.code})`; + await updateMap(); + if (loadArrivals) { + await runArrival(); + } + } + async function resolveLocationAndNearestStop(loadArrivals = true) { locating = true; locationMessage = "Resolving your location..."; @@ -292,9 +322,13 @@ lon: position.coords.longitude }; - locationMessage = "Finding nearest bus stop..."; - nearestStop = await resolveNearestBusStop(userLocation); - locationMessage = `Nearest stop: ${nearestStop.description} (${nearestStop.code})`; + locationMessage = "Finding nearby bus stops..."; + nearbyStops = await resolveNearbyBusStops(userLocation); + selectedStop = nearbyStops[0] || null; + if (!selectedStop) { + throw new Error("No nearby bus stop found."); + } + locationMessage = `Nearest stop found: ${selectedStop.description} (${selectedStop.code}). You can choose another nearby stop below.`; await updateMap(); setStatus("Location Ready", "ok"); @@ -356,18 +390,38 @@
- Nearest stop - {nearestStop ? `${nearestStop.code} - ${nearestStop.description}` : "Not resolved"} + Selected stop + {selectedStop ? `${selectedStop.code} - ${selectedStop.description}` : "Not resolved"}
Road - {nearestStop?.roadName || "-"} + {selectedStop?.roadName || "-"}
Distance - {nearestStop ? formatDistance(nearestStop.distanceMeters) : "-"} + {selectedStop ? formatDistance(selectedStop.distanceMeters) : "-"}
+ +
+

Nearby stops

+ {#if nearbyStops.length > 0} +
+ {#each nearbyStops as stop} + + {/each} +
+ {:else} +

No nearby stops loaded yet.

+ {/if} +