gc-wing/apps/web/src/features/liveRenderer/hooks/useLiveShipBatchRender.ts
htlee 6acf2045b2 chore: vessel-track 브랜치 병합 (squash)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 22:12:48 +09:00

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