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

92 lines
2.6 KiB
TypeScript

import type { CurrentVesselPosition, ProcessedTrack } from '../model/track.types';
function calculateHeading(from: [number, number], to: [number, number]): number {
const dx = to[0] - from[0];
const dy = to[1] - from[1];
let angle = (Math.atan2(dx, dy) * 180) / Math.PI;
if (angle < 0) angle += 360;
return angle;
}
function interpolate(
from: [number, number],
to: [number, number],
fromTs: number,
toTs: number,
currentTs: number,
): [number, number] {
if (toTs <= fromTs) return from;
if (currentTs <= fromTs) return from;
if (currentTs >= toTs) return to;
const ratio = (currentTs - fromTs) / (toTs - fromTs);
return [
from[0] + (to[0] - from[0]) * ratio,
from[1] + (to[1] - from[1]) * ratio,
];
}
export function getCurrentPosition(track: ProcessedTrack, currentTime: number): CurrentVesselPosition | null {
const len = track.timestampsMs.length;
if (len === 0 || track.geometry.length === 0) return null;
const firstTime = track.timestampsMs[0];
const lastTime = track.timestampsMs[len - 1];
if (currentTime < firstTime || currentTime > lastTime) return null;
if (len === 1) {
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: firstTime,
};
}
let hi = len - 1;
let lo = 0;
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
if (track.timestampsMs[mid] <= currentTime) lo = mid + 1;
else hi = mid - 1;
}
const idx = Math.max(0, Math.min(len - 2, hi));
const from = track.geometry[idx];
const to = track.geometry[idx + 1];
const fromTs = track.timestampsMs[idx];
const toTs = track.timestampsMs[idx + 1];
const position = interpolate(from, to, fromTs, toTs, currentTime);
const heading = calculateHeading(from, to);
const speed = track.speeds[idx] || 0;
return {
vesselId: track.vesselId,
targetId: track.targetId,
sigSrcCd: track.sigSrcCd,
shipName: track.shipName,
shipKindCode: track.shipKindCode,
nationalCode: track.nationalCode,
position,
heading,
speed,
timestamp: currentTime,
};
}
export function getCurrentPositions(tracks: ProcessedTrack[], currentTime: number): CurrentVesselPosition[] {
const out: CurrentVesselPosition[] = [];
for (const track of tracks) {
const pos = getCurrentPosition(track, currentTime);
if (pos) out.push(pos);
}
return out;
}