/** * 리플레이 데이터 저장소 * 참조: src/tracking/stores/mergedTrackStore.ts * * 청크 기반 선박 항적 데이터 저장 및 관리 */ import { create } from 'zustand'; import { parseTimestamp } from '../types/replay.types'; /** * 청크 기반 선박 데이터 병합 */ function mergeVesselChunks(existingChunks, newChunk) { if (!existingChunks) { return { vesselId: newChunk.vesselId, sigSrcCd: newChunk.sigSrcCd, targetId: newChunk.targetId, shipName: newChunk.shipName, shipKindCode: newChunk.shipKindCode, chunks: [newChunk], cachedPath: null, totalDistance: newChunk.totalDistance || 0, maxSpeed: newChunk.maxSpeed || 0, avgSpeed: newChunk.avgSpeed || 0, }; } // 기존 청크에 새 청크 추가 (시간순 정렬) const chunks = [...existingChunks.chunks, newChunk].sort((a, b) => { const timeA = parseTimestamp(a.timestamps[0]); const timeB = parseTimestamp(b.timestamps[0]); return timeA - timeB; }); return { ...existingChunks, chunks, cachedPath: null, // 캐시 무효화 totalDistance: Math.max(existingChunks.totalDistance, newChunk.totalDistance || 0), maxSpeed: Math.max(existingChunks.maxSpeed, newChunk.maxSpeed || 0), }; } /** * 병합된 경로 생성 (캐싱) */ function buildCachedPath(chunks) { const geometry = []; const timestamps = []; const timestampsMs = []; const speeds = []; chunks.forEach((chunk) => { if (chunk.geometry) { geometry.push(...chunk.geometry); } if (chunk.timestamps) { timestamps.push(...chunk.timestamps); chunk.timestamps.forEach((ts) => { timestampsMs.push(parseTimestamp(ts)); }); } if (chunk.speeds) { speeds.push(...chunk.speeds); } }); return { geometry, timestamps, timestampsMs, speeds, lastUpdated: Date.now(), }; } /** * MergedTrackStore */ const useMergedTrackStore = create((set, get) => ({ // ===== 상태 ===== // 청크 기반 저장소 (메인) vesselChunks: new Map(), // Map // 원본 청크 (리플레이용) rawChunks: [], // 메타데이터 timeRange: null, // { start: number, end: number } spatialBounds: null, // { minLon, maxLon, minLat, maxLat } // ===== 액션 ===== /** * 청크 추가 (최적화) */ addChunkOptimized: (chunkResponse) => { const tracks = chunkResponse.tracks || []; set((state) => { const newVesselChunks = new Map(state.vesselChunks); let timeRange = state.timeRange; tracks.forEach((track) => { const vesselId = track.vesselId; const existingChunks = newVesselChunks.get(vesselId); const mergedChunks = mergeVesselChunks(existingChunks, track); newVesselChunks.set(vesselId, mergedChunks); // 시간 범위 업데이트 if (track.timestamps && track.timestamps.length > 0) { const firstTime = parseTimestamp(track.timestamps[0]); const lastTime = parseTimestamp(track.timestamps[track.timestamps.length - 1]); if (!timeRange) { timeRange = { start: firstTime, end: lastTime }; } else { timeRange = { start: Math.min(timeRange.start, firstTime), end: Math.max(timeRange.end, lastTime), }; } } }); return { vesselChunks: newVesselChunks, timeRange, }; }); }, /** * 병합된 경로 반환 (캐시 사용) */ getMergedPath: (vesselId) => { const vesselChunks = get().vesselChunks.get(vesselId); if (!vesselChunks) return null; // 캐시 확인 if (vesselChunks.cachedPath) { return vesselChunks.cachedPath; } // 새로 생성 const cachedPath = buildCachedPath(vesselChunks.chunks); // 캐시 저장 set((state) => { const newVesselChunks = new Map(state.vesselChunks); newVesselChunks.set(vesselId, { ...vesselChunks, cachedPath, }); return { vesselChunks: newVesselChunks }; }); return cachedPath; }, /** * 원본 청크 추가 */ addRawChunk: (chunkResponse) => { set((state) => ({ rawChunks: [...state.rawChunks, chunkResponse], })); }, /** * 전체 초기화 */ clear: () => { set({ vesselChunks: new Map(), rawChunks: [], timeRange: null, spatialBounds: null, }); }, /** * 모든 선박 ID 반환 */ getAllVesselIds: () => { return Array.from(get().vesselChunks.keys()); }, /** * 선박 데이터 반환 */ getVesselChunks: (vesselId) => { return get().vesselChunks.get(vesselId) || null; }, })); export default useMergedTrackStore;