fix: 트랙 끝점 clamp + 연관 선박 위치 디버그 강화
- 트랙 시간 범위 밖: 가장 가까운 끝점으로 clamp (기존: skip) → 트랙 시작 전 = 첫 점, 트랙 종료 후 = 마지막 점 - 디버그 로그: corrPositions 상세 (track/live 소스별, 모델별 위치확인 수) - 기존 중복 로그 정리 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
8eacbb2c91
커밋
6ea394d120
@ -84,21 +84,8 @@ export function useGearReplayLayers(
|
|||||||
const ct = state.currentTime;
|
const ct = state.currentTime;
|
||||||
const st = state.startTime;
|
const st = state.startTime;
|
||||||
|
|
||||||
// 디버그: 첫 프레임에서 데이터 상태 출력
|
const shouldLog = !debugLoggedRef.current;
|
||||||
if (!debugLoggedRef.current) {
|
if (shouldLog) debugLoggedRef.current = true;
|
||||||
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})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find current frame
|
// Find current frame
|
||||||
const { index: frameIdx, cursor } = findFrameAtTime(state.frameTimes, ct, cursorRef.current);
|
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 corrPositions: CorrPosition[] = [];
|
||||||
const corrTrackMap = new Map(correlationTripsData.map(d => [d.id, d]));
|
const corrTrackMap = new Map(correlationTripsData.map(d => [d.id, d]));
|
||||||
const liveShips = shipsRef.current;
|
const liveShips = shipsRef.current;
|
||||||
|
const relTime = ct - st;
|
||||||
|
|
||||||
for (const [mn, items] of correlationByModel) {
|
for (const [mn, items] of correlationByModel) {
|
||||||
if (!enabledModels.has(mn)) continue;
|
if (!enabledModels.has(mn)) continue;
|
||||||
@ -281,13 +269,31 @@ export function useGearReplayLayers(
|
|||||||
let lat: number | undefined;
|
let lat: number | undefined;
|
||||||
let cog = 0;
|
let cog = 0;
|
||||||
|
|
||||||
// 방법 1: 트랙 데이터 보간
|
// 방법 1: 트랙 데이터 (보간 + 범위 밖은 끝점 clamp)
|
||||||
const tripData = corrTrackMap.get(c.targetMmsi);
|
const tripData = corrTrackMap.get(c.targetMmsi);
|
||||||
if (tripData && tripData.timestamps.length > 0) {
|
if (tripData && tripData.path.length > 0) {
|
||||||
const relTime = ct - st;
|
|
||||||
const ts = tripData.timestamps;
|
const ts = tripData.timestamps;
|
||||||
const path = tripData.path;
|
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 lo = 0;
|
||||||
let hi = ts.length - 1;
|
let hi = ts.length - 1;
|
||||||
while (lo < hi - 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) {
|
if (corrPositions.length > 0) {
|
||||||
layers.push(new IconLayer<CorrPosition>({
|
layers.push(new IconLayer<CorrPosition>({
|
||||||
id: 'replay-corr-vessels',
|
id: 'replay-corr-vessels',
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user