126 lines
4.4 KiB
TypeScript
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,
|
|
};
|
|
}
|