fix: 연관 선박 위치 live fallback — 트랙 데이터 없을 때 ships 배열 사용
- useGearReplayLayers에 shipsRef 파라미터 추가 - corrPositions 계산: 트랙 보간 우선 → live 선박 위치 fallback - KoreaMap: allShips → shipsRef에 매 렌더 동기화 (ref로 re-render 방지) - globalThis.Map으로 react-map-gl Map 타입 충돌 해결 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
6ba3db5cee
커밋
c4186a327d
@ -238,13 +238,21 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
||||
|
||||
// ── deck.gl 리플레이 레이어 (Zustand → imperative setProps, React 렌더 우회) ──
|
||||
const reactLayersRef = useRef<DeckLayer[]>([]);
|
||||
type ShipPos = { lng: number; lat: number; course?: number };
|
||||
const shipsRef = useRef(new globalThis.Map<string, ShipPos>());
|
||||
// live 선박 위치를 ref에 동기화 (리플레이 fallback용)
|
||||
const allShipsList = allShips ?? ships;
|
||||
const shipPosMap = new globalThis.Map<string, ShipPos>();
|
||||
for (const s of allShipsList) shipPosMap.set(s.mmsi, { lng: s.lng, lat: s.lat, course: s.course });
|
||||
shipsRef.current = shipPosMap;
|
||||
|
||||
const requestRender = useCallback(() => {
|
||||
if (!overlayRef.current) return;
|
||||
overlayRef.current.setProps({
|
||||
layers: [...reactLayersRef.current, ...replayLayerRef.current],
|
||||
});
|
||||
}, []);
|
||||
useGearReplayLayers(replayLayerRef, requestRender);
|
||||
useGearReplayLayers(replayLayerRef, requestRender, shipsRef);
|
||||
|
||||
useEffect(() => {
|
||||
fetchKoreaInfra().then(setInfra).catch(() => {});
|
||||
|
||||
@ -52,6 +52,7 @@ interface CorrPosition {
|
||||
export function useGearReplayLayers(
|
||||
replayLayerRef: React.MutableRefObject<Layer[]>,
|
||||
requestRender: () => void,
|
||||
shipsRef: React.MutableRefObject<Map<string, { lng: number; lat: number; course?: number }>>,
|
||||
): void {
|
||||
// ── React selectors (infrequent changes only) ────────────────────────────
|
||||
const historyFrames = useGearReplayStore(s => s.historyFrames);
|
||||
@ -263,9 +264,10 @@ export function useGearReplayLayers(
|
||||
}));
|
||||
}
|
||||
|
||||
// 6. Correlation vessel positions (interpolated from correlationTripsData)
|
||||
// 6. Correlation vessel positions (트랙 보간 우선, 없으면 live 위치 fallback)
|
||||
const corrPositions: CorrPosition[] = [];
|
||||
const corrTrackMap = new Map(correlationTripsData.map(d => [d.id, d]));
|
||||
const liveShips = shipsRef.current;
|
||||
|
||||
for (const [mn, items] of correlationByModel) {
|
||||
if (!enabledModels.has(mn)) continue;
|
||||
@ -275,29 +277,43 @@ export function useGearReplayLayers(
|
||||
for (const c of items as GearCorrelationItem[]) {
|
||||
if (corrPositions.some(p => p.mmsi === c.targetMmsi)) continue;
|
||||
|
||||
let lon: number | undefined;
|
||||
let lat: number | undefined;
|
||||
let cog = 0;
|
||||
|
||||
// 방법 1: 트랙 데이터 보간
|
||||
const tripData = corrTrackMap.get(c.targetMmsi);
|
||||
if (!tripData) continue;
|
||||
|
||||
const relTime = ct - st;
|
||||
const ts = tripData.timestamps;
|
||||
const path = tripData.path;
|
||||
if (ts.length === 0) continue;
|
||||
if (relTime < ts[0] || relTime > ts[ts.length - 1]) continue;
|
||||
|
||||
// Binary search in timestamps
|
||||
let lo = 0;
|
||||
let hi = ts.length - 1;
|
||||
while (lo < hi - 1) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (ts[mid] <= relTime) lo = mid; else hi = mid;
|
||||
if (tripData && tripData.timestamps.length > 0) {
|
||||
const relTime = ct - st;
|
||||
const ts = tripData.timestamps;
|
||||
const path = tripData.path;
|
||||
if (relTime >= ts[0] && relTime <= ts[ts.length - 1]) {
|
||||
let lo = 0;
|
||||
let hi = ts.length - 1;
|
||||
while (lo < hi - 1) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (ts[mid] <= relTime) lo = mid; else hi = mid;
|
||||
}
|
||||
const ratio = ts[hi] !== ts[lo] ? (relTime - ts[lo]) / (ts[hi] - ts[lo]) : 0;
|
||||
lon = path[lo][0] + (path[hi][0] - path[lo][0]) * ratio;
|
||||
lat = path[lo][1] + (path[hi][1] - path[lo][1]) * ratio;
|
||||
const dx = path[hi][0] - path[lo][0];
|
||||
const dy = path[hi][1] - path[lo][1];
|
||||
cog = (dx === 0 && dy === 0) ? 0 : (Math.atan2(dx, dy) * 180 / Math.PI + 360) % 360;
|
||||
}
|
||||
}
|
||||
const ratio = ts[hi] !== ts[lo] ? (relTime - ts[lo]) / (ts[hi] - ts[lo]) : 0;
|
||||
const lon = path[lo][0] + (path[hi][0] - path[lo][0]) * ratio;
|
||||
const lat = path[lo][1] + (path[hi][1] - path[lo][1]) * ratio;
|
||||
// heading from segment direction
|
||||
const dx = path[hi][0] - path[lo][0];
|
||||
const dy = path[hi][1] - path[lo][1];
|
||||
const cog = (dx === 0 && dy === 0) ? 0 : (Math.atan2(dx, dy) * 180 / Math.PI + 360) % 360;
|
||||
|
||||
// 방법 2: live 선박 위치 fallback
|
||||
if (lon === undefined) {
|
||||
const ship = liveShips.get(c.targetMmsi);
|
||||
if (ship) {
|
||||
lon = ship.lng;
|
||||
lat = ship.lat;
|
||||
cog = ship.course ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (lon === undefined || lat === undefined) continue;
|
||||
|
||||
corrPositions.push({
|
||||
mmsi: c.targetMmsi,
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user