signal-batch/frontend/src/features/recent-positions/stores/positionStore.ts
htlee ed0f3056b1 feat: Ship-GIS 기능 이관 — 최근위치/선박항적/뷰포트 리플레이
dark(ship-gis) 프로젝트의 맵 기반 3대 기능을 API 탐색기에 이관.
Feature 폴더 모듈화 구조로 타 프로젝트 재활용 가능하게 구성.

Phase 1: vessel-map 공유 모듈 (Deck.gl 9 + Zustand 5 + STOMP)
Phase 2: 최근 위치 (30초 폴링 + IconLayer + 선종 필터 + 팝업)
Phase 3: 선박 항적 (MMSI 조회 + PathLayer + 타임라인 보간)
Phase 4: 뷰포트 리플레이 (STOMP WebSocket 청크 + TripsLayer 애니메이션)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 15:19:21 +09:00

73 lines
1.9 KiB
TypeScript

import { create } from 'zustand'
import type { VesselPosition } from '../../vessel-map'
interface PositionState {
/** MMSI → 위치 맵 */
positions: Map<string, VesselPosition>
/** 선택된 MMSI (팝업 표시용) */
selectedMmsi: string | null
/** 선종별 가시성 필터 */
kindVisibility: Record<string, boolean>
/** 위치 데이터 전체 교체 */
setPositions: (list: VesselPosition[]) => void
/** 선박 선택/해제 */
selectVessel: (mmsi: string | null) => void
/** 특정 선종 토글 */
toggleKindVisibility: (kindCode: string) => void
/** 전체 선종 표시/숨김 */
setAllKindVisibility: (visible: boolean) => void
/** 가시성 필터 적용된 선박 목록 */
getVisiblePositions: () => VesselPosition[]
}
export const usePositionStore = create<PositionState>((set, get) => ({
positions: new Map(),
selectedMmsi: null,
kindVisibility: {},
setPositions: (list) => {
const map = new Map<string, VesselPosition>()
for (const p of list) {
map.set(p.mmsi, p)
}
set({ positions: map })
},
selectVessel: (mmsi) => set({ selectedMmsi: mmsi }),
toggleKindVisibility: (kindCode) =>
set((state) => ({
kindVisibility: {
...state.kindVisibility,
[kindCode]: !(state.kindVisibility[kindCode] ?? true),
},
})),
setAllKindVisibility: (visible) =>
set((state) => {
const next: Record<string, boolean> = {}
for (const code of Object.keys(state.kindVisibility)) {
next[code] = visible
}
return { kindVisibility: next }
}),
getVisiblePositions: () => {
const { positions, kindVisibility } = get()
const result: VesselPosition[] = []
for (const p of positions.values()) {
const code = p.shipKindCode || ''
if (kindVisibility[code] === false) continue
result.push(p)
}
return result
},
}))