지리정보 · 측점 · 프레임 맵핑 구조

abcvideo — 시간 기준 → Station(측점) 기준 재생 전환을 위한 데이터 맵핑 정리 · 2026-06-17

한 줄 요약 — 영상과 지리정보를 잇는 단 하나의 키는 프레임 번호(frame_cnt)다. 측점 기준 재생이란 결국 측점 → 대표 프레임 → currentTime = frame / fps 로 seek 하는 역방향 흐름이다.

1. 전체 맵핑 흐름

영상 프레임currentTime
frame_cnt드론 CSV 행
드론 자세lat·lon·alt·yaw·pitch·roll
투영핀홀 카메라
측점·POI·중심선화면 픽셀(0~1)

화살표는 현재 구현된 시간 → 지리정보(표시) 단방향. Station 기준 재생은 이 화살표를 반대로 타는 것 (§5).

geo(lat, lon, z)
   └─ 위경도+표고를 월드 ENU(m) 좌표로 변환  (원점 = 첫 측점)
        └─ 드론(카메라) 위치를 빼서 상대 벡터
             └─ 카메라 회전 적용  R_w2c = R_align · R_b2w(yaw,pitch,roll)ᵀ
                  └─ 핀홀 투영  px = 0.5 + (Xc/Zc)·(f/sensorW)
                               py = 0.5 + (Yc/Zc)·(f/sensorH)
                       └─ 화면 정규화 좌표 (0~1) → 캔버스 오버레이

2. 어떤 파일이 무엇을 맵핑하나

데이터 소스 (입력)

위치: GEO_DATA_DIR (기본 samplevideo/). 인코딩은 EUC-KR/UTF-8 자동 감지.

파일역할핵심 컬럼 / 키로더
*회덕*.csv
(POI·측점 제외)
드론 비행 로그 — 영상↔지리 연결의 본체. SRT 자막에서 추출된 프레임별 GPS·자세. frame_cnt, latitude, longitude, altitude, yaw, pitch, roll, focal_len geoMatch.ts
loadFrames()
building/
*POI*위경도*.csv
지장물·건물·터널·교량·역사. (_타원체고 버전 우선 사용) title, category_clean, lat, lon, z geoMatch.ts
loadPois()
building/
*측점*위경도*.csv
측점 — km측점(예 12K345). type=station. title, lat, lon, z geoMatch.ts
loadPois()
pythonsource/
input/center.csv
선로 중심선 224점 — 측점/POI의 표고(z) 스냅 기준선. lat(1), lon(2), 타원체고 h(5) geoMatch.ts
loadCenterline()
*.srt terrain offset(abs_alt − rel_alt) 산출용. 현재 로드만 하고 거의 미사용. rel_alt, abs_alt geoMatch.ts
loadTerrainOffset()

처리 / 변환 (로직)

파일책임
server/src/services/geoMatch.ts CSV 로드(싱글턴 캐시) · 평면근사 ENU 투영 · findFramesForPoi(이름→프레임) · findPoisForFrame(프레임→POI) · getWorldOrigin(원점=첫 측점) · stationOrder(km 정렬)
server/src/routes/geo.ts REST: /api/geo/pois · /search · /frame/:n · /frames · /centerline
client/src/utils/geoProjection.ts 정밀 투영 — EPSG:5186 TM(proj4), Python advanced_tuner_v2.py와 동일 회전식. 오버레이가 사용.

표시 / 조작 (UI)

파일역할
client/.../overlay/StationOverlay.tsx 실시간 캔버스 오버레이. 전 프레임 픽셀 위치를 Map<frameNum,…>로 사전계산 → RAF 루프에서 조회·보간·EMA 스무딩 후 그림.
client/.../geo/StationVerify.tsx 측점 탭. 측점 목록 클릭 → /api/geo/search → 최적 프레임으로 seek. 측점 재생의 토대
client/.../geo/GeoSearch.tsx 지리정보 탭. 이름→프레임 검색 + 현재 프레임→보이는 건물 역조회.
client/.../player/VideoPlayer.tsx frame↔time 변환의 단일 출처. frame = round(currentTime·VIDEO_FPS), seek는 currentTime(frame/VIDEO_FPS).
client/src/App.tsx 우측 사이드바 탭(주석/지리정보/측점) 배선. onSeekToFrame={f => handleSeek(f/fps)}.

3. 프레임 번호 = 모든 맵핑의 조인 키

// 영상 시간 ↔ 프레임  (CFR 가정, 누적연산 금지)
time  = frame / fps
frame = round(currentTime * fps)

// fps 값이 코드에 두 개 존재
client  VIDEO_FPS = 30000/1001 = 29.97   // VideoPlayer.tsx, StationOverlay.tsx
server  DEFAULT_FPS = 30                  // geoMatch.ts (FrameMatch.time 계산용)
주의 — fps 불일치(29.97 vs 30). seek는 프레임 번호로 하므로 실질 오차는 작지만, 서버 FrameMatch.time 값은 30 기준이라 부정확. 측점 기준 타임라인에서 시간(초)을 직접 쓰려면 반드시 29.97로 통일할 것.

4. 현재 제공되는 두 방향 질의

방향함수 / API동작UI
이름 → 프레임 findFramesForPoi()
GET /api/geo/search?q=
측점/건물명 검색 → 전 프레임 투영 → FOV 안 프레임 수집 → 연속구간 그룹화(GAP=30프레임) → 구간별 가장 중심에 가까운 프레임 1개 선택 StationVerify / GeoSearch
프레임 → 이름 findPoisForFrame()
GET /api/geo/frame/:n
해당 프레임 드론 자세로 전 POI 투영 → FOV 안 목록 반환 (거리순) GeoSearch 역조회
측점 기준 재생의 핵심 빌딩블록은 이미 있다. findFramesForPoi(측점명)측점 → 대표 프레임 을, onSeekToFrame프레임 → seek 를 담당. 남은 일은 이를 전 측점에 대해 한 번에 묶어 km순 타임라인으로 만드는 것뿐.

5. Station(측점) 기준 재생 설계

현재(시간축) vs 목표(측점축)

현재 — 시간 기준목표 — 측점 기준
타임라인 단위초 / 프레임측점 (km측점, stationOrder 순)
탐색 동작seek bar 드래그측점 선택 → 그 측점이 가장 잘 보이는 프레임으로 점프
"다음" 동작+N초다음 km측점의 대표 프레임
핵심 데이터frame ↔ time측점 → 대표 프레임 인덱스 테이블

제안 흐름

측점 N개/api/geo/pois (station)
측점→프레임findFramesForPoi ×N
km순 정렬stationOrder
측점 타임라인[{측점, frame, time}]
seekcurrentTime=frame/29.97

측점 인덱스 데이터 모델

interface StationCue {
  title:    string;   // "12K345"
  km:       number;   // stationOrder()/1000 = 12.345
  frame:    number;   // findFramesForPoi 대표 프레임 (구간 중심)
  time:     number;   // frame / 29.97   ← fps 통일 필수
  distance: number;   // 카메라~측점 수평거리 (정확도 참고)
  pixelX: number; pixelY: number;        // 화면 내 위치 (품질 판단)
}
type StationTimeline = StationCue[];     // km 오름차순

구현 옵션

A. 클라이언트 일괄 호출 서버 변경 없음
측점 목록을 받아 측점마다 /api/geo/search 를 호출(또는 1회 Promise.all)해 StationCue[] 구성. StationVerify 가 이미 측점별 단건으로 하는 일을 전체로 확장하는 수준. 측점 수가 수십~수백이면 호출 비용 고려.

B. 서버 일괄 인덱스 API 신설 권장
GET /api/geo/station-index — 서버가 전 측점에 대해 findFramesForPoi 를 한 번에 돌려 StationCue[](km순)를 반환. 프레임/POI 캐시가 이미 싱글턴이라 추가 I/O 없음. 클라는 1회 호출로 측점 타임라인 확보 → 측점 슬라이더/리스트로 바로 재생.

재생 UI 결선 (기존 자산 재사용)

// 이미 존재: 측점 클릭 → seek  (StationVerify.tsx → App.tsx)
onSeekToFrame = (frame) => handleSeek(frame / (fps || 30));   // fps=29.97 통일

// 추가: 측점 슬라이더 / 이전·다음 측점 버튼
nextStation()  → timeline[idx+1].frame  → onSeekToFrame
prevStation()  → timeline[idx-1].frame  → onSeekToFrame

// 현재 재생 위치 → 현재 측점 역표시 (선택)
currentFrame → timeline 에서 가장 가까운 cue → "현재 12K345 부근"
설계 포인트

후속 / 결정 필요

생성: Claude Code · 소스 기준 docs/history/2026-06-17_지리정보-측점-프레임-맵핑구조-분석.md