wing-ops/frontend/src/common/store/mapStore.ts

133 lines
3.8 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: true, s101: false, threeD: false, satellite: false },
mapTypes: DEFAULT_MAP_TYPES,
toggleMap: (key) =>
set((s) => ({
mapToggles: { ...s.mapToggles, [key]: !s.mapToggles[key] },
})),
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;
}
}
// s57 기본값 유지
if (newToggles['s57'] === undefined && types.find((t) => t.mapKey === 's57')) {
newToggles['s57'] = true;
}
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 }),
}));