# DefVideo → GhiVideo 업그레이드 상세 문서

> 작성일: 2026-06-30
> 비교 기준: `b23042/DefVideo`(구버전) → `b23042/GhiVideo`(신버전)
> 두 저장소는 git 히스토리가 분리된 별도 저장소이며, 동일 코드베이스에서 파생되었다. 본 문서는 실제 소스 트리(`diff -r`)를 직접 비교하여 작성되었다.

---

## 0. 한눈에 보기

| 영역 | 변경 규모 | 평가 |
|------|----------|------|
| **서버 (server)** | `app.ts` +2줄, 신규 `elevation.ts`(69줄) | 거의 동일 — 골격 유지 |
| **공유 타입 (shared)** | 변경 없음 | 동일 |
| **클라이언트 (client)** | 약 **+3,000줄** 순증 | **대폭 발전** |

**핵심 결론**: 기반 골격(서버 스트리밍/HLS/업로드, 공유 타입)은 그대로 유지하면서, **측점(체이니지) 기반 주행영상 분석 도메인 기능**이 본격적으로 구현된 한 단계 발전한 버전이다. 세 갈래로 요약하면:

1. **V2.0 데이터 형식 대응** — 영상 옆 KMZ/KML을 1순위로 POI·구조물 추출(없으면 `building/` 5종 CSV 폴백), 측점 CSV 기반 중심선 생성, 자동 인코딩 감지, 방향전환점 추출
2. **투영 정확도 개선** — 지오이드 보정, 지면고도(DEM) 적용, 역투영 기반 위치 보정
3. **영상 오버레이/편집 UI 고도화** — 라벨 평활, 겹침 억제, 컴팩트 팝업, 드래그 편집, 나침반 미니맵

---

## 1. 파일 단위 변경 요약

### 변경된 기존 파일

| 파일 | 변경량 | 내용 |
|------|--------|------|
| `client/src/components/overlay/StationOverlay.tsx` | **+1,188 / −167** | 라벨 평활·겹침억제·구조물표출·컴팩트팝업·편집모드 (최대 변경) |
| `client/src/utils/geoData.ts` | **+660 / −111** | V2.0 폴더 파싱·구조물·방향전환점·POI보정 |
| `client/src/stationbar/StationBar.tsx` | +340 / −87 | 이동거리축·시설등급필터·종점역 미도착 |
| `client/src/stationbar/components/Timeline/Timeline.tsx` | +248 / −27 | 구조물 겹침필터·라벨 그룹핑 |
| `client/src/components/player/VideoPlayer.tsx` | +168 / −40 | 영상컨트롤 UI 통합·smoothTimeRef·폴더 드롭 |
| `client/src/utils/geoProjection.ts` | +138 / −5 | 지오이드 보정·역투영 함수 3종 |
| `client/src/types/geo.ts` | +54 / −7 | 구조물/방향전환점/보정 타입 |
| `client/src/store/geoStore.ts` | +47 / −3 | POI 보정 분리관리·구조물 상태 |
| `client/src/components/overlay/RoutePanel.tsx` | +27 / −5 | 구조물 필터·방향 우선순위 |
| `client/src/store/playerStore.ts` | +13 / −1 | videoReady·영상 해상도 상태 |
| `client/src/hooks/useVideoPlayer.ts` | +8 / −0 | 영상 로드 이벤트 추적 |
| `server/src/app.ts` | +2 / −0 | elevation 라우트 등록 |

### 신규 추가 파일 (DefVideo엔 없음)

| 파일 | 줄수 | 역할 |
|------|------|------|
| `client/src/components/overlay/Minimap.tsx` | 69 | 드론 방위각(heading-up) 나침반 미니맵 |
| `client/src/store/settingsStore.ts` | 90 | 사용자 표시 설정 중앙 관리(localStorage 영속) |
| `client/src/utils/chainage.ts` | 64 | 측점값(체이니지) 계산·중심선 투영 |
| `server/src/routes/elevation.ts` | 69 | DEM(표고) 데이터 프록시 API |

---

## 2. 데이터 처리 — V2.0 형식 대응 (`geoData.ts`)

가장 많이 변경된 유틸. 구버전은 `center.csv` + 단일 POI CSV 중심이었으나, 신버전은 **영상 옆(root)의 KMZ/KML을 1순위 소스**로 POI·구조물을 직접 추출하고, `building/` 하위 CSV는 **KMZ가 없을 때의 폴백**으로 둔다(`loadFolderGeoData`의 `useKmz` 분기). 측점만 KMZ에 없어 항상 CSV에서 읽되, 측점·역사 CSV는 `building/` 또는 root 어디서든 찾는다(폴더 삭제 대비). 즉 현재 데이터는 KMZ(root) + 측점 CSV(root)만으로 동작하며 building 폴더가 필수는 아니다.

### 2.1 자동 인코딩 감지
- **구버전**: 파일별로 `utf-8` / `euc-kr`를 코드에 하드코딩
- **신버전**: ArrayBuffer 앞 3바이트(BOM `EF BB BF`)를 검사해 UTF-8/EUC-KR 자동 선택 → 혼재된 인코딩 자동 처리

### 2.2 데이터 소스 우선순위 (KMZ 우선 · CSV 폴백)
```
1순위: 영상 옆(root) *.kml / *.kmz   → parseKmz → POI·구조물 직접 추출
       (KMZ에 POI/구조물 있으면 useKmz=true → 아래 building CSV 미사용)

측점:  01)측점.csv  (building/ 또는 root) → 측점 + 방향전환점 (KMZ에 없어 항상 CSV)
역사:  02)지장물_역사.csv (building/ 또는 root) → 스테이션바 전용

폴백(KMZ 없을 때만):
  building/02)지장물.csv     → POI(지장물)
  building/03)교량.csv        → 구조물(bridge)
  building/04)터널.csv        → 구조물(tunnel)
  building/05)출입문번호.csv  → POI(출입문)
  building/06)구교.csv        → 구조물(bridge, 구교 아이콘)
```
- `center.csv`를 더 이상 찾지 않고, **측점(01)을 측점값 순으로 이어 중심선을 생성**(`buildCenterlineFromStations()`)
- 지장물·교량·터널·구교·출입문 CSV는 `building/` 안에서만 찾지만, KMZ가 있으면 통째로 무시되므로 KMZ 기반 데이터에서는 building 폴더가 없어도 정상 동작한다.

### 2.3 CSV 파싱 견고성 강화
- `makeFieldIndexer()`: 헤더명 기반 인덱싱 실패 시 위치 인덱스로 폴백 → 깨진 EUC-KR 헤더도 안전
- `cell()`: 음수/범위 초과 인덱스에서 빈 문자열 반환 → 크래시 방지
- `rowProps()`: CSV 원본 속성을 `{k, v}[]` 쌍으로 추출 → UI 라벨 클릭 팝업에 그대로 표출

### 2.4 신규 파싱 항목
- **측점 + 방향전환점**(`parseStations()`): `Z좌표_한국`(정표고, EPSG:5186) 우선 사용, 비고 컬럼에서 `방향전환점(상행->하행, 02:05)` 정규식 추출 → `DirectionChange` 생성
- **구조물**(`parseStructures()`): 교량/터널/구교 3종 통합, 위경도 대한민국 범위(33–39°N, 124–132°E) 검증, `연장(m)·시설종별·분류` 채움
- **역사**: `type='station'`으로 변환 → 스테이션바에만 표시(영상 오버레이 제외)
- **KMZ 원본**(`parseKmz()`): 구글 어스 KMZ가 있으면 직접 추출(CSV 중복 회피), 측점만 항상 CSV 사용
- **POI 위치 보정**(`parsePoiOverrides()`/`applyPoiOverrides()`): 드래그 보정값을 `<base>_poi_overrides.json`(`{title: {lat,lon,z}}`)으로 저장/복원

---

## 3. 투영 정확도 개선 (`geoProjection.ts`, `chainage.ts`)

### 3.1 카메라 파라미터 신규 필드
- `geoidOffset`(m): 정표고(EL) → 타원체고 변환. 드론 `abs_alt`(타원체고)와 datum 일치 (대전 기본 25.8m, KNGeoid18)
- `poiZOffset`(m): POI 전용 표고 보정. 선로 지면 가정 오차를 패널에서 조절

### 3.2 카메라 좌표 신규 필드
- `distH`: 수평 거리(m) — POI 거리필터용
- `fwd`/`side`: 진행방향(yaw) 기준 앞쪽/옆쪽 거리(m) → **비등방 거리필터**(앞은 멀리, 옆은 가깝게) 구현 가능

### 3.3 역투영 함수 3종 신규 (편집 기능의 수학적 기반)
- `worldFromPixel(px, py, range)`: 화면 픽셀 + 슬랜트거리 → 월드좌표(lat/lon/z). POI 드래그 보정(수평+수직 동시)
- `solveZForPixelY(...)`: 특정 화면 세로위치에 맞는 표고(z) 역산. "앞으로 밀려 보이는 슬라이드" 보정
- `groundPointFromPixel(px, py, zGround)`: 화면점과 특정 고도평면의 교차점(lat/lon). 지면 좌표 보정

### 3.4 측점/체이니지 계산 (`chainage.ts`, 신규)
- `kmFromTitle("157K970")` → `157970`(m), `fmtKm10()` → `"157k970"`
- `buildChainLine()`: 측점 → 평면투영 폴리라인
- `projectToChain()`: 드론 GPS → 폴리라인 투영 → `{km(측점값), offsetM(선로 수직이격)}`
- `nearestChainPoint()`: 최근접 측점 검색 → StationBar·VideoPlayer HUD 공유

---

## 4. 영상 오버레이 고도화 (`StationOverlay.tsx`)

### 4.1 라벨 평활 & 이상치 거부
- **이상치 거부**: 한 프레임에 화면폭 12%(`REJECT_DIST`) 이상 점프하면 노이즈로 보고 이전 위치 유지. 단 연속 8프레임(`MAX_REJECT_FRAMES`) 초과 시 수용 → 시크/재등장에서 멈추지 않음
- **속도 적응형 평활(One Euro 방식)**: 떨림(방향 왕복)은 강하게 평활, 실제 이동(방향 일관)은 즉시 추종 → 1배속 떨림 제거 + 빠른 배속 지연 해소
- 조절 파라미터: `smoothMinAlpha`(정지 시 최소 추종), `smoothSpeedRef`(즉시추종 기준속도)

### 4.2 POI 겹침 억제
- 화면상 가로 10%·세로 3.5% 이내 마커는 겹침으로 판정 → 드론에 더 가까운 것만 표시
- 같은 건물 여러 업체/시설 중 주요한 것만 노출 → 화면 정리

### 4.3 구조물 표출 & 컴팩트 팝업
- 교량/터널/구교를 POI처럼 라벨로 표출(이모지 🌉 구교, 🚪 출입문 추가)
- **컴팩트 필드**: 라벨 옆 항상 표시(시설종별 → 구조형식 → 연장 → 폭 → 용도 → 준공연도 순)
- 클릭 시 전체 팝업 확장, 다시 클릭 시 컴팩트로 토글. **다중 팝업** 누적 가능, ESC로 정리

### 4.4 POI 높이 모드 & DEM 자동 적용
- **드론 높이 기준**: POI를 드론보다 N미터 아래 고정(`droneHeightDrop`)
- **지면고도 기준**: POI를 실제 지형고도에 고정 → 드론 오르내려도 지물에 붙음
- 서버 elevation API(SRTM 30m DEM)로 모든 좌표의 실제 표고 일괄 적용 → 먼 곳에서 밀리는 현상 완화

### 4.5 드래그 편집 & 세로화각 보정
- POI를 마우스로 드래그 → `groundPointFromPixel`로 위경도 역산
- 표고 슬라이더로 높이 미세조정, 개별 리셋 가능
- **세로화각 보정 모드**: 라벨이 상하로 어긋날 때 POI를 실제 위치로 드래그 → 세로 화각(sensorH)만 역산 자동보정
- 보정값 JSON 내보내기/가져오기, 일괄 초기화

### 4.6 좌표 정렬 (object-fit:cover 보정)
- `coverRef`: 영상이 `object-fit:cover`로 크롭된 영역을 매 프레임 계산 → 정규좌표(0–1) ↔ 화면 px 정확 변환 → 포인터 히트테스트 정확

---

## 5. 나침반 미니맵 (`Minimap.tsx`, 신규)

- 우측 상단 고정 150×150px 아날로그 나침반 위젯(heading-up)
- 고정 프레임(배경원/외곽링) + 회전 카드(6° 간격 60눈금 + N 문자 + 빨강/흰 삼각형)
- 부모의 `--rot` CSS 변수로 부드럽게 회전, StationOverlay RAF에서 매 프레임 갱신(360° 언랩)
- 효과: 영상만으로 드론이 향하는 방향을 한눈에 파악

---

## 6. 하단 측점바 & 타임라인 (`StationBar.tsx`, `Timeline.tsx`)

### 6.1 측점 정확도 개선
- **이동거리축 추가**: 드론 실제 GPS 이동량을 시간축에 반영 → 호버(공중 대기) 구간 가시화, 전진/후진 색 리본(주황/하늘색) 구분
- **영상 FPS 자동 적응**: 고정 29.97 대신 `마지막 프레임 / 재생시간`으로 영상별 계산 → 측점 배지 정확도 향상
- **kmExists 플래그**: 실제 측점값 존재 여부 구분 → 좌표 반경만으로 잡힌 통과는 라벨 제외

### 6.2 시설등급 필터 & 구조물 표시
- 1종/2종/3종/기타 선택적 표시(settingsStore 연동, 체크박스 즉시 반영)
- 긴 이름 2줄 분할(CJK 1.0/영문 0.55 가중치)
- 상/하행 평행 변형은 영상 진행방향에 따라 하나만 표시
- 동명 구조물 반복 통과 → 중앙 라벨 1개 + 통과점 점선 드롭 + 수평 브래킷

### 6.3 종점역 미도착 시각화
- 영상이 종점역에 미도달 시 트랙 우측을 회색(재생불가)으로 표시(상한 15%)
- 종점역 마커를 '속 빈 링' 스타일(미도착)로

---

## 7. 영상 플레이어 (`VideoPlayer.tsx`, `useVideoPlayer.ts`, `playerStore.ts`)

- **영상제어 UI 수평 통합**: 좌하단 컨트롤을 한 줄 가로 배열(`영상제어 | 배속 | 프레임 | 좌측패널 | 선형 | 드론궤적 | GPS/고도 | 시설등급`), flex-wrap 자동 줄바꿈
- **smoothTimeRef**: 벽시계 기준 단조 보간 시간 → StationBar가 ref로 직접 읽어 React 리렌더 없이 transform 갱신 → 60fps 부드러운 커서, 시크 시 즉시 재동기화
- **videoReady 게이트**: `loadeddata` 이후에만 오버레이 렌더 → 영상보다 먼저 그려지는 깜빡임 제거
- **영상 해상도 추적**: `videoWidth/videoHeight`로 object-fit:cover 정렬 정확화
- **폴더 드롭 지원**: `webkitGetAsEntry()` 디렉토리 재귀 순회 → 영상+측점/POI 폴더 통째 로드
- **드론 정보 HUD**: 현재 프레임 최근접 드론 GPS/고도 항상 표시
- **버튼 정렬**: `min-w-[96px]` 고정폭 + amber 활성색, 프레임번호 6자리 고정폭

---

## 8. 설정 중앙 관리 (`settingsStore.ts`, 신규)

Zustand + persist(`localStorage: 'ghivideo.settings'`)로 다음 표시 설정을 영속화한다.

| 키 | 기본값 | 용도 |
|----|--------|------|
| `gradeFilter` | `{1종:T, 2종:T, 3종:F, 기타:T}` | 시설종별 표시 필터 |
| `poiOverlapExclude` | `true` | 겹친 POI 숨김 |
| `showRoutePanel` | `true` | 좌측 노선 패널 |
| `showStationDiag` | `false` | 측점 진단 HUD |
| `showCenterline` | `true` | 선형(중심선) 오버레이 |
| `showDronePath` | `true` | 드론 궤적 오버레이 |

- 누락된 키는 기본값으로 자동 보충(스키마 진화 대비)
- `isGradeVisible()` 공유 함수로 StationBar·RoutePanel·StationOverlay가 동일 규칙 적용

---

## 9. 서버: 고도 API 프록시 (`elevation.ts`, 신규)

- 브라우저 COEP/CSP 제약으로 클라이언트가 외부 DEM API를 직접 호출 불가 → **서버 중계**
- `GET /api/elevation?lat=36.4,36.5&lon=127.4,127.5` (배치, 콤마 구분)
- 응답: `{ elevation: number[], source }`
- 폴백 전략: ① opentopodata SRTM 30m(정밀) → ② open-meteo 90m(폴백)
- `app.ts`에 `app.use('/api/elevation', elevationRouter)` 등록 (서버 변경은 이것 + 라우트 파일이 전부)

---

## 10. 타입/스토어 변경 요약

### `geo.ts` 신규/확장
- `GeoPoint.props`: CSV 원본 속성 `{k,v}[]` (팝업 표시)
- `RouteStructure`: `lengthM`(연장) · `category`(교량/터널/구교/역사) · `grade`(시설종별) · `props` 추가
- `DirectionChange`(신규): `station` · `from`/`to` · `atSeconds`
- `PoiOverride` / `PoiOverrideMap`(신규): 드래그 보정값
- `RouteMeta.endStationGapMeters`: 종점역 미도착 거리
- `FolderGeoData`: `structures` · `directionChanges` · `poiOverrides` 추가

### `geoStore.ts`
- POI 보정 분리관리: `basePois`(원본) + `poiOverrides`(보정맵) → `applyPoiOverrides()` 결과가 `pois`
- 신규 상태: `structures`, `directionChanges`
- 신규 메서드: `setPoiOverride()`, `clearPoiOverride()`, `setPoiOverrides()`
- `loadFromFolder()`: Promise.all 병렬 로딩으로 8개 파서 동시 실행

---

## 11. 사용자 체감 개선 총괄

| 항목 | 개선 효과 |
|------|----------|
| **라벨 안정성** | 떨림/튐 제거 — 이상치 거부 + 속도적응 평활 |
| **화면 가독성** | 겹친 POI/구조물 자동 숨김 |
| **정보 탐색** | 다중 팝업 + 컴팩트/전체 토글 + ESC 정리 |
| **위치 정확도** | 지오이드 보정 + DEM 실제 표고 + 역투영 편집 |
| **편집 편의** | 드래그 + 표고 슬라이더 + 세로화각 자동보정 + JSON 저장 |
| **측점 정확도** | 이동거리축(호버 가시화) + 영상별 FPS 자동 |
| **커서 부드러움** | smoothTimeRef 단조보간 → 60fps |
| **작업 효율** | 폴더 통째 드롭 로드, 설정 localStorage 영속 |
| **방향 인지** | 나침반 미니맵으로 드론 방위각 실시간 표시 |
| **오버레이 안정** | videoReady 게이트로 깜빡임 제거 |

---

*본 문서는 `DefVideo`와 `GhiVideo`의 소스 트리 전체 비교(`diff -r`) 및 주요 변경 파일의 라인 단위 분석을 통해 작성되었다.*
