docs: 발표·특허·AI영상요약 자료 및 작업기록 추가

- 발표자료(좌표투영·측점기반재생) 갱신 + 그림/PDF
- 특허 출원 초안 및 도면
- AI 영상요약 서비스 작업기록(docs/history)
- 관련 scripts 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 11:20:03 +09:00
co-authored by Claude Opus 4.8
parent 5c8b499979
commit fe25ce00f1
78 changed files with 6186 additions and 15 deletions
@@ -0,0 +1,198 @@
# 데이터 폴더 입력 명세 — 스테이션기반 플레이어
> 웹플레이어가 **동영상이 있는 폴더**를 전달받아 재생할 때, 폴더 안에 어떤 파일이
> 어떤 형식으로 있어야 하는지를 정의한다.
> 근거: `client/src/utils/geoData.ts`(폴더 파서) 기준. 실데이터(v3.0, 대전조차장–회덕)로 검증.
---
## 1. 동작 개요
1. 사용자가 브라우저에서 **폴더를 선택**하면(`<input type="file" webkitdirectory>`), 폴더 내 전체 파일 목록이 플레이어에 전달된다.
2. 플레이어는 파일명 규칙으로 각 파일을 **자동 식별·파싱**한다(별도 설정 불필요).
3. 기준 이름 **base** = 영상 파일명에서 확장자를 뺀 것 (예: `상행)대전조차장-회덕.MP4` → base는 `상행)대전조차장-회덕`). 나머지 파일은 base 일치를 우선 매칭하고, 없으면 폴백 규칙으로 찾는다.
---
## 2. 폴더 구성 요약
| # | 파일 | 필수 | 역할 |
|---|------|------|------|
| 1 | `<base>.MP4` | **필수** | 드론 촬영 항공영상 (mp4/webm) |
| 2 | `<base>.csv` | **필수** | 드론 비행로그 — 프레임별 위치·자세각·초점거리 |
| 3 | `01)측점.csv` 또는 `측점.csv` | **필수** | 측점 목록(이정·좌표·실측 표고) → 중심선·스테이션바·POI 높이 보완의 근간 |
| 4 | `<base>.kmz` (또는 `.kml`) | **필수**(정책) | POI·구조물의 **유일한 소스** (지장물·교량·터널·출입문·구교·철도역) |
| 5 | `<base>.route.json` 또는 `route.json` | 선택 | 노선 기본 정보 + 구조물 위치 보정 |
| 6 | `<base>_poi_overrides.json` | 선택 | POI 드래그 보정 저장값 (역투영 결과) |
| — | `.srt`, `.qgz`, `.qgs` 등 그 외 파일 | 무시 | 플레이어가 읽지 않음 (작업용 부산물) |
**예시 폴더** (실데이터 v3.0):
```
상행)대전조차장-회덕 데이터(v3.0)/
├── 상행)대전조차장-회덕.MP4 ← 영상 (base 기준)
├── 상행)대전조차장-회덕.csv ← 드론 비행로그
├── 01)측점.csv ← 측점 (루트 또는 building/ 하위 모두 허용)
├── 상행)대전조차장-회덕.kmz ← POI·구조물 원본
├── 상행)대전조차장-회덕.route.json ← 노선 정보 (선택)
├── 상행)대전조차장-회덕.srt ← (무시됨)
└── 상행)대전조차장-회덕.qgz ← (무시됨, QGIS 작업파일)
```
---
## 3. 파일별 상세 명세
### 3.1 영상 — `<base>.MP4`
- **확장자**: `.mp4` 또는 `.webm` (대소문자 무관). `building/` 하위가 아닌 **루트의 첫 번째 영상 파일**을 사용한다.
- **코덱**: 브라우저 직접 재생이므로 **H.264 권장**.
- **용량**: 2GB 이상 가능 (`URL.createObjectURL()` 참조 방식 — 메모리에 통째로 올리지 않음).
- 이 파일명이 폴더 전체의 **base** 를 결정한다.
### 3.2 드론 비행로그 — `<base>.csv`
드론이 촬영과 동시에 기록한 프레임별 데이터. **헤더 이름으로 컬럼을 찾으며**(순서 무관), 헤더가 깨졌을 때만 위치 인덱스로 폴백한다.
| 컬럼(헤더명) | 의미 | 단위/규약 |
|---|---|---|
| `frame_cnt` | 프레임 번호 (0부터) | 정수 |
| `latitude` / `longitude` | 드론 위치 위도/경도 | 도(WGS84) |
| `altitude` | 드론 고도 | m (타원체고) |
| `yaw` | 기수 방위각 | 도 — 북=0, 시계방향 |
| `pitch` | 카메라 틸트 | 도 — 음수=아래 |
| `roll` | 롤 | 도 |
| `focal_len` | 초점거리 | mm (**35mm 환산**) |
- 추가 컬럼(`rel_alt`, `5186x`, `5186y`, `X좌표`, `Y좌표` 등)이 있어도 **무시되고 오류 없음**.
- `latitude` 가 숫자가 아닌 행은 건너뜀.
- **파일 선택 규칙**: 루트의 `.csv` 중 ① `<base>.csv` 정확 일치 우선, ② 없으면 파일명에 `POI` 가 없는 첫 `.csv` (영상명 비의존 폴백).
- **fps 자동 산출**: 최대 `frame_cnt` ÷ 영상 길이(초) → 표준 fps(24/25/29.97/30/50/60…)에 스냅. 따라서 **비행로그의 프레임 수는 영상의 실제 프레임 수와 일치해야 한다.**
### 3.3 측점 — `01)측점.csv` (또는 `측점.csv`)
철도 노선 측량 성과. 시간↔측점 대응표(스테이션바)와 선로 중심선, POI 높이 보완의 근간 데이터.
- **파일 찾기 규칙** (3단 폴백): ① `building/` 하위에서 파일명에 `01)측점` 포함 → ② 루트 포함 아무 위치에서 `01)측점` 포함 → ③ 파일명에 `측점` 포함.
- **컬럼** (헤더 이름 기준):
| 컬럼 | 필수 | 의미 |
|---|---|---|
| `측점` | **필수** | 이정 표기 (예 `157K900`) — 스테이션 정렬·표시 기준 |
| `lat` / `lon` | **필수** | 측점 좌표 (도, WGS84) — 숫자 아니면 해당 행 제외 |
| `Z좌표_한국` | 권장 | **실측 정표고(EL, m)** — 있으면 최우선 사용 |
| `Z좌표` | 폴백 | `Z좌표_한국` 이 없을 때 사용 |
| `비고` | 선택 | `방향전환점(상행->하행, 02:05)` 패턴이 있으면 방향전환점으로 추출 |
- **중심선 자동 생성**: v2.0 이후 `center.csv` 가 없으므로, 측점을 이정 순으로 이어 만든 폴리라인이 선로 중심선이 된다. → **측점 정확도가 곧 측점 투영·POI 높이 보완의 정확도.**
> ⚠️ **주의 (실데이터에서 발견)**: 상행 v3.0의 `01)측점.csv` 는 실측 표고가 `z` 컬럼에 있고 `Z좌표` 컬럼은 0.0 인데, 파서는 `Z좌표_한국` → `Z좌표` 순서로만 읽으므로 **표고가 0으로 로드된다**. 하행 파일처럼 표고 컬럼명을 **`Z좌표_한국` 으로 통일**하거나, 파서에 `z` 컬럼 폴백 추가가 필요하다.
### 3.4 POI·구조물 — `<base>.kmz` (또는 `.kml`)
**POI·구조물의 유일한 소스**다 (`building/` 하위 02)~06) CSV는 더 이상 읽지 않음). 루트의 bare `.kml` 이 있으면 우선, 없으면 `.kmz`(zip)를 해제해 `doc.kml` 을 읽는다.
- **KML 폴더 구조 → 분류 매핑**:
| KML 폴더명 (포함 키워드) | 분류 | 이름 필드 (description 표) |
|---|---|---|
| `03)교량` | 구조물 bridge | `구분` (+ `연장(m)`, `시설종별`) |
| `04)터널` | 구조물 tunnel | `구분` (+ `연장(m)`, `시설종별`) |
| `06)구교` | 구조물 bridge(구교) | `시설물명` (+ `연장(m)`) |
| `05)출입문번호` | POI (출입문) | `출입문번호` |
| `02)지장물` 등 그 외 | POI (지장물 등) | `구분``title` → placemark `name` 순 |
| — `source=KAKAO_RAIL` 또는 `category_clean=철도역/역사` | POI + **역사 구조물** (스테이션바에도 표출) | 위와 동일 |
- **Placemark 좌표**: `<coordinates>``lon,lat[,alt]` 우선. 없으면 description 속성 `lat`/`lon`. **한국 범위 검증**(위도 33~39, 경도 124~132) 밖이면 버림.
- **속성(description)**: HTML `<table>` 의 (키, 값) 행으로 기재 — `구분`, `연장(m)`, `시설종별`(1종/2종/3종/기타), `시설물명`, `출입문번호`, `source`, `category_clean`, `title`, 표고(`z`/`Z좌표`/`절대고도`) 등. 구글어스 재저장본의 표 변형(`<tbody>`, 엔티티 등)도 허용.
- **표고**: POI 속성에 `z`/`절대고도` 가 있으면 사용, 없으면 0 → 재생 시 **최근접 측점 실측 표고로 보완**된다.
- **누락 시**: 경고 로그 후 POI·구조물 없이 동작(측점·드론·영상은 정상). CSV 폴백은 없다.
### 3.5 노선 기본 정보 — `<base>.route.json` (선택)
루트의 `<base>.route.json` 우선, 없으면 `route.json`. UTF-8 JSON.
```json
{
"routeInfo": {
"name": "대전조차장-회덕", // 노선명 (표시용)
"direction": "상행", // 방향 라벨
"lengthKm": 4.25, // 구간 연장 (km)
"durationSec": 568, // 영상 길이 (초)
"startStationName": "대전", // 시점역
"endStationName": "신탄진", // 종점역
"stationTolerance": 20, // 측점 인정 반경(m) — 기본 40
"endStationGapMeters": 0 // 종점 미도착 거리(m) — 0이면 도착
},
"structures": [ // (선택) 구조물 보정 — name 매칭으로 override
{
"name": "회덕천교", // 괄호 이하 제거 후 부분일치 매칭
"station": "158k400", // 최우선: 이 측점값 위치에 배치 (숫자 m 또는 "158k400")
"offset": -10, // 진행방향 ±N m 미세조정
"startMileage": 158400, // 매칭 실패 시 폴백 이정
"endMileage": 158500,
"lat": 36.4031, "lon": 127.4216 // 좌표 직접 지정도 가능
}
]
}
```
- `structures` 항목은 KMZ 유래 구조물에 **명시된 필드만 덮어쓰고**, KMZ에 없는 항목은 새 구조물로 추가된다.
### 3.6 POI 위치 보정 — `<base>_poi_overrides.json` (선택)
영상에서 POI 라벨을 **드래그(역투영)** 해 보정한 좌표의 저장/배포 파일. 폴더에 있으면 로드 시 자동 적용된다.
```json
{
"baseName": "상행)대전조차장-회덕",
"overrides": {
"회덕터널": { "lat": 36.40312, "lon": 127.42169, "z": 66.1 }
}
}
```
- `overrides` 없이 평면 맵(`{ "회덕터널": {…} }`)도 허용. `z` 는 정표고(m). POI `title` 정확 일치로 매칭.
---
## 4. 공통 규칙 (인코딩·파싱)
| 항목 | 규칙 |
|---|---|
| CSV 인코딩 | **자동 감지** — 앞 3바이트가 UTF-8 BOM(EF BB BF)이면 UTF-8, 아니면 EUC-KR. 파일별 하드코딩 없음 |
| CSV 컬럼 | **헤더 이름으로 탐색** (순서 무관, 여분 컬럼 무시). 헤더 훼손 시 위치 인덱스 폴백 |
| CSV 형식 | 큰따옴표 필드 지원, 빈 줄 무시, `\r\n`/`\n` 모두 허용 |
| KML | UTF-8. `.kml` > `.kmz`(내부 `doc.kml`) 우선순위 |
| JSON | UTF-8. 파싱 실패 시 조용히 무시(선택 파일이므로) |
| 폴더 구조 | `building/` 하위는 측점 CSV 탐색 외에는 사용하지 않음. 하위 폴더 깊이는 1단계(`building/`)만 인식 |
---
## 5. 파일 누락 시 동작
| 누락 파일 | 동작 |
|---|---|
| 영상 | 재생 불가 (폴더 인식 실패) |
| 드론 비행로그 | 측점 투영·POI 오버레이·fps 산출 불가 → 사실상 일반 플레이어로 전락 |
| 측점 CSV | 중심선·스테이션바·POI 높이 보완 불가 (시간축 재생만 가능) |
| KMZ/KML | **경고 로그** 후 POI·구조물 미표시 (측점·드론·영상은 정상 동작) |
| route.json | 기본값으로 동작 (노선명 등 미표시, stationTolerance=40) |
| poi_overrides.json | 보정 없이 원본 좌표 사용 |
---
## 부록 — 구현 근거 (소스 위치)
| 항목 | 위치 |
|---|---|
| 폴더 통합 로더 | `client/src/utils/geoData.ts``loadFolderGeoData()` |
| 영상/base 식별 | 같은 파일 — `findVideoFile()`, `deriveBaseName()` |
| 드론 CSV 파서 | 같은 파일 — `parseDroneFrames()` |
| 측점 CSV 파서 (+방향전환점) | 같은 파일 — `parseStations()` |
| KMZ/KML 파서 | 같은 파일 — `parseKmz()` |
| route.json / 구조물 병합 | 같은 파일 — `parseRouteMeta()`, `mergeStructures()` |
| POI 보정 로드/적용 | 같은 파일 — `parsePoiOverrides()`, `applyPoiOverrides()` |
| 인코딩 자동 감지 | 같은 파일 — `decodeBytes()` |
| 중심선 생성 | 같은 파일 — `buildCenterlineFromStations()` |
| (참고) 서버 검색 API 용 데이터 | `server/src/routes/geo.ts` — 환경변수 `GEO_DATA_DIR` 폴더에 동일 형상 필요 |