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,
};
}