초기 커밋: 스테이션(측점) 기반 주행영상 플레이어
- 클라이언트(React/Vite): Video.js 플레이어, 하단 스테이션바, POI/구조물 영상 오버레이, 카메라 파라미터 보정, 미니맵/RoutePanel - 서버(Express): Range 스트리밍, HLS 변환, 프레임 추출, tus 업로드, 주석 API - 최근 작업: 스테이션바 종점역 표출/양끝 정렬/미도착(재생불가) 표시, POI 라벨 '구분' 우선·컴팩트 팝업·겹침제외 토글·동일좌표 다중행, 진행방향(상/하) 우선 표출, 커서 배지 크기·픽셀 떨림 개선 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 클라이언트 지리정보 스토어 (Zustand)
|
||||
*
|
||||
* 폴더 선택으로 파싱한 드론 프레임 / POI / 측점 / 중심선 / ENU 원점을 보관한다.
|
||||
* 4개 소비 컴포넌트(GeoSearch / StationVerify / StationOverlay / RoutePanel)가
|
||||
* 이 스토어를 단일 소스로 구독한다(서버 /api/geo/* 대체).
|
||||
*
|
||||
* playerStore.ts 패턴을 따른다.
|
||||
*
|
||||
* POI 위치 보정(poiOverrides): 마우스 드래그로 만든 title→{lat,lon,z} 맵.
|
||||
* basePois(파싱 원본)에 applyPoiOverrides 를 적용한 결과가 pois 다.
|
||||
* 내보내기/가져오기 파일(`<base>_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;
|
||||
/** v2.0 CSV(03)교량/04)터널/06)구교) 유래 구조물 + route.json 보정. */
|
||||
structures: RouteStructure[];
|
||||
/** 측점 비고에서 추출한 방향전환점 목록. */
|
||||
directionChanges: DirectionChange[];
|
||||
|
||||
/**
|
||||
* 폴더 선택 파일에서 지리정보를 파싱해 스토어에 적재한다.
|
||||
* 발견한 영상 File 을 반환(없으면 null) — 호출자가 loadLocalFile 로 재생.
|
||||
*/
|
||||
loadFromFolder: (files: FileList | File[]) => Promise<File | null>;
|
||||
/** 단일 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[],
|
||||
};
|
||||
|
||||
export const useGeoStore = create<GeoStore>((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,
|
||||
});
|
||||
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 }),
|
||||
}));
|
||||
Reference in New Issue
Block a user