diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-19 13:06:27 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-19 13:06:27 +0100 |
| commit | 2a9aca302485bc08f5b2dd2a54987de6f80fc338 (patch) | |
| tree | 38171abad21b2952eca6ff9e8534545b4c28ed12 /src/frontend/app/routes | |
| parent | 37cdb0c418a7f2b47e40ae9db7ad86e1fddc86fe (diff) | |
Implement loading stops as tiles from OTP
Diffstat (limited to 'src/frontend/app/routes')
| -rw-r--r-- | src/frontend/app/routes/home.tsx | 2 | ||||
| -rw-r--r-- | src/frontend/app/routes/map.tsx | 142 | ||||
| -rw-r--r-- | src/frontend/app/routes/planner.tsx | 10 | ||||
| -rw-r--r-- | src/frontend/app/routes/stops-$id.tsx | 7 |
4 files changed, 77 insertions, 84 deletions
diff --git a/src/frontend/app/routes/home.tsx b/src/frontend/app/routes/home.tsx index e97659a..36565bd 100644 --- a/src/frontend/app/routes/home.tsx +++ b/src/frontend/app/routes/home.tsx @@ -31,7 +31,7 @@ export default function StopList() { () => new Fuse(data || [], { threshold: 0.3, - keys: ["name.original", "name.intersect", "stopId"], + keys: ["name", "stopId"], }), [data] ); diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx index 39fc062..db9de59 100644 --- a/src/frontend/app/routes/map.tsx +++ b/src/frontend/app/routes/map.tsx @@ -1,8 +1,7 @@ -import StopDataProvider, { type Stop } from "../data/StopDataProvider"; +import StopDataProvider from "../data/StopDataProvider"; import "./map.css"; import { DEFAULT_STYLE, loadStyle } from "app/maps/styleloader"; -import type { Feature as GeoJsonFeature, Point } from "geojson"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Map, { @@ -16,8 +15,11 @@ import Map, { } from "react-map-gl/maplibre"; import { useNavigate } from "react-router"; import { PlannerOverlay } from "~/components/PlannerOverlay"; -import { StopSheet } from "~/components/StopSummarySheet"; -import { REGION_DATA } from "~/config/RegionConfig"; +import { + StopSheet, + type StopSheetProps, +} from "~/components/map/StopSummarySheet"; +import { APP_CONSTANTS } from "~/config/constants"; import { usePageTitle } from "~/contexts/PageTitleContext"; import { usePlanner } from "~/hooks/usePlanner"; import { useApp } from "../AppContext"; @@ -28,19 +30,9 @@ export default function StopMap() { const { t } = useTranslation(); const navigate = useNavigate(); usePageTitle(t("navbar.map", "Mapa")); - const [stops, setStops] = useState< - GeoJsonFeature< - Point, - { - stopId: string; - name: string; - lines: string[]; - cancelled?: boolean; - prefix: string; - } - >[] - >([]); - const [selectedStop, setSelectedStop] = useState<Stop | null>(null); + const [selectedStop, setSelectedStop] = useState< + StopSheetProps["stop"] | null + >(null); const [isSheetOpen, setIsSheetOpen] = useState(false); const { mapState, updateMapState, theme } = useApp(); const mapRef = useRef<MapRef>(null); @@ -63,46 +55,11 @@ export default function StopMap() { return; } const feature = features[0]; - console.debug("Map click feature:", feature); - const props: any = feature.properties; handlePointClick(feature); }; useEffect(() => { - StopDataProvider.getStops().then((data) => { - const features: GeoJsonFeature< - Point, - { - stopId: string; - name: string; - lines: string[]; - cancelled?: boolean; - prefix: string; - } - >[] = data.map((s) => ({ - type: "Feature", - geometry: { - type: "Point", - coordinates: [s.longitude as number, s.latitude as number], - }, - properties: { - stopId: s.stopId, - name: s.name.original, - lines: s.lines, - cancelled: s.cancelled ?? false, - prefix: s.stopId.startsWith("renfe:") - ? "stop-renfe" - : s.cancelled - ? "stop-vitrasa-cancelled" - : "stop-vitrasa", - }, - })); - setStops(features); - }); - }, []); - - useEffect(() => { //const styleName = "carto"; const styleName = "openfreemap"; loadStyle(styleName, theme) @@ -166,26 +123,29 @@ export default function StopMap() { const handlePointClick = (feature: any) => { const props: any = feature.properties; - if (!props || !props.stopId) { + // TODO: Move ID to constant, improve type checking + if (!props || feature.layer.id !== "stops") { console.warn("Invalid feature properties:", props); return; } - const stopId = props.stopId; + const stopId = props.id; - // fetch full stop to get lines array - StopDataProvider.getStopById(stopId) - .then((stop) => { - if (!stop) { - console.warn("Stop not found:", stopId); - return; - } - setSelectedStop(stop); - setIsSheetOpen(true); - }) - .catch((err) => { - console.error("Error fetching stop details:", err); - }); + console.debug("Stop clicked:", stopId, props); + + setSelectedStop({ + stopId: props.id, + stopCode: props.code, + name: props.name || "Unknown Stop", + lines: JSON.parse(props.routes || "[]").map((route) => { + return { + line: route.shortName, + colour: route.colour, + textColour: route.textColour, + }; + }), + }); + setIsSheetOpen(true); }; return ( @@ -203,7 +163,7 @@ export default function StopMap() { style={{ width: "100%", height: "100%" }} interactiveLayerIds={["stops", "stops-label"]} onClick={onMapClick} - minZoom={11} + minZoom={5} scrollZoom pitch={0} roll={0} @@ -214,7 +174,7 @@ export default function StopMap() { zoom: mapState.zoom, }} attributionControl={{ compact: false }} - maxBounds={[REGION_DATA.bounds.sw, REGION_DATA.bounds.ne]} + maxBounds={[APP_CONSTANTS.bounds.sw, APP_CONSTANTS.bounds.ne]} > <NavigationControl position="bottom-right" /> <GeolocateControl @@ -225,8 +185,10 @@ export default function StopMap() { <Source id="stops-source" - type="geojson" - data={{ type: "FeatureCollection", features: stops }} + type="vector" + tiles={[StopDataProvider.getTileUrlTemplate()]} + minzoom={11} + maxzoom={20} /> <Layer @@ -234,8 +196,26 @@ export default function StopMap() { type="symbol" minzoom={11} source="stops-source" + source-layer="stops" layout={{ - "icon-image": ["get", "prefix"], + // TODO: Fix ñapa by maybe including this from the server side? + "icon-image": [ + "match", + ["get", "feed"], + "vitrasa", + "stop-vitrasa", + "santiago", + "stop-santiago", + "coruna", + "stop-coruna", + "xunta", + "stop-xunta", + "renfe", + "stop-renfe", + "feve", + "stop-feve", + "#stop-generic", + ], "icon-size": [ "interpolate", ["linear"], @@ -256,6 +236,7 @@ export default function StopMap() { id="stops-label" type="symbol" source="stops-source" + source-layer="stops" minzoom={16} layout={{ "text-field": ["get", "name"], @@ -267,10 +248,21 @@ export default function StopMap() { }} paint={{ "text-color": [ - "case", - ["==", ["get", "prefix"], "stop-renfe"], + "match", + ["get", "feed"], + "vitrasa", + "#95D516", + "santiago", + "#508096", + "coruna", + "#E61C29", + "xunta", + "#007BC4", + "renfe", "#870164", - "#e72b37", + "feve", + "#EE3D32", + "#333333", ], "text-halo-color": "#FFF", "text-halo-width": 1, diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx index b1a9813..9e44425 100644 --- a/src/frontend/app/routes/planner.tsx +++ b/src/frontend/app/routes/planner.tsx @@ -9,7 +9,7 @@ import { useLocation } from "react-router"; import { useApp } from "~/AppContext"; import LineIcon from "~/components/LineIcon"; import { PlannerOverlay } from "~/components/PlannerOverlay"; -import { REGION_DATA } from "~/config/RegionConfig"; +import { APP_CONSTANTS } from "~/config/constants"; import { usePageTitle } from "~/contexts/PageTitleContext"; import { type Itinerary } from "~/data/PlannerApi"; import { usePlanner } from "~/hooks/usePlanner"; @@ -392,7 +392,7 @@ const ItineraryDetail = ({ if (!arrivalsByStop[stopKey]) { try { const resp = await fetch( - `${REGION_DATA.consolidatedCirculationsEndpoint}?stopId=${encodeURIComponent(leg.from.stopCode || leg.from.stopId)}`, + `${APP_CONSTANTS.consolidatedCirculationsEndpoint}?stopId=${encodeURIComponent(leg.from.stopCode || leg.from.stopId)}`, { headers: { Accept: "application/json" } } ); @@ -424,9 +424,11 @@ const ItineraryDetail = ({ ref={mapRef} initialViewState={{ longitude: - origin?.lon || (REGION_DATA.defaultCenter as [number, number])[0], + origin?.lon || + (APP_CONSTANTS.defaultCenter as [number, number])[0], latitude: - origin?.lat || (REGION_DATA.defaultCenter as [number, number])[1], + origin?.lat || + (APP_CONSTANTS.defaultCenter as [number, number])[1], zoom: 13, }} mapStyle={mapStyle} diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx index 6d06215..46358dc 100644 --- a/src/frontend/app/routes/stops-$id.tsx +++ b/src/frontend/app/routes/stops-$id.tsx @@ -10,7 +10,7 @@ import { StopHelpModal } from "~/components/StopHelpModal"; import { StopMapModal } from "~/components/StopMapModal"; import { ConsolidatedCirculationList } from "~/components/Stops/ConsolidatedCirculationList"; import { ConsolidatedCirculationListSkeleton } from "~/components/Stops/ConsolidatedCirculationListSkeleton"; -import { REGION_DATA } from "~/config/RegionConfig"; +import { APP_CONSTANTS } from "~/config/constants"; import { usePageTitle } from "~/contexts/PageTitleContext"; import { useAutoRefresh } from "~/hooks/useAutoRefresh"; import StopDataProvider, { type Stop } from "../data/StopDataProvider"; @@ -58,7 +58,7 @@ const loadConsolidatedData = async ( stopId: string ): Promise<ConsolidatedCirculation[]> => { const resp = await fetch( - `${REGION_DATA.consolidatedCirculationsEndpoint}?stopId=${stopId}`, + `${APP_CONSTANTS.consolidatedCirculationsEndpoint}?stopId=${stopId}`, { headers: { Accept: "application/json", @@ -123,8 +123,7 @@ export default function Estimates() { // Helper function to get the display name for the stop const getStopDisplayName = useCallback(() => { if (customName) return customName; - if (stopData?.name.intersect) return stopData.name.intersect; - if (stopData?.name.original) return stopData.name.original; + if (stopData?.name) return stopData.name; return `Parada ${stopId}`; }, [customName, stopData, stopId]); |
