From 276e73412abef28c222c52a84334d49f5e414f3c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:39:08 +0100 Subject: Use consolidated data API in map sheet with shared card component (#100) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: arielcostas <94913521+arielcostas@users.noreply.github.com> Co-authored-by: Ariel Costas Guerrero --- src/frontend/app/components/StopMapSheet.tsx | 151 ++++++++++++++++++++++----- 1 file changed, 122 insertions(+), 29 deletions(-) (limited to 'src/frontend/app/components/StopMapSheet.tsx') diff --git a/src/frontend/app/components/StopMapSheet.tsx b/src/frontend/app/components/StopMapSheet.tsx index e87e8c8..b3a1666 100644 --- a/src/frontend/app/components/StopMapSheet.tsx +++ b/src/frontend/app/components/StopMapSheet.tsx @@ -1,6 +1,10 @@ import maplibregl from "maplibre-gl"; import React, { useEffect, useMemo, useRef, useState } from "react"; -import Map, { AttributionControl, Marker, type MapRef } from "react-map-gl/maplibre"; +import Map, { + AttributionControl, + Marker, + type MapRef, +} from "react-map-gl/maplibre"; import { useApp } from "~/AppContext"; import { getLineColor } from "~/data/LineColors"; import type { RegionId } from "~/data/RegionConfig"; @@ -53,24 +57,38 @@ export const StopMap: React.FC = ({ const lat2 = (b.lat * Math.PI) / 180; const sinDLat = Math.sin(dLat / 2); const sinDLon = Math.sin(dLon / 2); - const h = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon; + const h = + sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon; return 2 * R * Math.asin(Math.min(1, Math.sqrt(h))); }; const computeFocusPoints = (): Pt[] => { const buses: Pt[] = []; for (const c of busPositions) { - if (c.currentPosition) buses.push({ lat: c.currentPosition.latitude, lon: c.currentPosition.longitude }); + if (c.currentPosition) + buses.push({ + lat: c.currentPosition.latitude, + lon: c.currentPosition.longitude, + }); } - const stopPt = stop.latitude && stop.longitude ? { lat: stop.latitude, lon: stop.longitude } : null; - const userPt = userPosition ? { lat: userPosition.latitude, lon: userPosition.longitude } : null; + const stopPt = + stop.latitude && stop.longitude + ? { lat: stop.latitude, lon: stop.longitude } + : null; + const userPt = userPosition + ? { lat: userPosition.latitude, lon: userPosition.longitude } + : null; if (buses.length === 0 && !stopPt && !userPt) return []; // Choose anchor for proximity: stop > user > average of buses let anchor: Pt | null = stopPt || userPt || null; if (!anchor && buses.length) { - let lat = 0, lon = 0; - for (const b of buses) { lat += b.lat; lon += b.lon; } + let lat = 0, + lon = 0; + for (const b of buses) { + lat += b.lat; + lon += b.lon; + } anchor = { lat: lat / buses.length, lon: lon / buses.length }; } @@ -122,7 +140,7 @@ export const StopMap: React.FC = ({ }); }, () => {}, - { enableHighAccuracy: true, maximumAge: 15000, timeout: 5000 }, + { enableHighAccuracy: true, maximumAge: 15000, timeout: 5000 } ); geoWatchId.current = navigator.geolocation.watchPosition( (pos) => { @@ -133,7 +151,7 @@ export const StopMap: React.FC = ({ }); }, () => {}, - { enableHighAccuracy: true, maximumAge: 30000, timeout: 10000 }, + { enableHighAccuracy: true, maximumAge: 30000, timeout: 10000 } ); } catch {} return () => { @@ -158,7 +176,7 @@ export const StopMap: React.FC = ({ const busPositions = useMemo( () => circulations.filter((c) => !!c.currentPosition), - [circulations], + [circulations] ); // Fit bounds to stop + buses, with ~1km padding each side, with a modest animation @@ -185,13 +203,17 @@ export const StopMap: React.FC = ({ const bounds = new maplibregl.LngLatBounds(sw, ne); // Determine predominant bus quadrant relative to stop to bias padding. - const padding: number | { top: number; right: number; bottom: number; left: number } = 24; + const padding: + | number + | { top: number; right: number; bottom: number; left: number } = 24; // If the diagonal is huge (likely outliers sneaked in), clamp via zoom fallback try { if (points.length === 1) { const only = points[0]; - mapRef.current.getMap().jumpTo({ center: [only.lon, only.lat], zoom: 16 }); + mapRef.current + .getMap() + .jumpTo({ center: [only.lon, only.lat], zoom: 16 }); } else { mapRef.current.fitBounds(bounds, { padding: padding as any, @@ -208,7 +230,10 @@ export const StopMap: React.FC = ({ const pts = computeFocusPoints(); if (pts.length === 0) return; - let minLat = pts[0].lat, maxLat = pts[0].lat, minLon = pts[0].lon, maxLon = pts[0].lon; + let minLat = pts[0].lat, + maxLat = pts[0].lat, + minLon = pts[0].lon, + maxLon = pts[0].lon; for (const p of pts) { if (p.lat < minLat) minLat = p.lat; if (p.lat > maxLat) maxLat = p.lat; @@ -220,14 +245,22 @@ export const StopMap: React.FC = ({ const ne = [maxLon, maxLat] as [number, number]; const bounds = new maplibregl.LngLatBounds(sw, ne); - const padding: number | { top: number; right: number; bottom: number; left: number } = 24; + const padding: + | number + | { top: number; right: number; bottom: number; left: number } = 24; try { if (pts.length === 1) { const only = pts[0]; - mapRef.current.getMap().easeTo({ center: [only.lon, only.lat], zoom: 16, duration: 450 }); + mapRef.current + .getMap() + .easeTo({ center: [only.lon, only.lat], zoom: 16, duration: 450 }); } else { - mapRef.current.fitBounds(bounds, { padding: padding as any, duration: 500, maxZoom: 17 } as any); + mapRef.current.fitBounds(bounds, { + padding: padding as any, + duration: 500, + maxZoom: 17, + } as any); } } catch {} }; @@ -256,15 +289,36 @@ export const StopMap: React.FC = ({ {/* Stop marker (center) */} {stop.latitude && stop.longitude && ( - +
- - + + - + @@ -274,7 +328,11 @@ export const StopMap: React.FC = ({ {/* User position marker (if available) */} {userPosition && ( - +
@@ -290,7 +348,12 @@ export const StopMap: React.FC = ({ const gaps: number[] = new Array(busPositions.length).fill(baseGap); if (map && zoom >= 14.5 && busPositions.length > 1) { const pts = busPositions.map((c) => - c.currentPosition ? map.project([c.currentPosition.longitude, c.currentPosition.latitude]) : null, + c.currentPosition + ? map.project([ + c.currentPosition.longitude, + c.currentPosition.latitude, + ]) + : null ); for (let i = 0; i < pts.length; i++) { const pi = pts[i]; @@ -314,7 +377,12 @@ export const StopMap: React.FC = ({ const showLabel = zoom >= 13; const labelGap = gaps[idx] ?? baseGap; return ( - +
= ({ width="20" height="20" viewBox="0 0 24 24" - style={{ filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.3))" }} + style={{ + filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.3))", + }} > - + {showLabel && (
= ({ )} {/* Floating controls */}
-
-- cgit v1.3