/**
* 클라이언트 지리정보 스토어 (Zustand)
*
* 폴더 선택으로 파싱한 드론 프레임 / POI / 측점 / 중심선 / ENU 원점을 보관한다.
* 4개 소비 컴포넌트(GeoSearch / StationVerify / StationOverlay / RoutePanel)가
* 이 스토어를 단일 소스로 구독한다(서버 /api/geo/* 대체).
*
* playerStore.ts 패턴을 따른다.
*
* POI 위치 보정(poiOverrides): 마우스 드래그로 만든 title→{lat,lon,z} 맵.
* basePois(파싱 원본)에 applyPoiOverrides 를 적용한 결과가 pois 다.
* 내보내기/가져오기 파일(`_poi_overrides.json`)로 영속화한다.
*/
import { create } from 'zustand';
import type {
DroneFrame,
GeoPoint,
CenterlinePoint,
GeoOrigin,
RouteMeta,
RouteStructure,
DirectionChange,
PoiOverride,
PoiOverrideMap,
} from '../types/geo';
import { loadFolderGeoData, applyPoiOverrides } from '../utils/geoData';
interface GeoStore {
loaded: boolean;
frames: DroneFrame[];
pois: GeoPoint[]; // type==='poi' (poiOverrides 적용 결과)
basePois: GeoPoint[]; // 파싱 원본 (보정 전) — 보정 재적용용
poiOverrides: PoiOverrideMap; // title → {lat,lon,z}
stations: GeoPoint[]; // type==='station' (stationOrder 정렬)
centerline: CenterlinePoint[];
origin: GeoOrigin | null;
baseName: string | null;
routeMeta: RouteMeta | null;
/** KMZ 원본 구조물(교량/터널/구교/역사) + route.json 보정. */
structures: RouteStructure[];
/** 측점 비고에서 추출한 방향전환점 목록. */
directionChanges: DirectionChange[];
/** 마지막 폴더 로드에서 KMZ(POI·구조물 원본)가 누락됐는지. true면 재구축 필요. */
kmzMissing: boolean;
/**
* 폴더 선택 파일에서 지리정보를 파싱해 스토어에 적재한다.
* 발견한 영상 File 을 반환(없으면 null) — 호출자가 loadLocalFile 로 재생.
*/
loadFromFolder: (files: FileList | File[]) => Promise;
/** 단일 POI 보정 설정/갱신 (드래그 종료 시). title 키. */
setPoiOverride: (title: string, ov: PoiOverride) => void;
/** 단일 POI 보정 해제. */
clearPoiOverride: (title: string) => void;
/** 보정맵 전체 교체 (가져오기). */
setPoiOverrides: (map: PoiOverrideMap) => void;
clear: () => void;
}
const EMPTY = {
loaded: false,
frames: [] as DroneFrame[],
pois: [] as GeoPoint[],
basePois: [] as GeoPoint[],
poiOverrides: {} as PoiOverrideMap,
stations: [] as GeoPoint[],
centerline: [] as CenterlinePoint[],
origin: null as GeoOrigin | null,
baseName: null as string | null,
routeMeta: null as RouteMeta | null,
structures: [] as RouteStructure[],
directionChanges: [] as DirectionChange[],
kmzMissing: false,
};
export const useGeoStore = create((set) => ({
...EMPTY,
loadFromFolder: async (files) => {
const data = await loadFolderGeoData(files);
set({
loaded: true,
frames: data.frames,
basePois: data.pois,
poiOverrides: data.poiOverrides,
pois: applyPoiOverrides(data.pois, data.poiOverrides),
stations: data.stations,
centerline: data.centerline,
origin: data.origin,
baseName: data.baseName,
routeMeta: data.routeMeta,
structures: data.structures,
directionChanges: data.directionChanges,
kmzMissing: data.kmzMissing,
});
return data.videoFile;
},
setPoiOverride: (title, ov) =>
set((s) => {
const poiOverrides = { ...s.poiOverrides, [title]: ov };
return { poiOverrides, pois: applyPoiOverrides(s.basePois, poiOverrides) };
}),
clearPoiOverride: (title) =>
set((s) => {
const poiOverrides = { ...s.poiOverrides };
delete poiOverrides[title];
return { poiOverrides, pois: applyPoiOverrides(s.basePois, poiOverrides) };
}),
setPoiOverrides: (map) =>
set((s) => ({ poiOverrides: map, pois: applyPoiOverrides(s.basePois, map) })),
clear: () => set({ ...EMPTY }),
}));