import StopDataProvider from "../data/StopDataProvider"; import "./map.css"; import { DEFAULT_STYLE, loadStyle } from "app/maps/styleloader"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Map, { GeolocateControl, Layer, NavigationControl, Source, type MapLayerMouseEvent, type MapRef, type StyleSpecification, } from "react-map-gl/maplibre"; import { useNavigate } from "react-router"; import { PlannerOverlay } from "~/components/PlannerOverlay"; import { StopSummarySheet, 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"; import "../tailwind-full.css"; // Componente principal del mapa export default function StopMap() { const { t } = useTranslation(); const navigate = useNavigate(); usePageTitle(t("navbar.map", "Mapa")); const [selectedStop, setSelectedStop] = useState< StopSheetProps["stop"] | null >(null); const [isSheetOpen, setIsSheetOpen] = useState(false); const { mapState, updateMapState, theme } = useApp(); const mapRef = useRef(null); const { searchRoute } = usePlanner(); // Style state for Map component const [mapStyle, setMapStyle] = useState(DEFAULT_STYLE); // Handle click events on clusters and individual stops const onMapClick = (e: MapLayerMouseEvent) => { const features = e.features; if (!features || features.length === 0) { console.debug( "No features found on map click. Position:", e.lngLat, "Point:", e.point ); return; } const feature = features[0]; handlePointClick(feature); }; useEffect(() => { //const styleName = "carto"; const styleName = "openfreemap"; loadStyle(styleName, theme) .then((style) => setMapStyle(style)) .catch((error) => console.error("Failed to load map style:", error)); }, [theme]); useEffect(() => { const handleMapChange = () => { if (!mapRef.current) return; const map = mapRef.current.getMap(); if (!map) return; const center = map.getCenter(); const zoom = map.getZoom(); updateMapState([center.lat, center.lng], zoom); }; const handleStyleImageMissing = (e: any) => { // Suppress warnings for missing sprite images from base style // This prevents console noise from OpenFreeMap's missing icons if (!mapRef.current) return; const map = mapRef.current.getMap(); if (!map || map.hasImage(e.id)) return; // Log warning for our own icons if they are missing if (e.id.startsWith("stop-")) { console.warn(`Missing icon image: ${e.id}`); } // Add a transparent 1x1 placeholder to prevent repeated warnings map.addImage(e.id, { width: 1, height: 1, data: new Uint8Array(4), }); }; if (mapRef.current) { const map = mapRef.current.getMap(); if (map) { map.on("moveend", handleMapChange); map.on("styleimagemissing", handleStyleImageMissing); } } return () => { if (mapRef.current) { const map = mapRef.current.getMap(); if (map) { map.off("moveend", handleMapChange); map.off("styleimagemissing", handleStyleImageMissing); } } }; }, [mapRef.current]); const getLatitude = (center: any) => Array.isArray(center) ? center[0] : center.lat; const getLongitude = (center: any) => Array.isArray(center) ? center[1] : center.lng; const handlePointClick = (feature: any) => { const props: { id: string; code: string; name: string; routes: string; } = feature.properties; // TODO: Move ID to constant, improve type checking if (!props || feature.layer.id !== "stops") { console.warn("Invalid feature properties:", props); return; } const stopId = props.id; const routes: { shortName: string; colour: string; textColour: string; }[] = JSON.parse(props.routes || "[]"); setSelectedStop({ stopId: props.id, stopCode: props.code, name: props.name || "Unknown Stop", lines: routes.map((route) => { return { line: route.shortName, colour: route.colour, textColour: route.textColour, }; }), }); setIsSheetOpen(true); }; return (
searchRoute(o, d, time, arriveBy)} onNavigateToPlanner={() => navigate("/planner")} clearPickerOnOpen={true} showLastDestinationWhenCollapsed={false} cardBackground="bg-white/95 dark:bg-slate-900/90" /> {selectedStop && ( setIsSheetOpen(false)} stop={selectedStop} /> )}
); }