fix: 트랙 끝점 clamp + 연관 선박 위치 디버그 강화

- 트랙 시간 범위 밖: 가장 가까운 끝점으로 clamp (기존: skip)
  → 트랙 시작 전 = 첫 점, 트랙 종료 후 = 마지막 점
- 디버그 로그: corrPositions 상세 (track/live 소스별, 모델별 위치확인 수)
- 기존 중복 로그 정리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
htlee 2026-03-31 08:55:00 +09:00
부모 8eacbb2c91
커밋 6ea394d120

파일 보기

@ -84,21 +84,8 @@ export function useGearReplayLayers(
const ct = state.currentTime;
const st = state.startTime;
// 디버그: 첫 프레임에서 데이터 상태 출력
if (!debugLoggedRef.current) {
debugLoggedRef.current = true;
console.log('[GearReplay] renderFrame 시작:', {
historyFrames: state.historyFrames.length,
correlationByModel: state.correlationByModel.size,
modelNames: [...state.correlationByModel.keys()],
correlationTripsData: correlationTripsData.length,
enabledModels: [...enabledModels],
enabledVessels: enabledVessels.size,
});
for (const [mn, items] of state.correlationByModel) {
console.log(` [${mn}] ${items.length}건 (vessels: ${items.filter(c => c.targetType === 'VESSEL').length}, gear: ${items.filter(c => c.targetType !== 'VESSEL').length})`);
}
}
const shouldLog = !debugLoggedRef.current;
if (shouldLog) debugLoggedRef.current = true;
// Find current frame
const { index: frameIdx, cursor } = findFrameAtTime(state.frameTimes, ct, cursorRef.current);
@ -264,10 +251,11 @@ export function useGearReplayLayers(
}));
}
// 6. Correlation vessel positions (트랙 보간 우선, 없으면 live 위치 fallback)
// 6. Correlation vessel positions (트랙 보간 → 끝점 clamp → live fallback)
const corrPositions: CorrPosition[] = [];
const corrTrackMap = new Map(correlationTripsData.map(d => [d.id, d]));
const liveShips = shipsRef.current;
const relTime = ct - st;
for (const [mn, items] of correlationByModel) {
if (!enabledModels.has(mn)) continue;
@ -281,13 +269,31 @@ export function useGearReplayLayers(
let lat: number | undefined;
let cog = 0;
// 방법 1: 트랙 데이터 보간
// 방법 1: 트랙 데이터 (보간 + 범위 밖은 끝점 clamp)
const tripData = corrTrackMap.get(c.targetMmsi);
if (tripData && tripData.timestamps.length > 0) {
const relTime = ct - st;
if (tripData && tripData.path.length > 0) {
const ts = tripData.timestamps;
const path = tripData.path;
if (relTime >= ts[0] && relTime <= ts[ts.length - 1]) {
if (relTime <= ts[0]) {
// 트랙 시작 전 → 첫 점 사용
lon = path[0][0]; lat = path[0][1];
if (path.length > 1) {
const dx = path[1][0] - path[0][0];
const dy = path[1][1] - path[0][1];
cog = (dx === 0 && dy === 0) ? 0 : (Math.atan2(dx, dy) * 180 / Math.PI + 360) % 360;
}
} else if (relTime >= ts[ts.length - 1]) {
// 트랙 종료 후 → 마지막 점 사용
const last = path.length - 1;
lon = path[last][0]; lat = path[last][1];
if (last > 0) {
const dx = path[last][0] - path[last - 1][0];
const dy = path[last][1] - path[last - 1][1];
cog = (dx === 0 && dy === 0) ? 0 : (Math.atan2(dx, dy) * 180 / Math.PI + 360) % 360;
}
} else {
// 범위 내 → 보간
let lo = 0;
let hi = ts.length - 1;
while (lo < hi - 1) {
@ -327,6 +333,33 @@ export function useGearReplayLayers(
}
}
// 디버그: 첫 프레임에서 전체 상태 출력
if (shouldLog) {
const trackHit = corrPositions.filter(p => corrTrackMap.has(p.mmsi)).length;
const liveHit = corrPositions.length - trackHit;
console.log('[GearReplay] renderFrame:', {
historyFrames: state.historyFrames.length,
correlationByModel: state.correlationByModel.size,
modelNames: [...state.correlationByModel.keys()],
corrTripsData: correlationTripsData.length,
corrTrackMap: corrTrackMap.size,
enabledModels: [...enabledModels],
enabledVessels: enabledVessels.size,
relTime: Math.round(relTime / 60000) + 'min',
members: members.length,
corrPositions: corrPositions.length,
posSource: `track:${trackHit} live:${liveHit}`,
});
// 모델별 상세
for (const [mn, items] of state.correlationByModel) {
const modEnabled = enabledModels.has(mn);
const modPositions = corrPositions.filter(p => {
return items.some(c => c.targetMmsi === p.mmsi);
}).length;
console.log(` [${mn}] ${modEnabled ? 'ON' : 'OFF'} ${items.length}건 → 위치확인 ${modPositions}`);
}
}
if (corrPositions.length > 0) {
layers.push(new IconLayer<CorrPosition>({
id: 'replay-corr-vessels',