import { useEffect, useMemo, useRef, useState, type MutableRefObject } from 'react'; import type maplibregl from 'maplibre-gl'; import type { AisTarget } from '../../../entities/aisTarget/model/types'; import ShipBatchRenderer from '../core/ShipBatchRenderer'; import type { LiveShipFeature, ViewportBounds } from '../model/liveShip.types'; interface UseLiveShipBatchRenderResult { renderedFeatures: LiveShipFeature[]; renderedTargets: AisTarget[]; renderedMmsiSet: Set; } function getMapBounds(map: maplibregl.Map): ViewportBounds { const bounds = map.getBounds(); return { minLon: bounds.getWest(), maxLon: bounds.getEast(), minLat: bounds.getSouth(), maxLat: bounds.getNorth(), }; } export function useLiveShipBatchRender( mapRef: MutableRefObject, features: LiveShipFeature[], sourceTargets: AisTarget[], mapSyncEpoch: number, ): UseLiveShipBatchRenderResult { const rendererRef = useRef(null); const [renderedFeatures, setRenderedFeatures] = useState([]); useEffect(() => { const renderer = new ShipBatchRenderer(); renderer.initialize((ships) => { setRenderedFeatures(ships); }); rendererRef.current = renderer; return () => { renderer.dispose(); rendererRef.current = null; }; }, []); useEffect(() => { const renderer = rendererRef.current; if (!renderer) return; renderer.setData(features); renderer.immediateRender(); }, [features]); useEffect(() => { const map = mapRef.current; const renderer = rendererRef.current; if (!map || !renderer) return; const sync = () => { if (!rendererRef.current) return; renderer.setZoom(map.getZoom()); renderer.setViewportBounds(getMapBounds(map)); renderer.requestRender(); }; sync(); map.on('moveend', sync); map.on('zoomend', sync); return () => { map.off('moveend', sync); map.off('zoomend', sync); }; }, [mapRef, mapSyncEpoch]); const renderedMmsiSet = useMemo(() => { const next = new Set(); for (const feature of renderedFeatures) { next.add(feature.mmsi); } return next; }, [renderedFeatures]); const renderedTargets = useMemo(() => { if (renderedMmsiSet.size === 0) return []; return sourceTargets.filter((target) => renderedMmsiSet.has(target.mmsi)); }, [sourceTargets, renderedMmsiSet]); return { renderedFeatures, renderedTargets, renderedMmsiSet }; }