gc-wing/apps/web/src/features/trackReplay/hooks/useTrackReplayDeckLayers.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

126 lines
4.4 KiB
TypeScript

import { useMemo } from 'react';
import { getCurrentPositions } from '../lib/interpolate';
import { createReplayTrailLayer } from '../layers/replayLayers';
import { createDynamicTrackLayers, createStaticTrackLayers } from '../layers/trackLayers';
import type { CurrentVesselPosition, ProcessedTrack } from '../model/track.types';
import { useTrackPlaybackStore } from '../stores/trackPlaybackStore';
import { useTrackQueryStore } from '../stores/trackQueryStore';
export interface TrackReplayDeckRenderState {
trackReplayDeckLayers: unknown[];
enabledTracks: ProcessedTrack[];
currentPositions: CurrentVesselPosition[];
showPoints: boolean;
showVirtualShip: boolean;
showLabels: boolean;
renderEpoch: number;
}
export function useTrackReplayDeckLayers(): TrackReplayDeckRenderState {
const tracks = useTrackQueryStore((state) => state.tracks);
const disabledVesselIds = useTrackQueryStore((state) => state.disabledVesselIds);
const highlightedVesselId = useTrackQueryStore((state) => state.highlightedVesselId);
const setHighlightedVesselId = useTrackQueryStore((state) => state.setHighlightedVesselId);
const showPoints = useTrackQueryStore((state) => state.showPoints);
const showVirtualShip = useTrackQueryStore((state) => state.showVirtualShip);
const showLabels = useTrackQueryStore((state) => state.showLabels);
const showTrail = useTrackQueryStore((state) => state.showTrail);
const renderEpoch = useTrackQueryStore((state) => state.renderEpoch);
const isPlaying = useTrackPlaybackStore((state) => state.isPlaying);
const currentTime = useTrackPlaybackStore((state) => state.currentTime);
const playbackRenderTime = useMemo(() => {
if (!isPlaying) return currentTime;
// Throttle to ~10fps while playing to reduce relayout pressure.
return Math.floor(currentTime / 100) * 100;
}, [isPlaying, currentTime]);
const enabledTracks = useMemo(() => {
if (!tracks.length) return [];
if (disabledVesselIds.size === 0) return tracks;
return tracks.filter((track) => !disabledVesselIds.has(track.vesselId));
}, [tracks, disabledVesselIds]);
const currentPositions = useMemo(() => {
void renderEpoch;
if (enabledTracks.length === 0) return [];
const sampled = getCurrentPositions(enabledTracks, playbackRenderTime);
if (sampled.length > 0 || isPlaying) return sampled;
// Ensure an immediate first-frame marker when query data arrives but
// playback has not started yet (globe static-render case).
return enabledTracks.flatMap((track) => {
if (track.geometry.length === 0) return [];
const firstTs = track.timestampsMs[0] ?? playbackRenderTime;
return [
{
vesselId: track.vesselId,
targetId: track.targetId,
sigSrcCd: track.sigSrcCd,
shipName: track.shipName,
shipKindCode: track.shipKindCode,
nationalCode: track.nationalCode,
position: track.geometry[0],
heading: 0,
speed: track.speeds[0] ?? 0,
timestamp: firstTs,
} as CurrentVesselPosition,
];
});
}, [enabledTracks, playbackRenderTime, isPlaying, renderEpoch]);
const staticLayers = useMemo(
() => {
void renderEpoch;
return createStaticTrackLayers({
tracks: enabledTracks,
showPoints,
highlightedVesselId,
onPathHover: setHighlightedVesselId,
});
},
[enabledTracks, showPoints, highlightedVesselId, setHighlightedVesselId, renderEpoch],
);
const dynamicLayers = useMemo(
() => {
void renderEpoch;
return createDynamicTrackLayers({
currentPositions,
showVirtualShip,
showLabels,
onPathHover: setHighlightedVesselId,
});
},
[currentPositions, showVirtualShip, showLabels, setHighlightedVesselId, renderEpoch],
);
const trailLayer = useMemo(
() => {
void renderEpoch;
return createReplayTrailLayer({
tracks: enabledTracks,
currentTime: playbackRenderTime,
showTrail,
});
},
[enabledTracks, playbackRenderTime, showTrail, renderEpoch],
);
const trackReplayDeckLayers = useMemo(
() => [...staticLayers, ...(trailLayer ? [trailLayer] : []), ...dynamicLayers],
[staticLayers, dynamicLayers, trailLayer],
);
return {
trackReplayDeckLayers,
enabledTracks,
currentPositions,
showPoints,
showVirtualShip,
showLabels,
renderEpoch,
};
}