89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
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<number>;
|
|
}
|
|
|
|
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<maplibregl.Map | null>,
|
|
features: LiveShipFeature[],
|
|
sourceTargets: AisTarget[],
|
|
mapSyncEpoch: number,
|
|
): UseLiveShipBatchRenderResult {
|
|
const rendererRef = useRef<ShipBatchRenderer | null>(null);
|
|
const [renderedFeatures, setRenderedFeatures] = useState<LiveShipFeature[]>([]);
|
|
|
|
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<number>();
|
|
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 };
|
|
}
|