import { useEffect, useMemo } from 'react'; import L from 'leaflet'; import { MapContainer, Marker, Popup, TileLayer, Tooltip, useMap, useMapEvents, } from 'react-leaflet'; import type { MapTileStyle } from './mapStyles'; import type { BBox, ParsedOsmResult } from '../search/types'; type MapViewProps = { results: ParsedOsmResult[]; selectedResultId: string | null; tileStyle: MapTileStyle; showResultLabels: boolean; fitMapToResults: boolean; dimMap: boolean; onResultSelect: (id: string) => void; onViewportChange: (bbox: BBox) => void; }; const DEFAULT_CENTER: [number, number] = [52.2297, 21.0122]; const DEFAULT_ZOOM = 13; export function MapView({ results, selectedResultId, tileStyle, showResultLabels, fitMapToResults, dimMap, onResultSelect, onViewportChange, }: MapViewProps) { const markerIcon = useMemo( () => L.divIcon({ className: 'result-marker', html: '', iconSize: [18, 18], iconAnchor: [9, 9], }), [], ); const selectedMarkerIcon = useMemo( () => L.divIcon({ className: 'result-marker result-marker-selected', html: '', iconSize: [24, 24], iconAnchor: [12, 12], }), [], ); return ( item.id === selectedResultId) ?? null} /> {results.map((result) => ( onResultSelect(result.id), }} > {result.name ?? 'Unnamed place'}
{result.type} {result.osmId}
{showResultLabels ? ( {result.name ?? `${result.type} ${result.osmId}`} ) : null}
))}
); } function ViewportReporter({ onViewportChange, }: { onViewportChange: (bbox: BBox) => void; }) { const map = useMapEvents({ moveend: () => onViewportChange(boundsToBBox(map.getBounds())), zoomend: () => onViewportChange(boundsToBBox(map.getBounds())), }); useEffect(() => { onViewportChange(boundsToBBox(map.getBounds())); }, [map, onViewportChange]); return null; } function FitResults({ results, enabled, }: { results: ParsedOsmResult[]; enabled: boolean; }) { const map = useMap(); useEffect(() => { if (!enabled || results.length === 0) { return; } const bounds = L.latLngBounds(results.map((result) => [result.lat, result.lon])); if (bounds.isValid()) { map.fitBounds(bounds.pad(0.2), { animate: true, maxZoom: 16, }); } }, [enabled, map, results]); return null; } function SelectedResultFocus({ result }: { result: ParsedOsmResult | null }) { const map = useMap(); useEffect(() => { if (result) { map.setView([result.lat, result.lon], Math.max(map.getZoom(), 15), { animate: true, }); } }, [map, result]); return null; } function boundsToBBox(bounds: L.LatLngBounds): BBox { const southWest = bounds.getSouthWest(); const northEast = bounds.getNorthEast(); return { south: southWest.lat, west: southWest.lng, north: northEast.lat, east: northEast.lng, }; }