import { useEffect, useRef } from 'react'; import Map from 'ol/Map'; import View from 'ol/View'; import Select from 'ol/interaction/Select'; import { click } from 'ol/events/condition'; import type { Road, WorkbenchState } from '../types/state'; import { createLayers, updateLayers, type LayerName } from '../map/layers'; interface Props { state: WorkbenchState; selected: Road | null; visible: Partial>; scene: boolean; onSelectRoad: (road: Road) => void; onFeature: (properties: Record) => void; } export function MapCanvas({ state, selected, visible, scene, onSelectRoad, onFeature }: Props) { const target = useRef(null); const mapRef = useRef(null); const layersRef = useRef | null>(null); const selectedRef = useRef(selected); const stateRef = useRef(state); selectedRef.current = selected; stateRef.current = state; useEffect(() => { if (!target.current) return; const layers = createLayers( () => selectedRef.current, () => scene, ); layersRef.current = layers; const map = new Map({ target: target.current, layers: Object.values(layers), view: new View({ center: [0, 0], zoom: 2 }), }); mapRef.current = map; const select = new Select({ condition: click, layers: (layer) => layer !== layers.selectedRoad, hitTolerance: 12, style: null, }); map.addInteraction(select); const listener = ({ selected: values }: { selected: import('ol/Feature').default[] }) => { const properties = values[0]?.getProperties(); if (!properties) return; const roadId = properties.road_id || properties.from_road_id || (typeof properties.from_lane_id === 'string' ? properties.from_lane_id.slice(5, properties.from_lane_id.lastIndexOf(':')) : undefined); const road = stateRef.current.compiled.model.roads.find((item) => item.id === roadId); if (road) onSelectRoad(road); onFeature(properties); }; select.on('select', listener); return () => { select.un('select', listener); map.setTarget(undefined); }; // Map construction must be once per mounted canvas; state changes update sources below. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { const layers = layersRef.current; const map = mapRef.current; if (!layers || !map) return; updateLayers(layers, state, selected); const extent = layers.osm.getSource()!.getExtent(); if (extent && Number.isFinite(extent[0])) map.getView().fit(extent, { padding: [48, 48, 48, 48], maxZoom: 19 }); }, [state, selected]); useEffect(() => { const layers = layersRef.current; if (!layers) return; Object.entries(visible).forEach(([name, enabled]) => layers[name as LayerName]?.setVisible( !scene || !['osm', 'lanes', 'reference', 'gaodeReference', 'connectors'].includes(name) ? enabled !== false : false, ), ); layers.native.changed(); layers.sidewalks.changed(); }, [visible, scene]); return
; }