wing-ops/frontend/src/common/store/mapStore.ts
jeonghyo.k a86188f473 feat(map): 전체 탭 지도 배경 토글 통합 및 기본지도 변경
- 지도 스타일 상수를 mapStyles.ts로 추출
- useBaseMapStyle 훅 생성 (mapToggles 기반 스타일 반환)
- 9개 탭 컴포넌트의 하드코딩 스타일을 공유 훅으로 교체
- 각 Map에 S57EncOverlay 추가
- 초기 mapToggles를 모두 false로 변경 (기본지도 표시)
2026-03-31 17:56:40 +09:00

130 lines
3.9 KiB
TypeScript

import { create } from 'zustand'
import { api } from '../services/api'
import { haversineDistance, polygonAreaKm2 } from '../utils/geo'
export interface MapTypeItem {
mapKey: string;
mapNm: string;
mapLevelCd: string | null;
}
export type MeasureMode = 'distance' | 'area' | null;
export interface MeasurePoint {
lat: number;
lon: number;
}
export interface MeasureResult {
id: string;
mode: 'distance' | 'area';
points: MeasurePoint[];
value: number; // distance(m) or area(km²)
}
interface MapToggles {
s57: boolean;
s101: boolean;
threeD: boolean;
satellite: boolean;
}
interface MapState {
mapToggles: MapToggles;
mapTypes: MapTypeItem[];
toggleMap: (key: keyof MapToggles) => void;
loadMapTypes: () => Promise<void>;
// 측정
measureMode: MeasureMode;
measureInProgress: MeasurePoint[];
measurements: MeasureResult[];
setMeasureMode: (mode: MeasureMode) => void;
addMeasurePoint: (pt: MeasurePoint) => void;
commitAreaMeasurement: () => void;
removeMeasurement: (id: string) => void;
clearAllMeasurements: () => void;
}
const DEFAULT_MAP_TYPES: MapTypeItem[] = [
{ mapKey: 's57', mapNm: 'S-57 전자해도', mapLevelCd: 'S-57' },
{ mapKey: 's101', mapNm: 'S-101 전자해도', mapLevelCd: 'S-101' },
{ mapKey: 'threeD', mapNm: '3D 지도', mapLevelCd: '3D' },
{ mapKey: 'satellite', mapNm: '위성 영상', mapLevelCd: 'SAT' },
]
let measureIdCounter = 0;
export const useMapStore = create<MapState>((set, get) => ({
mapToggles: { s57: false, s101: false, threeD: false, satellite: false },
mapTypes: DEFAULT_MAP_TYPES,
toggleMap: (key) =>
set((s) => {
const isCurrentlyOn = s.mapToggles[key];
const allOff: MapToggles = { s57: false, s101: false, threeD: false, satellite: false };
return {
mapToggles: isCurrentlyOn ? allOff : { ...allOff, [key]: true },
};
}),
loadMapTypes: async () => {
try {
const res = await api.get<MapTypeItem[]>('/map-base/active')
const types = res.data
const current = get().mapToggles
const newToggles: Partial<MapToggles> = {}
for (const t of types) {
if (t.mapKey in current) {
newToggles[t.mapKey as keyof MapToggles] = current[t.mapKey as keyof MapToggles] ?? false
}
}
// 모든 토글 기본 off (기본지도 표시)
set({ mapTypes: types, mapToggles: { ...current, ...newToggles } })
} catch {
// API 실패 시 fallback 유지
}
},
// 측정
measureMode: null,
measureInProgress: [],
measurements: [],
setMeasureMode: (mode) =>
set({ measureMode: mode, measureInProgress: [] }),
addMeasurePoint: (pt) => {
const { measureMode, measureInProgress } = get();
if (measureMode === 'distance') {
const next = [...measureInProgress, pt];
if (next.length >= 2) {
const dist = haversineDistance(next[0], next[1]);
const id = `measure-${++measureIdCounter}`;
set((s) => ({
measurements: [...s.measurements, { id, mode: 'distance', points: [next[0], next[1]], value: dist }],
measureInProgress: [],
}));
} else {
set({ measureInProgress: next });
}
} else if (measureMode === 'area') {
set({ measureInProgress: [...measureInProgress, pt] });
}
},
commitAreaMeasurement: () => {
const { measureInProgress } = get();
if (measureInProgress.length < 3) return;
const area = polygonAreaKm2(measureInProgress);
const id = `measure-${++measureIdCounter}`;
set((s) => ({
measurements: [...s.measurements, { id, mode: 'area', points: [...measureInProgress], value: area }],
measureInProgress: [],
}));
},
removeMeasurement: (id) =>
set((s) => ({ measurements: s.measurements.filter((m) => m.id !== id) })),
clearAllMeasurements: () =>
set({ measurements: [], measureInProgress: [], measureMode: null }),
}))