초기 커밋: 스테이션(측점) 기반 주행영상 플레이어
- 클라이언트(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,37 @@
|
||||
# RoutePanel 미니맵 추가 및 개선
|
||||
|
||||
**소요 시간**: 약 3시간
|
||||
**Context 사용량**: input ~60k / output ~20k tokens
|
||||
**이슈**: 없음
|
||||
|
||||
---
|
||||
|
||||
## 작업 내용
|
||||
|
||||
### RoutePanel 컴포넌트 생성 및 VideoPlayer 통합
|
||||
|
||||
- `client/src/components/overlay/RoutePanel.tsx` 신규 생성
|
||||
- `VideoPlayer.tsx`에 통합 (`showStations` 토글 연동)
|
||||
- props: `currentTime`, `visible`, `onSeek`
|
||||
|
||||
### 기능
|
||||
|
||||
- 세로 미니맵 패널 (화면 좌측)
|
||||
- 교량/터널 POI만 필터링하여 표시 (터널: 보라, 교량: 하늘색)
|
||||
- 초록 박스: 현재 카메라에 보이는 km 범위
|
||||
- 오렌지 마커: 현재 위치, 드래그하면 해당 측점으로 seek
|
||||
- 시점/종점 역명 표시 (역사 카테고리 POI 중 km 최소/최대)
|
||||
|
||||
### 수정 이력
|
||||
|
||||
- `StationOverlay.tsx` `alt` → `altitude` 타입 오류 수정
|
||||
- `cleanTitle()`: (상)/(하) 접미어 제거
|
||||
- km 방향 여러 차례 수정 끝에 확정: 높은 km = 위, 낮은 km = 아래
|
||||
- 패널 높이 80%, 겹침 간격 9%
|
||||
- 글씨 크기 +30%, 배경 투명도 밝게
|
||||
|
||||
## 산출물
|
||||
|
||||
- `client/src/components/overlay/RoutePanel.tsx` (신규)
|
||||
- `client/src/components/player/VideoPlayer.tsx`
|
||||
- `client/src/components/overlay/StationOverlay.tsx`
|
||||
@@ -0,0 +1,32 @@
|
||||
# StationOverlay 렌더링 끊김 분석 + history 자동화 규칙 추가
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~8k / output ~3k tokens
|
||||
**이슈**: 없음
|
||||
|
||||
---
|
||||
|
||||
## 작업 내용
|
||||
|
||||
### 1. StationOverlay 렌더링 파이프라인 분석
|
||||
- `client/src/components/overlay/StationOverlay.tsx` 코드 분석
|
||||
- 렌더링 구조: RAF 루프(60fps) + renderCache useEffect(드론 프레임 변경 시)
|
||||
- 끊김 원인 파악:
|
||||
- `panelDroneFrame` 상태 변경 시 `useEffect`에서 수백~수천 개 좌표 변환(`toCameraCoords` + `pixelFromCamera`)이 메인 스레드에서 동기 실행
|
||||
- 계산이 16ms 초과 시 RAF 프레임 드롭 → 뚝뚝 끊김
|
||||
- 측정 방법 제안: `performance.now()` 로깅으로 캐시 빌드 시간 측정
|
||||
|
||||
| 항목 | 실제 |
|
||||
|------|------|
|
||||
| RAF draw 횟수 | ~60fps (항상) |
|
||||
| 캐시 재빌드 횟수 | ~30fps (드론 프레임 변경 시) |
|
||||
| 끊김 원인 | 캐시 빌드 시 메인 스레드 블로킹 |
|
||||
|
||||
### 2. history 자동화 규칙 CLAUDE.md 추가
|
||||
- 사용자 기대: 작업 완료 시 날짜+주제별 history 파일 자동 작성
|
||||
- 현재 훅은 "리마인더/가드"만 담당, 파일 생성은 Claude 몫
|
||||
- CLAUDE.md 상단에 "작업 완료 시 필수 — 히스토리 기록" 섹션 추가
|
||||
|
||||
## 산출물
|
||||
- `CLAUDE.md` — 히스토리 기록 규칙 섹션 추가
|
||||
- `docs/history/2026-04-01_StationOverlay-렌더링분석.md` (이 파일)
|
||||
@@ -0,0 +1,52 @@
|
||||
# StationOverlay 렌더링 최적화 — 텍스트 스무딩
|
||||
|
||||
**소요 시간**: 약 90분
|
||||
**Context 사용량**: input ~40k / output ~12k tokens
|
||||
**이슈**: 없음
|
||||
|
||||
---
|
||||
|
||||
## 작업 내용
|
||||
|
||||
### 문제
|
||||
- 측점/지장물 텍스트 라벨이 영상 재생 중 뚝뚝 끊겨 표시됨
|
||||
- 드론 pitch/GPS 노이즈 + 30fps 데이터 → 60fps RAF 스텝 차이
|
||||
|
||||
### 해결 과정
|
||||
|
||||
1. **원인 분석**
|
||||
- `renderCacheRef` useEffect에서 CL 500개 × toCameraCoords = 매 33ms 메인 스레드 블로킹
|
||||
- 텍스트는 31개로 작은 부분이었음, CL이 실제 병목
|
||||
- 디버그 패널 추가로 firstY 값이 5-7px 불규칙 점프 확인
|
||||
|
||||
2. **텍스트 사전 계산 Map 도입**
|
||||
- `labelMapRef: Map<frameNum, {stationLabels, poiMarkers}>` 전 프레임 precompute
|
||||
- `requestIdleCallback` 백그라운드 계산 (메인 스레드 비블로킹)
|
||||
- RAF: `Map.get(frameNum)` O(1) 조회만
|
||||
|
||||
3. **데이터 스무딩 (smoothFrame)**
|
||||
- ±N 프레임 이동 평균 (yaw는 sin/cos 평균 후 atan2)
|
||||
- UI 슬라이더로 조절 가능 (0~60fr)
|
||||
- 기본값: smooth=10
|
||||
|
||||
4. **30fps→60fps 보간 (frac interpolation)**
|
||||
- `performance.now()`로 prop 업데이트 이후 경과 시간 추정
|
||||
- 현재/다음 프레임 사이 선형 보간 → 스텝 제거
|
||||
|
||||
5. **EMA (지수 이동 평균) 표시 위치 스무딩**
|
||||
- `displayedStRef`, `displayedPoiRef`: 각 라벨별 현재 표시 위치 유지
|
||||
- RAF마다: `pos = prev + (target - prev) × α`
|
||||
- UI 슬라이더로 α 조절 (0.01~1.0), 기본값: α=0.01
|
||||
- α=0.01: 매우 부드럽지만 약간 뒤처짐
|
||||
|
||||
6. **시각 개선**
|
||||
- 글씨 크기 2배 (9px→18px, 10px→20px)
|
||||
- bold + strokeText 4px 검정 테두리 (배경 박스 제거)
|
||||
- CL 중심선 재활성화
|
||||
|
||||
### 최종 기본값
|
||||
- smoothHalf: 10 (±333ms 이동 평균)
|
||||
- emaAlpha: 0.01 (~1600ms lag, 매우 부드러움)
|
||||
|
||||
## 산출물
|
||||
- `client/src/components/overlay/StationOverlay.tsx` 전면 개편
|
||||
@@ -0,0 +1,40 @@
|
||||
# history-hooks 적용 및 토큰 사용량 집계
|
||||
|
||||
**소요 시간**: 약 40분
|
||||
**Context 사용량**: input ~13k / output ~142k tokens (cache 포함)
|
||||
**이슈**: #0
|
||||
|
||||
---
|
||||
|
||||
## 작업 내용
|
||||
|
||||
### 1. history_hooks 리뷰 및 프로젝트 적용
|
||||
- `history_hooks/` 디렉토리의 5개 파일 분석
|
||||
- `.claude/hooks/`에 복사: `guard-history-fields.py`, `guard-history-fields.sh`, `guard-history-reminder.sh`, `session-context.sh`, `path.json`
|
||||
- `session-context.sh`, `guard-history-reminder.sh`를 `$CLAUDE_PROJECT_DIR` 기반 절대경로로 수정
|
||||
- `.claude/settings.json`에 3개 훅 이벤트 등록:
|
||||
- `PostToolUse(Edit|Write)` → `guard-history-fields.sh`
|
||||
- `Stop` → `guard-history-reminder.sh`
|
||||
- `UserPromptSubmit` → `session-context.sh`
|
||||
- `docs/history/` 디렉토리 생성
|
||||
|
||||
### 2. 일자별 토큰 사용량 집계
|
||||
- `~/.claude/projects/d--MYCLAUDE-PROJECT-abcvideo/*.jsonl` 파싱
|
||||
- `requestId` 중복 제거 후 날짜별 집계
|
||||
- 결과: 총 974 요청, input 12,556 / output 135,445 / cache_create 3,655,924 / cache_read 91,618,473
|
||||
|
||||
### 3. Gitea 이슈 코멘트 등록
|
||||
- `kimminsung/dronevideoplayer#1`에 일자별 토큰 사용량 테이블 코멘트 추가
|
||||
|
||||
### 4. guard-history-reminder.sh 수정
|
||||
- 기존: `exit 0` → stderr 메시지만 출력, Claude 그냥 종료
|
||||
- 변경: 오늘 날짜 히스토리 파일 없으면 `exit 2`로 Claude 종료 차단 → 히스토리 작성 강제
|
||||
|
||||
## 산출물
|
||||
- `.claude/hooks/guard-history-fields.py`
|
||||
- `.claude/hooks/guard-history-fields.sh`
|
||||
- `.claude/hooks/guard-history-reminder.sh` (수정)
|
||||
- `.claude/hooks/session-context.sh`
|
||||
- `.claude/hooks/path.json`
|
||||
- `.claude/settings.json` (Stop, UserPromptSubmit 훅 추가)
|
||||
- `docs/history/` 디렉토리
|
||||
@@ -0,0 +1,53 @@
|
||||
# 2026-04-02 프레임 동기화, 렌더링 최적화, 터널링
|
||||
|
||||
## 작업 개요
|
||||
- 프레임 번호 불일치(Python vs abcvideo) 원인 분석 및 수정
|
||||
- StationOverlay 렌더링 끊김 개선
|
||||
- 외부 접속 터널링(cloudflared) 설정
|
||||
- 빨간선 하단 클리핑 수정
|
||||
- 좌표계/카메라 파라미터 시스템 설명
|
||||
|
||||
## 완료 항목
|
||||
|
||||
### 프레임 동기화 수정
|
||||
- **원인**: VFC 측정에서 첫 프레임 카운트 포함 → 29.97fps 영상이 31fps로 오감지
|
||||
- 29.97fps → 1초에 30프레임, elapsed≈1.001s, frameCount=31 → round(31/1.001)=**31fps**
|
||||
- `useFrameStep.ts`: VFC 첫 호출은 startTime만 기록, 카운트 제외
|
||||
- `VideoPlayer.tsx`: 프레임 표시를 `VIDEO_FPS=30000/1001(29.97)`로 계산 → Python SRT FrameCnt와 일치
|
||||
- `StationOverlay.tsx`: 드론 프레임 매칭을 `currentTime`(초) 기반으로 변경 (fps 오감지 무관)
|
||||
|
||||
### 빨간선 하단 클리핑 수정
|
||||
- 기존: 한쪽 끝점만 화면 밖이어도 세그먼트 전체 스킵
|
||||
- 수정: Cohen-Sutherland trivial reject (양쪽 다 같은 방향 밖일 때만 스킵)
|
||||
- SCREEN_M: 30 → 200으로 증가
|
||||
|
||||
### StationOverlay 렌더링 최적화
|
||||
- 드론 경로(흰색 선) 완전 제거
|
||||
- 좌표 변환(224점 proj4)을 useEffect로 이동 → 드론 프레임 변경 시만 실행
|
||||
- RAF 루프: 캐시된 픽셀 좌표만 읽어 draw (계산 없음 → 60fps 유지)
|
||||
- ResizeObserver로 캔버스 크기 별도 관리
|
||||
|
||||
### 기본값 ON
|
||||
- `showStations` 초기값 `false` → `true`
|
||||
|
||||
### 터널링
|
||||
- `vite.config.ts`: `allowedHosts: true` 추가
|
||||
- cloudflared 명령: `cloudflared tunnel --url http://localhost:5173`
|
||||
- PowerShell 환경변수 설정: `$env:VIDEOS_DIR="..."; npm run dev:server`
|
||||
|
||||
### 좌표 시스템 설명
|
||||
- 타원체고(h) = 표고(H) + 지오이드고(N=25.449m) 설명
|
||||
- EPSG:5186 TM 투영 원리
|
||||
- center.csv vs 측점_XY값.csv 역할 차이
|
||||
- 드론 카메라 회전 행렬(Rz×Rx×Ry) → 핀홀 투영 전체 흐름 설명
|
||||
|
||||
## 기술 부채 / 남은 이슈
|
||||
- 재생 중 끊김이 완전히 제거되지 않음 (데이터 자체가 29.97fps 이산 → 근본적 한계)
|
||||
- server-side geoMatch.ts는 여전히 구형 ENU 근사 사용 (클라이언트 렌더링에는 미사용)
|
||||
|
||||
## Git
|
||||
- 커밋: `30bec66` (frontend 브랜치)
|
||||
- push 완료
|
||||
|
||||
**소요 시간**: 180분
|
||||
**Context 사용량**: input 180k / output 18k tokens
|
||||
@@ -0,0 +1,27 @@
|
||||
# 동영상 로드 실패 (서버 중지) 복구
|
||||
|
||||
**이슈**: #0
|
||||
**소요 시간**: 10분
|
||||
**Context 사용량**: input 45k / output 3k tokens
|
||||
|
||||
## 증상
|
||||
좌측 동영상 목록에서 항목 클릭 시 "로드할 수 없음" 에러.
|
||||
|
||||
## 원인
|
||||
- 백엔드 서버(pm2 프로세스 `abcVideo`, 포트 55173)가 **stopped 상태**였음.
|
||||
- 프론트엔드와 API가 동일 포트(노드 정적 서빙)에서 동작하므로, 서버가 죽으면 `/api/stream/:videoId` 요청이 전부 실패 → 영상 로드 불가.
|
||||
- pm2 error 로그상 과거 `EADDRINUSE`(포트 3030 충돌)로 재시작 실패 이력이 있었고 stopped로 남아 있었음.
|
||||
- 코드(URL 인코딩, Range Request, 한글·괄호 포함 파일명 처리)는 정상.
|
||||
|
||||
## 조치
|
||||
1. `pm2 restart abcVideo --update-env` → online 복구
|
||||
2. 검증
|
||||
- `GET /api/videos` → `하행)회덕-대전조차장.MP4` 반환
|
||||
- `GET /api/stream/...` (Range 0–1MB) → HTTP 206, 1MB 수신
|
||||
- `GET /api/meta/...` → HTTP 200
|
||||
3. `pm2 save`로 프로세스 목록 저장
|
||||
|
||||
## 후속 참고
|
||||
- `FFmpeg not found in PATH` 경고 → HLS 변환/프레임 추출은 FFmpeg 설치 후 동작 (즉시 재생은 영향 없음).
|
||||
- `client/vite.config.ts` 프록시 타깃이 `localhost:3030`인데 실제 서버 포트는 55173.
|
||||
프로덕션 정적 서빙에는 무관하나, Vite 개발 서버(`npm run dev`) 사용 시 API 프록시 불일치로 동작 안 함 → 개발 모드 사용 시 포트 정정 필요.
|
||||
@@ -0,0 +1,76 @@
|
||||
# 폴더 임포트 v2.0 데이터 형상 전환
|
||||
|
||||
**소요 시간**: 약 16분 (기능개발 에이전트 기준)
|
||||
**Context 사용량**: input ~추정 / output ~107k tokens (기능개발 에이전트 subagent_tokens 106,989 기준)
|
||||
|
||||
## 작업 개요
|
||||
|
||||
폴더 선택 시 로딩하는 지리정보 데이터를 v1.0 형상에서 v2.0 형상으로 전환했다.
|
||||
v1.0(POI_위경도값/측점_위경도값/타원체고/center.csv 의존)을 더 이상 지원하지 않고,
|
||||
실측 납품 폴더 구조(드론 base.csv + building/ 하위 번호 CSV + route.json 보정)에 맞춘 v2.0 전용 파서로 전면 재작성했다.
|
||||
|
||||
### 작업 배경 (v1.0 → v2.0)
|
||||
- v1.0은 별도 가공 산출물(`POI_위경도값`, `측점_위경도값`, `center.csv`, 타원체고 컬럼)에 의존 → 실제 납품 폴더와 컬럼/인코딩이 불일치
|
||||
- v2.0 실데이터는 한 폴더에 드론 CSV(UTF-8)와 측량 CSV(EUC-KR)가 혼재하고, 중심선(center.csv)이 없음
|
||||
- 따라서 인코딩 자동 감지 + 번호 CSV 직접 파싱 + 측점 기반 중심선 생성으로 구조를 단순화
|
||||
|
||||
## 변경 파일과 핵심 내용
|
||||
|
||||
### client/src/types/geo.ts
|
||||
- `DirectionChange` 타입 신설: `{ station, from, to, atSeconds }`
|
||||
- `RouteStructure` 에 `lengthM?`, `category?` 추가 (type 유니온 `bridge|tunnel|station` 유지, 구교 → `bridge`)
|
||||
- `FolderGeoData` 에 `structures: RouteStructure[]`, `directionChanges: DirectionChange[]` 추가
|
||||
|
||||
### client/src/utils/geoData.ts (전면 재작성, v2.0 전용)
|
||||
- 인코딩 자동 감지 `decodeBytes(buf)`: 앞 3바이트 `EF BB BF` → UTF-8, 아니면 EUC-KR, 잔여 `U+FEFF` 제거
|
||||
- `readCsv(file)` 시그니처 단순화 (인코딩 인자 제거)
|
||||
- `makeFieldIndexer`: 헤더명 `indexOf` 실패 시 위치 인덱스 폴백
|
||||
- 신규 파서: `parseStations` / `parsePois` / `parseStructures` / `buildCenterlineFromStations` / `mergeStructures`
|
||||
- v1.0 코드 제거: `POI_위경도값`/`측점_위경도값`/타원체고 식별, `center.csv` 로더, 인코딩 하드코딩 분기
|
||||
|
||||
### client/src/store/geoStore.ts
|
||||
- `structures`, `directionChanges` 적재
|
||||
|
||||
### client/src/stationbar/StationBar.tsx
|
||||
- 구조물 마커 소스를 `routeMeta.structures` → `geoStore.structures`(CSV 유래) 우선으로 전환
|
||||
- 위치 로직(station → lat/lon → 이름매칭 → mileage)은 lat/lon 지원으로 그대로 활용
|
||||
|
||||
## 컬럼 매핑 (실파일 검증)
|
||||
|
||||
| 구분 | 파일 | 인코딩 | 매핑 |
|
||||
|------|------|--------|------|
|
||||
| 드론 | `<base>.csv` | UTF-8 | frame_cnt/latitude/longitude/altitude/yaw/pitch/roll/focal_len — 변경 없음 |
|
||||
| 측점 | building/`01)측점.csv` | EUC-KR | title←측점, z←Z좌표, lat, lon, type=station; 비고에서 DirectionChange 추출(mm:ss→초) |
|
||||
| POI(1) | 루트 `<base>_POI.csv` | UTF-8 | title / category_clean / lat / lon, z=0 |
|
||||
| POI(2) | building/`02)지장물.csv` | EUC-KR | 명칭 / category='지장물' / lat / lon / z←Z좌표 |
|
||||
| 구조물 | `03)교량`(bridge) + `04)터널`(tunnel) + `06)구교`(bridge, category='구교') | — | name / lengthM←연장(m) / lat / lon; 위경도 범위(33~39 / 124~132) 밖 깨진 행 필터 |
|
||||
| 중심선 | center.csv 없음 | — | 측점 mileage 정렬 폴리라인으로 생성 |
|
||||
| 보정 | route.json | — | `mergeStructures` 가 name 매칭으로 offset/station/좌표 override + 미존재 항목 추가 (선택적 보정) |
|
||||
|
||||
## 결정 사항
|
||||
- **v2.0 전용화**: v1.0 지원 코드를 제거하고 v2.0 형상만 로딩
|
||||
- **구조물 = CSV 자동 + route.json 보정**: 03/04/06 CSV에서 자동 수집하고 route.json은 선택적 override/augment 레이어로만 사용
|
||||
- **측점 중심선**: center.csv 부재 → 측점 mileage 정렬 폴리라인을 중심선으로 사용
|
||||
- **비고 방향전환**: 측점 비고 텍스트에서 상/하행 전환 + 시각(mm:ss)을 정규식으로 추출하여 `DirectionChange` 생성
|
||||
|
||||
## 실데이터 검증값 (하행)회덕-대전조차장 데이터, v2.0)
|
||||
- 인코딩: 드론/POI/05)출입문/06)구교 = UTF-8(BOM), 01~04 = EUC-KR (BOM 자동감지로 일괄 처리)
|
||||
- directionChanges 2건: 157K930 상행→하행 @125s, 158K340 하행→상행 @75s
|
||||
- 측점 47개
|
||||
- POI 137개 (`<base>_POI` 114 + 지장물 23)
|
||||
- 구조물: 교량 12 (깨진 1행 필터) + 터널 2 + 구교 9
|
||||
|
||||
## 미구현 / 후속 과제
|
||||
- **05)출입문번호.csv 파싱 스킵**: 운영데이터로 재생바 아이콘이 과도해져 기본 표시 제외. 파싱 자체를 생략(Timeline 렌더는 bridge/tunnel/station만). 추후 필요 시 `parseDoors` 추가.
|
||||
- **directionChanges 와이어링**: 타입/스토어까지 적재 완료. StationBar `barSegments` 의 방향전환은 현재 연속 체이니지(chain) 자동 판정 로직을 유지하고, `directionChanges` 강제 분할은 미적용. 자동 판정이 영상과 일치하므로 회귀 위험 회피 목적의 후속 과제로 둠.
|
||||
|
||||
## 검수 포인트
|
||||
- 한글 인코딩 깨짐 없음 (BOM 자동감지)
|
||||
- 깨진 행 마커 제외 (위경도 범위 밖 필터)
|
||||
- route.json 보정 동작
|
||||
- RoutePanel / RouteInfoOverlay 는 POI category로 교량·터널을 필터 → v2.0에서는 structures가 분리되어 해당 목록이 비어 보일 수 있음(정상, 구조물은 StationBar로 표시)
|
||||
|
||||
## 빌드 결과
|
||||
- `nvm use 20` → `npm run build -w client` → `tsc && vite build`
|
||||
- ✓ built (215 modules), 에러 0
|
||||
- chunk > 500kB 경고는 기존 video.js 특성 (정상)
|
||||
@@ -0,0 +1,84 @@
|
||||
# 구조물 시설종별(1종/2종/3종) 표시 필터
|
||||
|
||||
**소요 시간**: 약 9분 (기능개발 에이전트 기준)
|
||||
**Context 사용량**: input ~추정 / output ~100k tokens (subagent_tokens 100,287 기준)
|
||||
|
||||
## 작업 개요
|
||||
|
||||
구조물(교량/터널/구교)의 **시설종별(1종/2종/3종)** 에 따라 재생바(StationBar)와 경로 패널(RoutePanel)의 표시를 필터링하는 기능을 추가했다.
|
||||
플레이어에 1/2/3종 체크박스를 두어 사용자가 종별 노출을 즉시 토글할 수 있고, 선택 상태는 localStorage 에 지속된다.
|
||||
|
||||
### 작업 배경 (사용자 요청)
|
||||
- 교량 등 **3종 구조물이 너무 많아** 재생바·패널이 혼잡 → 기본적으로 3종은 숨기고 싶다는 요청
|
||||
- 시설종별 1종/2종/3종을 **체크박스로 켜고 끄는** 토글 UI 요구
|
||||
- 기본값은 **1종 ☑ / 2종 ☑ / 3종 ☐** (3종만 기본 숨김)
|
||||
|
||||
## 변경 / 신설 파일과 핵심 내용
|
||||
|
||||
### client/src/types/geo.ts (변경)
|
||||
- `RouteStructure` 에 `grade?: string` 추가 (시설종별 원문 문자열, 예: `"3종"`, `"2종"`, `"기타"`)
|
||||
|
||||
### client/src/utils/geoData.ts — parseStructures (변경)
|
||||
- `pushFrom` spec 에 `gradeFallback`(시설종별 컬럼 위치 인덱스) 추가
|
||||
- 시설종별을 헤더명 `'시설종별'` `indexOf` 우선 + 위치 인덱스 폴백으로 읽음
|
||||
- **교량**: 시설종별 = `idx 1`
|
||||
- **터널**: 시설종별 = `idx 5`
|
||||
- **구교(06)**: 시설종별 컬럼 없음 → `gradeFallback` 미지정 → `grade` = `undefined`
|
||||
- 읽은 값은 `trim`, 빈 문자열이면 `undefined` 로 정규화
|
||||
|
||||
### client/src/store/settingsStore.ts (신설)
|
||||
- `zustand` + `persist` 미들웨어, 저장 키 `'ghivideo.settings'`
|
||||
- `gradeFilter` 기본값 `{ '1종': true, '2종': true, '3종': false }`
|
||||
- `setGradeFilter(grade, visible)` 액션
|
||||
- `KNOWN_GRADES`(`['1종','2종','3종']`) 와 공유 표시 규칙 `isGradeVisible` export
|
||||
- `merge` 로 저장본에 누락된 키를 기본값으로 보충 (버전 호환)
|
||||
|
||||
### 공유 표시 규칙 — isGradeVisible
|
||||
```
|
||||
!grade || !KNOWN_GRADES.includes(grade) || gradeFilter[grade]
|
||||
```
|
||||
- `grade` 없음(구교 등) → **항상 표시**
|
||||
- `grade` 가 KNOWN 미포함('기타'/'인상' 등) → **항상 표시**
|
||||
- 1/2/3종 → `gradeFilter[grade] === true` 일 때만 표시
|
||||
|
||||
### client/src/stationbar/StationBar.tsx (변경)
|
||||
- `storeStructuresRaw` 를 `isGradeVisible` 로 필터(`useMemo`, `gradeFilter` 구독) → `structureMarks` 가 필터된 목록을 사용
|
||||
|
||||
### client/src/components/overlay/RoutePanel.tsx (변경)
|
||||
- `filteredPois` 에 `isGradeVisible` 조건 추가(동일 규칙, `gradeFilter` 구독)
|
||||
|
||||
### client/src/components/player/VideoPlayer.tsx (변경)
|
||||
- 좌하단 컨트롤 그룹의 **배속 행 아래**에 `"시설등급"` 라벨 + 1/2/3종 체크박스 3개
|
||||
- 노출 조건: `showVideoControls && geoLoaded`
|
||||
- 각 체크박스는 `setGradeFilter` 에 바인딩 → 즉시 토글 반영
|
||||
|
||||
## 시설종별 파싱 요약
|
||||
|
||||
| 구분 | 파일(03/04/06) | 시설종별 컬럼 | 결과 |
|
||||
|------|----------------|----------------|------|
|
||||
| 교량 | 03)교량 | `idx 1` (헤더 '시설종별' 우선) | grade = "3종"/"기타" 등 |
|
||||
| 터널 | 04)터널 | `idx 5` (헤더 '시설종별' 우선) | grade = "2종" 등 |
|
||||
| 구교 | 06)구교 | 없음 | grade = undefined (항상 표시) |
|
||||
|
||||
## 기본값 / 지속
|
||||
|
||||
- 기본값: **1종 ☑ / 2종 ☑ / 3종 ☐**
|
||||
- localStorage 키 `'ghivideo.settings'` 로 선택 상태 지속 (`zustand persist`)
|
||||
- UI 위치: 플레이어 좌하단 컨트롤(배속 행 아래), `showVideoControls && geoLoaded` 일 때 노출
|
||||
|
||||
## 실데이터 분포 (하행)회덕-대전조차장 데이터, v2.0, 좌표필터 적용 후)
|
||||
|
||||
- **교량 12건**: 시설종별 3종 ×10, 기타 ×2 ("인상" 1건은 깨진 좌표로 이미 필터됨)
|
||||
- **터널 2건**: 시설종별 2종 ×2
|
||||
- **구교 9건**: 시설종별 컬럼 없음 → `grade` undefined(항상 표시)
|
||||
- 기본값(1종☑2종☑3종☐) 적용 시: **3종 교량 10건 숨김**, 기타 교량 2건 · 터널 2건(2종) · 구교 9건 표시
|
||||
|
||||
## 빌드 결과
|
||||
|
||||
- `nvm use 20` → `npm run build -w client` → `tsc && vite build`
|
||||
- 에러 0 (기존 chunk > 500kB 경고만, video.js 특성상 정상)
|
||||
|
||||
## 검수 포인트 / 후속 과제
|
||||
|
||||
- **1종 데이터 부재**: 이 데이터엔 1종 구조물이 없어 1종 체크박스의 실제 토글 효과는 1종이 포함된 다른 데이터로 검증 권장(필터 구조 자체는 완성)
|
||||
- **StationOverlay 미적용**: 영상 위 측점 마커(StationOverlay)는 `structures` 를 사용하지 않으므로 필터 미적용 — 요구 범위(재생바/패널)대로
|
||||
@@ -0,0 +1,742 @@
|
||||
# POI/측점 투영 오차 개선 — 개발자 기술 문서
|
||||
|
||||
**날짜**: 2026-06-22
|
||||
**대상 데이터**: 하행)회덕-대전조차장 (v2.0)
|
||||
**수정 파일**: `client/src/utils/geoProjection.ts`, `client/src/utils/geoData.ts`, `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
이 문서는 GPS POI/측점/중심선 오버레이의 위치 오차를 개선한 작업 기록이다.
|
||||
**개발자가 코드를 직접 이해하고 추가 수정할 수 있도록** 배경·원인·코드·튜닝 방법을 모두 담는다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 투영 파이프라인 개요 (먼저 읽기)
|
||||
|
||||
영상 위에 측점/POI/선로중심선을 그리는 전체 흐름:
|
||||
|
||||
```
|
||||
CSV/SRT 드론 텔레메트리(프레임별 lat/lon/alt/yaw/pitch/roll)
|
||||
│ geoData.ts: parseDroneFrames / parseStations / parsePois / parseStructures
|
||||
▼
|
||||
geoStore (Zustand) ── frames / stations / pois / centerline / origin
|
||||
│
|
||||
▼
|
||||
StationOverlay.tsx
|
||||
├─ smoothFrame(): 드론 자세를 ±N프레임 이동평균으로 평활 (GPS/자세 노이즈 제거)
|
||||
├─ startLabelPrecompute(): 전 프레임 × 전 측점/POI 의 화면좌표를 미리 계산 → labelMap
|
||||
├─ RAF 루프: labelMap 조회 + 프레임 간 보간 + 화면 EMA → canvas 그리기
|
||||
│
|
||||
▼ (좌표 변환 핵심)
|
||||
geoProjection.ts: projectPoint() / toCameraCoords() + pixelFromCamera()
|
||||
1) WGS84(lat/lon) → EPSG:5186 TM(easting/northing, m) [proj4]
|
||||
2) 대상·드론의 상대 ENU 벡터 rel = target - drone (m)
|
||||
3) 회전행렬 R_b2w = Rz(-yaw)·Rx(pitch)·Ry(roll), R_w2c = R_align·R_b2wᵀ
|
||||
4) 카메라좌표 (Xc,Yc,Zc) = R_w2c·rel
|
||||
5) 핀홀 투영 px = 0.5 + (Xc/Zc)·(f/sensorW), py = 0.5 + (Yc/Zc)·(f/sensorH)
|
||||
```
|
||||
|
||||
투영 공식 자체는 Python 원본 `pythonsource/advanced_tuner_v2.py` 와 1:1 일치한다 (검증 완료).
|
||||
**오차는 공식이 아니라 "공식에 들어가는 입력값(표고·평활지연)" 에서 발생**했다.
|
||||
|
||||
데이터 사실(검증됨):
|
||||
- 영상: 3840×2160, **29.97fps(=30000/1001)** → 코드의 `VIDEO_FPS` 와 일치 (프레임 동기화 OK)
|
||||
- focal_len=24.0(고정), pitch=-29.8°(고정), roll=0(고정) → intrinsic/자세 입력 OK
|
||||
- yaw: GPS 진행방향(course-over-ground)과 짐벌 yaw 차이 중앙값 **-0.74°** → yaw 바이어스 없음
|
||||
|
||||
---
|
||||
|
||||
## 1. [수정] 측점 표고를 잘못된 컬럼에서 읽던 문제 (수직 오차 ~31°)
|
||||
|
||||
### 증상
|
||||
측점/중심선이 실제보다 훨씬 아래(또는 화면 밖)에 그려짐. 구버전에서 측점 158K500이
|
||||
`py=1.225`(화면 아래로 이탈)로 계산됨.
|
||||
|
||||
### 원인
|
||||
`building/01)측점.csv` 에는 표고 컬럼이 **두 개**다:
|
||||
- `Z좌표` (col 3): **로컬 좌표계 높이 ≈ 0** (예: 0.0235)
|
||||
- `Z좌표_한국` (col 9): **EPSG:5186 실제 정표고 ≈ 41.5m**
|
||||
|
||||
기존 `parseStations` 가 `Z좌표`(≈0)를 읽어, 측점/중심선이 표고 0에 놓였다.
|
||||
드론 abs_alt(타원체고)=84m 와의 수직차가 -84m(부각 42.7°)로 과대 계산됨.
|
||||
|
||||
### 검증 (proj4 직접 계산, 157K900, frame 0, 수평거리 91.3m)
|
||||
| 사용 표고 | 드론과 수직차 | 부각 |
|
||||
|---|---|---|
|
||||
| Z좌표≈0 (기존) | -84.2m | 42.7° ❌ |
|
||||
| Z좌표_한국 41.5 (정표고) | -42.7m | 25.1° |
|
||||
| 정표고+지오이드 ≈67 (타원체고) | **-17.7m** | **11.0°** ✅ |
|
||||
|
||||
드론 SRT `rel_alt`=16.85m 와 타원체고 사용 시 수직차(-17.7m)가 일치 → 정답 확인.
|
||||
|
||||
### 코드 (`client/src/utils/geoData.ts`, `parseStations`)
|
||||
```diff
|
||||
const iTitle = fi('측점', 0);
|
||||
- const iZ = fi('Z좌표', 3);
|
||||
+ // Z좌표(col 3)는 로컬좌표(≈0). 실제 표고는 Z좌표_한국(EPSG:5186, 정표고)에 있다. 없으면 폴백.
|
||||
+ const iZKorea = fi('Z좌표_한국');
|
||||
+ const iZ = iZKorea >= 0 ? iZKorea : fi('Z좌표', 3);
|
||||
```
|
||||
> 중심선은 `buildCenterlineFromStations()` 가 측점 z를 그대로 쓰므로 이 한 줄로 함께 교정됨.
|
||||
> **다른 데이터에서 컬럼명이 다르면** `fi('Z좌표_한국')` 의 문자열만 바꾸면 된다.
|
||||
|
||||
---
|
||||
|
||||
## 2. [수정] 정표고 → 타원체고 변환 (지오이드 보정) 추가
|
||||
|
||||
### 원인
|
||||
측점의 `Z좌표_한국`(41.5m)은 **정표고(EL, 해수면 기준)** 이고, 드론 `abs_alt`(84m)는
|
||||
**타원체고(GRS80 기준)**. 둘은 datum이 달라 **지오이드고(N)** 만큼 차이난다.
|
||||
대전 지역 N ≈ 25.8m (= 84.244 − 16.853 − 41.59, KNGeoid 값과 일치).
|
||||
|
||||
정표고+25.8 = 타원체고 → 드론 alt와 같은 기준이 됨.
|
||||
|
||||
### 설계
|
||||
지오이드 보정은 **모든 대상(측점/POI/중심선)** 에 공통 적용되므로 투영 레이어
|
||||
(`geoProjection.ts`)에서 대상 고도에 더한다. 튜닝 가능하도록 `CameraParams` 필드로 노출.
|
||||
|
||||
### 코드 (`client/src/utils/geoProjection.ts`)
|
||||
```diff
|
||||
// CameraParams 인터페이스
|
||||
+ geoidOffset: number; // 지오이드고(m): 대상 정표고(EL)→타원체고. 드론 abs_alt와 datum 일치. 대전≈25.8
|
||||
|
||||
// DEFAULT_CAMERA_PARAMS
|
||||
+ geoidOffset: 25.8,
|
||||
|
||||
// projectPoint() 와 buildRelEnu() 두 곳 모두
|
||||
- const stEnu = geoToEnu(targetLat, targetLon, targetAlt, ...);
|
||||
+ const stEnu = geoToEnu(targetLat, targetLon, targetAlt + (params.geoidOffset ?? 0), ...);
|
||||
```
|
||||
> 수학: `rel[2] = (targetZ + geoid) − droneAlt`. geoid를 키우면 대상이 위로(멀리) 이동.
|
||||
> **다른 지역**은 패널 "지오이드" 슬라이더로 조정 (국내 22~30m). 코드 기본값은
|
||||
> `DEFAULT_CAMERA_PARAMS.geoidOffset` 에서 변경.
|
||||
|
||||
---
|
||||
|
||||
## 3. [수정] 화면 EMA 과평활로 인한 라벨 지연 (~1.65초)
|
||||
|
||||
### 증상
|
||||
카메라가 움직일 때 라벨이 뒤늦게(약 1.6초) 따라옴 → 이동 중 좌우/상하 어긋남.
|
||||
|
||||
### 원인
|
||||
`StationOverlay.tsx` 의 화면좌표 EMA(지수이동평균) 계수 `emaAlpha` 기본값이 **0.01**.
|
||||
RAF 60fps에서 시정수 ≈ 16.7ms/0.01 ≈ **1.65초**. 입력은 이미 `smoothFrame(±10fr)`로
|
||||
평활되는데 화면 EMA까지 과하게 걸려 이중 평활 → 큰 지연.
|
||||
|
||||
### 코드 (`client/src/components/overlay/StationOverlay.tsx`)
|
||||
```diff
|
||||
- const [emaAlpha, setEmaAlpha] = useState(0.01);
|
||||
- const emaAlphaRef = useRef(0.01);
|
||||
+ // 0.01은 과평활(지연~1.65s). 입력이 smoothFrame으로 이미 평활되므로 화면 EMA는 가볍게(0.4≈지연25ms).
|
||||
+ const [emaAlpha, setEmaAlpha] = useState(0.4);
|
||||
+ const emaAlphaRef = useRef(0.4);
|
||||
```
|
||||
> EMA 동작: `disp += (target − disp) · α`. α↑ = 반응 빠름/덜 부드러움, α↓ = 느림/부드러움.
|
||||
> 떨림이 심하면 패널 "EMA α" 를 0.2~0.3으로, 지연이 거슬리면 0.5~0.7로.
|
||||
|
||||
---
|
||||
|
||||
## 4. [수정] POI가 "실제보다 가까이(아래)" 뜨는 문제 — POI 전용 표고 보정
|
||||
|
||||
### 증상
|
||||
드론 시점에서 라벨(POI)이 실제 위치보다 가까이/아래에 뜸. 특히 건물(회덕화물역 등).
|
||||
|
||||
### 원인 (이건 "버그"가 아니라 데이터 한계)
|
||||
화면의 POI들(회덕화물역·회덕역주차장·국도17호선 등)은 웹소스 `_POI.csv` 출신으로
|
||||
**표고 컬럼이 아예 없다**. 또 오버레이는 POI 고도를 `nearestCL().z`(가장 가까운 선로점 표고)로
|
||||
덮어쓴다 → **모든 POI가 "선로 지면 높이"로 가정**됨.
|
||||
|
||||
계산(frame 0, 드론 지면 위 17m): 회덕화물역 거리 27m, 수직갭 17m → 부각 32°, py=0.55.
|
||||
이 값은 **지면 점 기준으로는 기하학적으로 정확**하다. 그러나
|
||||
1. 건물은 높이가 있어 지면 마커가 건물 몸체보다 아래에 보이고,
|
||||
2. 주변 지반이 선로보다 높으면(예: +4~5m) 그만큼 더 아래/가깝게 보임.
|
||||
|
||||
per-POI 실제 표고(DEM/건물높이)가 없어 자동 보정은 불가. → **튜닝 핸들**을 제공.
|
||||
|
||||
### 설계
|
||||
- 선로 중심선/측점은 표고가 정확하므로 **건드리면 안 됨**.
|
||||
- 따라서 지오이드(전체 적용)와 별개로, **POI에만** 더하는 `poiZOffset` 을 신설.
|
||||
- 적용 위치: 투영 레이어가 아니라 `StationOverlay` 의 **POI 루프에서만** poiZ에 가산.
|
||||
|
||||
### 코드
|
||||
`client/src/utils/geoProjection.ts` (필드/기본값만 추가, 투영식엔 미사용):
|
||||
```diff
|
||||
+ poiZOffset: number; // POI 전용 표고 보정(m): POI/구조물 마커만 들어올림. 측점·중심선 미적용.
|
||||
...
|
||||
+ poiZOffset: 0, // 기본 0 (건물높이/지형차에 맞춰 패널에서 조정)
|
||||
```
|
||||
`client/src/components/overlay/StationOverlay.tsx` (POI 루프):
|
||||
```diff
|
||||
for (const poi of allPoi) {
|
||||
- const poiZ = nearestCL(poi.lat, poi.lon)?.z ?? poi.z;
|
||||
+ // 측점/중심선과 달리 POI만 poiZOffset 적용 (건물 높이/지형차 보정)
|
||||
+ const poiZ = (nearestCL(poi.lat, poi.lon)?.z ?? poi.z) + (currentParams.poiZOffset ?? 0);
|
||||
const cc = toCameraCoords(drone, poi.lat, poi.lon, poiZ, currentParams, worldOrigin);
|
||||
```
|
||||
패널 슬라이더 추가(지오이드 아래):
|
||||
```
|
||||
<ParamRow label="POI 표고" value={params.poiZOffset} min={-10} max={40} step={0.5} unit="m" .../>
|
||||
```
|
||||
> **사용법**: POI가 건물보다 아래/가깝게 뜨면 "POI 표고" 를 +방향으로 올린다(회덕 부근 +4~5m 권장).
|
||||
> 기본 0은 기존 동작 유지(선로지면). 도로처럼 실제 지면에 있는 POI는 0이 맞을 수 있어,
|
||||
> 데이터 성격에 따라 절충해 맞춘다.
|
||||
>
|
||||
> **추가 개선 여지(미구현)**: 구조물/지장물 CSV가 실제 표고를 제공하면 per-POI z를 쓰도록
|
||||
> `nearestCL().z` 대신 해당 z를 사용. 현재 building CSV의 `Z좌표` 는 로컬값(≈0~16)이라 부적합.
|
||||
|
||||
---
|
||||
|
||||
## 5. [수정] 먼 POI가 "선로 안"에 어긋나 보이는 문제 — POI 거리 제한
|
||||
|
||||
### 증상
|
||||
88자원(고물상) 등 일부 POI가 선로 위/안에 표시됨. "안 맞는 POI가 좀 있다."
|
||||
|
||||
### 원인 (핵심)
|
||||
**멀리 있는 POI는 부각이 0°에 수렴해 화면 "지평선(소실점)"에 몰린다.** 그 소실점은
|
||||
선로가 사라지는 지점과 같다 → 먼 POI들이 전부 선로 위에 겹쳐 찍힌다.
|
||||
|
||||
측점 160K900 프레임에서 화면에 잡히는 POI 9개 중 8개가 **240~312m 거리**이고 전부
|
||||
`py≈0.0`(지평선), px 0.2~0.6에 몰려 있었다. 88자원은 노선기준 ~2.3km로 더 멀다.
|
||||
가까운 POI(태경자동차 121m)는 정상 위치. 즉 **"안 맞는 POI = 먼 POI"** 다.
|
||||
|
||||
추가로 이 POI들은 표고가 없어 선로 지면 높이에 놓여 정확히 소실선 위에 앉는다(§4 참조).
|
||||
|
||||
### 설계
|
||||
거리 컷이 가장 단순·확실. 카메라좌표의 슬랜트 거리 `√(Xc²+Yc²+Zc²)` 가 임계값을
|
||||
넘으면 그 POI를 사전계산 단계에서 제외(라벨 자체를 안 만듦). 측점/중심선은 미적용
|
||||
(측점은 선로 위라 소실점으로 가도 자연스러움).
|
||||
|
||||
### 코드 (`client/src/components/overlay/StationOverlay.tsx`)
|
||||
상태 추가:
|
||||
```diff
|
||||
+ const [maxPoiDist, setMaxPoiDist] = useState(200); // POI 표시 거리 상한(m)
|
||||
+ const maxPoiDistRef = useRef(200);
|
||||
+ useEffect(() => { maxPoiDistRef.current = maxPoiDist; }, [maxPoiDist]);
|
||||
```
|
||||
precompute 시그니처에 인자 추가 + POI 루프에서 컷:
|
||||
```diff
|
||||
- const startLabelPrecompute = useCallback((currentParams, currentSmoothHalf) => {
|
||||
+ const startLabelPrecompute = useCallback((currentParams, currentSmoothHalf, currentMaxPoiDist) => {
|
||||
...
|
||||
+ const MAX_POI_DIST = currentMaxPoiDist;
|
||||
for (const poi of allPoi) {
|
||||
const cc = toCameraCoords(...);
|
||||
if (cc.Zc < CLIP_Z) continue;
|
||||
+ const slant = Math.sqrt(cc.Xc*cc.Xc + cc.Yc*cc.Yc + cc.Zc*cc.Zc);
|
||||
+ if (slant > MAX_POI_DIST) continue; // 먼 POI 제외
|
||||
```
|
||||
두 호출부와 effect 의존성에 `maxPoiDist` 추가, 패널에 슬라이더 추가:
|
||||
```
|
||||
<ParamRow label="POI 거리" value={maxPoiDist} min={30} max={1000} step={10} unit="m" .../>
|
||||
```
|
||||
> **튜닝**: 기본 200m. 너무 휑하면 ↑(300~500), 먼 라벨이 거슬리면 ↓(100~150).
|
||||
> 슬랜트 거리(3D)라 드론 높이(~35m) 포함이지만 100m+에선 수평거리와 거의 같다.
|
||||
> **측점에도 같은 컷을 주고 싶으면** 측점 루프(`for (const st of allSt)`)에 동일 패턴 추가.
|
||||
|
||||
---
|
||||
|
||||
## 6. [결정] 선로변 POI 좌표 오차는 코드로 보정 불가 (데이터 한계)
|
||||
|
||||
### 분석
|
||||
거리 필터 적용 후에도 88자원 라벨이 선로 위에 보인 케이스 분석:
|
||||
- 스크린샷 프레임은 측점 160K700 부근, 이때 88자원은 **90m 거리**(먼 POI 아님 → 거리필터 통과 정상).
|
||||
- 88자원 좌표(36.381059,127.420448)의 **선로 중심선까지 수직거리 = 13.5m** (nearest 측점 160K800, 31m).
|
||||
- 즉 좌표 자체가 선로 바로 옆 → 90m 거리에서 13.5m 측면오프셋은 화면상 선로와 겹쳐 보임.
|
||||
|
||||
### 결론
|
||||
- POI **수평 위치(px)** 는 좌표의 bearing으로 정확히 결정됨(투영 정확). 투영 버그 아님.
|
||||
- KAKAO/VWORLD POI는 주소 기반 지오코딩 → ±수십m 오차. 철도 인접 업체(고물상 등)는
|
||||
좌표가 선로 옆에 찍힘. **올바른 좌표 없이는 코드로 못 고침.**
|
||||
|
||||
### 방침 (사용자 결정)
|
||||
- "선로 옆 실제 POI일 수 있으니 숨기지 말고, 줄일 수 있는 오차만 최소화."
|
||||
- → 근접 POI 숨김/거리 하향 **미적용**. 거리 필터 200m 유지.
|
||||
- → 줄일 수 있는 체계오차(수직 datum/지오이드/지연/소실점몰림/FOV)는 이미 적용·튜닝 가능.
|
||||
- → 개별 POI 좌표를 바로잡으려면 별도 보정좌표 파일이 필요(향후 과제).
|
||||
|
||||
---
|
||||
|
||||
## 7. [분석] KMZ `절대고도`는 상수 — POI 표고 개선에 사용 불가
|
||||
|
||||
### 배경
|
||||
`2구간_14.회덕-대전조차장_하행.kmz`(=doc.kml)에 각 POI의 description 표가 있고
|
||||
`절대고도` 필드가 있음. POI별 실측 지반고도면 §4의 poiZOffset 추정을 대체할 수 있어 검토.
|
||||
|
||||
### 분석 (python으로 doc.kml 전수 파싱)
|
||||
- Placemark 100개, 절대고도 보유 83개. **전부 67.391m (고유값 1개), Z좌표=0.**
|
||||
- 67.391 = 노선 출발점 지면 타원체고 (= 드론 frame0 abs_alt 84.244 − rel_alt 16.853).
|
||||
- 즉 **출발점 고도 하나를 모든 POI에 일괄 기입**한 값. per-POI 실측 아님.
|
||||
|
||||
### 왜 쓰면 안 되나
|
||||
현재 방식(POI = 가장 가까운 선로점 표고 + 지오이드)은 선로 상승(정표고 41.6→57.5m)을
|
||||
따라 POI 고도가 67→83m로 변함 → 지형 상승 반영. KMZ 상수(67.391)는 후반 POI를 ~16m
|
||||
낮게 만들어 **현재 방식보다 부정확**. (드론 abs_alt가 84→120으로 상승하는 것과도 부합)
|
||||
|
||||
### 결론
|
||||
- KMZ `절대고도`는 datum 검증용으로만 유효(지오이드 25.8 재확인). 표고 개선엔 미사용.
|
||||
- 진짜 개선엔 **POI별 실측 Z** 또는 **DEM** 필요. KMZ 좌표는 _POI.csv와 동일이라
|
||||
88자원 등 수평 오차도 변화 없음.
|
||||
- 코드 변경 없음.
|
||||
|
||||
---
|
||||
|
||||
## 8. [전수조사] 데이터 폴더 전체 탐색 — 실측 고도원(DEM) 부재 확인
|
||||
|
||||
### 조사 대상 (데이터 폴더 전 파일)
|
||||
`*.MP4 / *.srt / *.csv(드론) / *_POI.csv / building/01~06 / *.kmz / *.qgz / *_구조물추출_Z.xlsx`
|
||||
|
||||
### 고도(Z) 데이터 전수 비교
|
||||
| 소스 | 고도 필드 | 값 | 판정 |
|
||||
|---|---|---|---|
|
||||
| building/01)측점.csv | `Z좌표_한국` | 정표고 41.6→57.5m (**가변, 실측**) | ✅ 유일한 실제 가변 고도. **이미 사용** |
|
||||
| building/01)측점.csv | `Z좌표` | 로컬 ≈0 | 무효 |
|
||||
| *_POI.csv / KMZ / xlsx(교량·터널·지장물) | `절대고도` | **상수 67.391** | 출발점 고도 일괄, 무효 |
|
||||
| xlsx 구교 | `z` | 67.391 + 로컬오프셋(비고 '기존값') | 무효 |
|
||||
| xlsx(교량·터널) | `Z좌표` | 로컬 2~22 | 구조물 상대높이, 지형 아님 |
|
||||
| *.srt | `rel_alt` | 이륙점 기준 | 지형프로파일 아님(상수지형) |
|
||||
| *.qgz(QGIS) | 래스터/DEM | **없음** (벡터 레이어만 참조) | 없음 |
|
||||
|
||||
### .qgz 분석
|
||||
참조 GeoPackage `14회덕-대전조차장_layers.gpkg`(폴더에 없음)의 레이어:
|
||||
`드론경로_버퍼_150m, 드론경로_라인/포인트, 출입문, 구조물_교량/터널, 지장물`.
|
||||
→ **DEM 없음.** 또한 **POI 선정 기준이 '드론경로 150m 버퍼'** 임을 확인.
|
||||
|
||||
### 결론
|
||||
- POI별 실측 지형고도(DEM)는 데이터셋에 **존재하지 않음**.
|
||||
- 실제 가변 고도는 측점 `Z좌표_한국` 뿐이며 **현재 POI 고도 추정(최근접 측점 표고+지오이드)에 이미 사용 중** → 수직은 데이터 한계상 최선.
|
||||
- 좌표는 모든 파일이 동일(지오코딩) → 88자원류 수평오차 개선 불가.
|
||||
|
||||
### 적용 변경
|
||||
데이터 설계(150m 버퍼)에 맞춰 POI 거리 필터 기본값 **200 → 150m**.
|
||||
```diff
|
||||
- const [maxPoiDist, setMaxPoiDist] = useState(200);
|
||||
- const maxPoiDistRef = useRef(200);
|
||||
+ const [maxPoiDist, setMaxPoiDist] = useState(150); // '드론경로_버퍼_150m'와 정렬
|
||||
+ const maxPoiDistRef = useRef(150);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. [검증] 구글어스 '지면고도' vs 현재 방식 — 0.2m 일치 (변경 불필요)
|
||||
|
||||
### 배경
|
||||
사용자가 KMZ를 구글어스에 import → POI 클릭 시 `지면 고도` 값이 표시됨을 확인
|
||||
(예: 신대천교(복) = 41.4566m). "파일에 per-POI 고도가 있다"는 취지.
|
||||
|
||||
### 사실관계
|
||||
- **KMZ 파일**: `절대고도=67.391`(상수)만 저장. (§7과 동일, 변함없음)
|
||||
- **구글어스**: 클릭 시 자체 DEM에서 `지면고도`(정표고)를 **실시간 계산**해 표시.
|
||||
파일 값이 아니라 구글 지형 데이터.
|
||||
- **현재 앱**: 측량 측점 `Z좌표_한국`(정표고)로 POI 지면고도 추정.
|
||||
|
||||
### 수치 검증
|
||||
| POI | 구글어스 지면고도 | 현재(최근접 측점 정표고) | 차이 |
|
||||
|---|---|---|---|
|
||||
| 신대천교(복) | 41.4566m | 41.26m (158K400) | **0.2m** |
|
||||
|
||||
→ 90m 거리에서 0.2m = 0.13°, ~6px(2160p), 무시 가능. 현재 방식이 POI별로 변하는
|
||||
지면고도(40.8~80.5m, 지형상승 반영)를 이미 정확히 산출 → **구글어스 DEM과 동급**.
|
||||
|
||||
### 결론
|
||||
- 수직(고도)은 이미 측량 측점 기반으로 구글어스와 0.2m 일치 → **개선 여지 없음, 코드 변경 없음**.
|
||||
- 공개 DEM(SRTM 30m, ±수m)을 끌어와도 측량 측점보다 정밀하지 않아 regression 위험.
|
||||
- 잔여 88자원류는 **수평 좌표(지오코딩) 오차** → 고도 데이터로 해결 불가(올바른 좌표 필요).
|
||||
|
||||
---
|
||||
|
||||
## 10. [기능] POI 마우스 드래그 위치 보정 + 내보내기/가져오기
|
||||
|
||||
### 목적
|
||||
지오코딩 오차로 어긋난 POI(88자원 등)를 **마우스로 끌어** 올바른 위치에 놓고, 그
|
||||
보정을 파일로 저장 → 다음 로드 시 자동 반영. (수평+수직 동시 보정)
|
||||
|
||||
### 동작 원리
|
||||
1. **편집 모드** ON → 오버레이 canvas가 포인터 입력을 받음(`pointer-events`).
|
||||
2. POI 마커 **드래그** → 놓은 화면 픽셀을 **역투영**해 월드 좌표(lat/lon/z) 복원.
|
||||
- 2D→3D 깊이 모호성은 **슬랜트 거리 유지**로 해소(드래그 시작 시 거리 고정).
|
||||
- 따라서 한 번의 드래그로 **수평·수직 동시** 보정.
|
||||
3. 결과를 `title` 키로 store에 저장(`setPoiOverride`) → `pois` 재적용 → labelMap 재계산.
|
||||
4. **내보내기**: `<base>_poi_overrides.json` 다운로드. **가져오기**: 파일 선택 적용.
|
||||
5. 그 파일을 **데이터 폴더에 두면** 다음 폴더 로드 시 `parsePoiOverrides`가 자동 적용.
|
||||
|
||||
### 역투영 수학 (검증됨: Zc>0 왕복오차 ≈0)
|
||||
`geoProjection.ts worldFromPixel()`:
|
||||
```
|
||||
dir_cam ∝ ((px-0.5-cx0)·sW/f, (py-0.5-cy0)·sH/f, 1) // 픽셀→카메라 방향
|
||||
cam = dir_cam/|dir_cam| · range // 거리 유지
|
||||
rel = R_w2cᵀ · cam // 회전 역
|
||||
E = droneE + offX + rel.x, N = droneN + offY + rel.y
|
||||
z = droneAlt + offZ + rel.up − geoid // 정표고로 복원(투영이 geoid 재가산)
|
||||
(lat,lon) = TM⁻¹(E,N)
|
||||
```
|
||||
> 화면에 보이는 POI(Zc>0)만 드래그 가능 → 역투영 정확. 카메라 뒤(Zc<0)는 해당 없음.
|
||||
|
||||
### 보정 POI의 고도 처리
|
||||
보정된 POI는 precompute에서 **자신의 z(정표고)를 그대로 사용**(`overridesRef`로 판별).
|
||||
미보정 POI는 기존대로 `nearestCL.z + poiZOffset`. (둘 다 투영에서 geoid 가산)
|
||||
|
||||
### 수정 파일
|
||||
- `types/geo.ts`: `PoiOverride`, `PoiOverrideMap`, `FolderGeoData.poiOverrides`.
|
||||
- `utils/geoProjection.ts`: `worldFromPixel()` (역투영).
|
||||
- `utils/geoData.ts`: `parsePoiOverrides()`(폴더 JSON 로드), `applyPoiOverrides()`(title 매칭 교체), `loadFolderGeoData`에 연결.
|
||||
- `store/geoStore.ts`: `basePois`/`poiOverrides` 상태, `setPoiOverride`/`clearPoiOverride`/`setPoiOverrides`, 로드시 적용.
|
||||
- `components/overlay/StationOverlay.tsx`: 편집모드 토글, canvas 포인터 핸들러(hit-test→드래그→drop 역투영), 드래그 피드백 렌더, 패널 UI(편집/내보내기/가져오기), 보정 POI z 처리, override 변경 시 재계산.
|
||||
|
||||
### 사용법 (사용자)
|
||||
1. 카메라 파라미터 패널 → **"✎ 편집 모드"** 클릭.
|
||||
2. POI를 끌어 올바른 위치에 놓기(영상 일시정지 권장 — 자세가 안정적).
|
||||
3. **"내보내기"** → `<base>_poi_overrides.json` 저장.
|
||||
4. 그 파일을 **데이터 폴더**(MP4 옆)에 복사 → 다음 폴더 로드 시 자동 적용.
|
||||
(또는 **"가져오기"** 로 즉시 적용)
|
||||
|
||||
### 한계/참고
|
||||
- 식별자가 `title` 뿐 → 같은 title POI가 여럿이면 함께 보정됨(현 데이터엔 거의 없음).
|
||||
- drop 후 라벨 이동은 재계산(≈500ms debounce) 뒤 반영.
|
||||
- 측점/중심선/구조물은 편집 대상 아님(POI만).
|
||||
|
||||
---
|
||||
|
||||
## 11. [개선] 라벨 이동 부드럽게 — 부드러운 시간원 + 연속 프레임 보간
|
||||
|
||||
### 증상
|
||||
재생 중 라벨이 매끄럽지 않고 "멈췄다 점프" 하듯 보임.
|
||||
|
||||
### 원인 (2가지)
|
||||
1. StationOverlay에 **거친 `currentTime`** 전달: playerStore.currentTime은 Video.js
|
||||
`timeupdate` 이벤트(약 4~15Hz)로 갱신됨([useVideoPlayer.ts:43]). 반면 VideoPlayer엔
|
||||
이미 60fps 단조보간 시간 `smoothTimeRef`가 있는데 라벨엔 안 쓰고 있었음.
|
||||
2. 보간 `frac`이 **1프레임으로 클램프**(`Math.min(0.999, …)`): currentTime 갱신 간격이
|
||||
1프레임을 넘으면 라벨이 frame+1에서 멈췄다가 다음 갱신 때 여러 프레임 점프.
|
||||
|
||||
### 수정
|
||||
- VideoPlayer: `<StationOverlay timeRef={smoothTimeRef} …>` 추가 (StationBar와 동일 패턴).
|
||||
- StationOverlay RAF: 시간원을 timeRef로 바꾸고 **연속 프레임 인덱스**로 보간:
|
||||
```diff
|
||||
- const frameNum = currentFrameNumRef.current;
|
||||
- const estTime = currentTimeSecRef.current + (now - timeUpdateWallRef.current)/1000;
|
||||
- const frac = Math.min(0.999, estTime*VIDEO_FPS - frameNum); // 1프레임 클램프(멈춤)
|
||||
- const labelsA = get(frameNum), labelsB = get(frameNum+1);
|
||||
+ const estTime = timeRef ? timeRef.current : (prop 기반 폴백);
|
||||
+ const estFrame = estTime * VIDEO_FPS;
|
||||
+ let baseFrame = Math.floor(estFrame), frac = estFrame - baseFrame; // 클램프 없음
|
||||
+ let labelsA = get(baseFrame), labelsB = get(baseFrame+1);
|
||||
+ if (!labelsA) { baseFrame = currentFrameNumRef.current; frac = 0; … } // 갭 폴백
|
||||
```
|
||||
- Props에 `timeRef?: React.MutableRefObject<number>` 추가.
|
||||
|
||||
### 효과
|
||||
- timeRef는 일시정지·시크·배속까지 보정된 60fps 단조시간 → 라벨이 매 프레임 연속 이동.
|
||||
- 1프레임 클램프 제거로 갱신 간격이 길어도 라벨이 끊기지 않고 진행.
|
||||
- 기존 화면 EMA(α=0.4)는 유지 → 프레임 경계 속도변화까지 완만.
|
||||
- 일시정지 시 timeRef 고정 → 라벨 표류 없음(이전 폴백식은 벽시계로 표류 위험).
|
||||
|
||||
### 남은 것 (선택)
|
||||
- 빨간 **중심선**은 아직 `currentTime`(coarse) 기반 + 비보간이라 라벨만큼 매끄럽지 않음.
|
||||
같은 방식(보간 드론 자세로 RAF에서 투영)으로 부드럽게 가능 — 필요 시 후속 작업.
|
||||
|
||||
---
|
||||
|
||||
## 12. [버그픽스] 라벨이 옆으로 튀었다 복귀 — 인덱스 짝짓기 + 이상치 거부
|
||||
|
||||
### 증상
|
||||
재생 중 일부 라벨이 한 프레임 옆으로 확 튀었다가 다음 프레임에 제자리로 돌아옴.
|
||||
|
||||
### 근본 원인 (인덱스 짝짓기 오류)
|
||||
precompute는 프레임별로 `stationLabels`/`poiMarkers`에 **클리핑/화면밖 필터를 통과한 것만
|
||||
push** 한다. RAF 보간은 `labelsA[i]` ↔ `labelsB[i]` 를 **배열 인덱스로 짝지어** 보간했는데,
|
||||
A↔B 사이에 라벨 하나가 필터에 들고나면 인덱스가 밀려 **서로 다른 라벨끼리 보간** →
|
||||
좌표가 확 튀고, 다음 프레임에 정렬되며 복귀.
|
||||
|
||||
### 수정 (StationOverlay.tsx)
|
||||
1. **title로 짝짓기** (근본 해결): 프레임 B 라벨을 `Map<title, entry>` 로 인덱싱 후
|
||||
`stA.title` 로 조회. 항상 같은 라벨끼리 보간.
|
||||
```diff
|
||||
- const stB = labelsB?.stationLabels[i]; // 인덱스 짝짓기(필터 변하면 어긋남)
|
||||
+ const stB_byTitle = new Map((labelsB?.stationLabels ?? []).map(s => [s.title, s]));
|
||||
+ const stB = stB_byTitle.get(stA.title); // title 짝짓기
|
||||
```
|
||||
2. **이상치 거부 필터** (요청 + 잔여 GPS/투영 스파이크 대비): 표시좌표 갱신 시 이전 위치
|
||||
대비 점프가 임계(`REJECT_DIST=0.12`, 정규화) 초과면 **갱신 무시(이전 위치 유지)**.
|
||||
시크/재등장에 갇히지 않도록 연속 `MAX_REJECT_FRAMES=8` 후 수용.
|
||||
```ts
|
||||
function smoothStep(prev, tx, ty, alpha) {
|
||||
if (!prev) return { x:tx, y:ty, rej:0 };
|
||||
const d = Math.hypot(tx-prev.x, ty-prev.y);
|
||||
if (d > REJECT_DIST && prev.rej < MAX_REJECT_FRAMES)
|
||||
return { x:prev.x, y:prev.y, rej:prev.rej+1 }; // 이상치 무시
|
||||
return { x:prev.x+(tx-prev.x)*alpha, y:prev.y+(ty-prev.y)*alpha, rej:0 };
|
||||
}
|
||||
```
|
||||
3. **사라진 라벨 상태 정리**: 이번 프레임에 안 그린 라벨은 displayed 맵에서 삭제 →
|
||||
재등장 시 옛 좌표가 남아 거부로 늦게 뜨는 것 방지(새 위치에서 시작).
|
||||
|
||||
### 튜닝
|
||||
- 정상 팬 이동은 60fps에서 프레임당 ≪0.12 라 영향 없음. 스파이크(0.2~0.5)만 걸러짐.
|
||||
- 너무 빠른 팬에서 잠깐 머뭇하면 `REJECT_DIST` ↑ 또는 `MAX_REJECT_FRAMES` ↓.
|
||||
|
||||
---
|
||||
|
||||
## 13. [기능] 구조물(교량/터널/구교)도 영상 오버레이에 표출
|
||||
|
||||
### 배경
|
||||
"신대천교(복)이 왜 안 보이나?" → 신대천교(복)은 `03)교량.csv`(구조물)에만 있고
|
||||
`_POI.csv`/지장물엔 없음. 오버레이는 측점+POI만 그려 구조물은 미표시였음
|
||||
(구조물은 RoutePanel 미니맵 전용이었음).
|
||||
|
||||
### 수정 (StationOverlay.tsx)
|
||||
- `useGeoStore(s => s.structures)` 구독, 구조물을 GeoPoint 형태로 변환해
|
||||
`allStructuresRef` 에 보관(이모지: category 교량🌉/터널🚇/구교🌉).
|
||||
```ts
|
||||
allStructuresRef.current = storeStructures
|
||||
.filter(s => typeof s.lat==='number' && typeof s.lon==='number')
|
||||
.map(s => ({ title:s.name, category:s.category ?? (s.type==='tunnel'?'터널':'교량'),
|
||||
lat:s.lat, lon:s.lon, z:0, type:'poi' }));
|
||||
```
|
||||
- precompute에서 `allPoi = allPoisRef.current.concat(allStructuresRef.current)` →
|
||||
구조물도 POI와 동일 파이프라인(평면거리필터·title보간·EMA·드래그보정)으로 표시.
|
||||
- 재계산 트리거에 `storeStructures` 의존 추가.
|
||||
|
||||
### 효과/참고
|
||||
- 드론 평면거리 60m 이내의 교량/터널/구교가 라벨로 표출됨.
|
||||
- 측점선 토글(`visible`)·POI 거리필터·POI 드래그 보정이 동일 적용.
|
||||
- lat/lon 없는(측점기반) 구조물은 제외.
|
||||
|
||||
---
|
||||
|
||||
## 14. [기능] POI 유효거리 앞/옆 분리(비등방 거리필터)
|
||||
|
||||
### 요구
|
||||
"앞쪽 유효거리와 옆쪽 유효거리를 달리해서 POI 표출."
|
||||
|
||||
### 구현
|
||||
평면거리를 진행방향(yaw) 기준 **앞쪽(fwd)·옆쪽(side)** 으로 분해해 각각 임계 적용.
|
||||
- `geoProjection.ts toCameraCoords`: relEnu와 yaw로 성분 계산
|
||||
```ts
|
||||
const yaw = toRad(camera.yaw + params.yawOffset);
|
||||
cc.fwd = relEnu[0]*sin(yaw) + relEnu[1]*cos(yaw); // +앞
|
||||
cc.side = relEnu[0]*cos(yaw) - relEnu[1]*sin(yaw); // 좌우
|
||||
```
|
||||
- `StationOverlay`: `maxPoiDist` → `maxPoiFront`/`maxPoiSide`(상태·ref·슬라이더 2개).
|
||||
필터: `if (cc.fwd > MAX_FRONT || |cc.side| > MAX_SIDE) continue;`
|
||||
- 기본값 앞 60m / 옆 40m. 패널 "POI 앞쪽"(10~1000) · "POI 옆쪽"(5~500) 슬라이더.
|
||||
|
||||
### 효과
|
||||
- 진행방향으로는 멀리(앞 큰 값), 좌우 클러터는 좁게(옆 작은 값) 식으로 독립 조절.
|
||||
- 측점/구조물도 동일 POI 파이프라인 → 함께 적용.
|
||||
|
||||
---
|
||||
|
||||
## 15. [변경] 구조물 표출 위치 분리 — 교량·터널=스테이션바 / 구교=영상
|
||||
|
||||
### 요구
|
||||
`building/`의 **교량(03)·터널(04)** 은 하단 **스테이션바**에, **나머지(구교 06 + 지장물 02 +
|
||||
_POI)** 는 **영상 화면**(POI 거리설정 적용)에 표출.
|
||||
|
||||
### 사실관계
|
||||
- 스테이션바(StationBar)는 이미 `geoStore.structures`를 `s.type`으로 판정해 교량/터널을
|
||||
표출 중. 단 **구교(06)는 type='bridge'라 교량에 섞여 바에 같이 표출**되고 있었음.
|
||||
- §13에서 임시로 교량/터널/구교를 모두 영상에 넣었음 → 본 변경으로 정리.
|
||||
|
||||
### 수정
|
||||
- `StationOverlay.tsx`: 영상 구조물은 **category==='구교'만** 포함(교량/터널 제외).
|
||||
```diff
|
||||
- .filter(s => typeof s.lat==='number' && typeof s.lon==='number') // 모든 구조물
|
||||
+ .filter(s => s.category==='구교' && typeof s.lat==='number' && typeof s.lon==='number')
|
||||
```
|
||||
- `stationbar/StationBar.tsx`: 구조물 루프에서 **구교 제외**(교량/터널만 바에 유지).
|
||||
```diff
|
||||
for (const s of storeStructures) {
|
||||
+ if (s.category === '구교') continue; // 구교는 영상으로
|
||||
const cat = s.type === 'tunnel' ? '터널' : s.type === 'bridge' ? '교량' : '역사';
|
||||
```
|
||||
|
||||
### 결과 라우팅
|
||||
| 데이터 | 표출 위치 |
|
||||
|---|---|
|
||||
| 03)교량, 04)터널 | 하단 스테이션바 |
|
||||
| 06)구교 | 영상 오버레이(거리필터) |
|
||||
| 02)지장물, _POI.csv | 영상 오버레이(거리필터) |
|
||||
| 01)측점 | 영상(측점 라벨) + 바(측점값) |
|
||||
|
||||
> 구분 키는 `RouteStructure.category`('교량'/'터널'/'구교'). type만으론 구교/교량이
|
||||
> 둘 다 'bridge'라 분리 불가 → category로 라우팅.
|
||||
|
||||
---
|
||||
|
||||
## 16. [기능] 역사(02)지장물_역사.csv) → 스테이션바 표출
|
||||
|
||||
### 요구
|
||||
새 파일 `building/02)지장물_역사.csv`(역=철도역)는 하단 스테이션바에만 표출,
|
||||
그 외(지장물·구교·일반 POI)는 영상에만(거리필터).
|
||||
|
||||
### 분석
|
||||
- 파일 컬럼은 지장물과 동일(명칭,…,lat,lon), EUC-KR. 내용=역(대전조차장역·회덕화물역).
|
||||
- 스테이션바 Timeline은 이미 `category==='역사'`를 **원형 아이콘**으로 렌더(Timeline.tsx:181).
|
||||
- 함정1: 파일명 `02)지장물_역사.csv`가 `02)지장물`을 포함 → 기존 지장물 파서가 오집 가능.
|
||||
- 함정2: 회덕화물역은 `_POI.csv`에도 `철도역`으로 존재 → 바·영상 중복 노출.
|
||||
|
||||
### 수정 (geoData.ts / StationOverlay.tsx)
|
||||
- `parseHistoricStations()`: `지장물_역사` 파일 → `RouteStructure{type:'station', category:'역사', lat, lon}`.
|
||||
`loadFolderGeoData`에서 `structures`에 concat → 바가 역사로 표출(영상은 '구교'만 표시해 제외됨).
|
||||
- `parsePois` 지장물 매칭에서 **'역사' 포함 파일 제외**(오집 방지).
|
||||
- 영상 POI 목록에서 **category '철도역'/'역사' 제외** → 역사 중복(영상) 제거, 바에만 표출.
|
||||
|
||||
### 결과 라우팅(최종)
|
||||
| 데이터 | 표출 |
|
||||
|---|---|
|
||||
| 03)교량, 04)터널 | 스테이션바 |
|
||||
| **02)지장물_역사 (역사)** | **스테이션바(원형 아이콘)** |
|
||||
| 06)구교 | 영상(거리필터) |
|
||||
| 02)지장물, _POI(철도역 제외) | 영상(거리필터) |
|
||||
| _POI 철도역 | 제외(역사로 바에 표출) |
|
||||
|
||||
---
|
||||
|
||||
## 17. [진단+영속화] 멀리 있는 POI가 어긋나 보이고 가까이 가면 맞는 현상
|
||||
|
||||
### 증상
|
||||
멀리서 본 POI가 실제 지도 위치와 차이가 크고, 드론이 접근(천정점)하면 정확해짐.
|
||||
재생 중 POI가 "뒤로 밀려나며" 수렴.
|
||||
|
||||
### 진단 (기하학)
|
||||
- **천정점에서 정확 + 멀수록 오차 증가**는 **표고(z) 오차가 아님**.
|
||||
표고 오차의 화면 영향 Δθ ≈ Δz·D/(D²+H²) 는 D≈H(드론높이~35m)에서 최대이고
|
||||
멀수록(D≫H) 감소. 가까이(~30m)서 맞으면 표고는 정상 → DEM(50m)은 오히려 틀림(z≈42 정상).
|
||||
- **천정점=0, 지평선으로 증가**는 **카메라 각도(yaw) 미세편차**의 특징(POI가 yaw축인
|
||||
천정 방향에 있을 때 0, 지평선에서 최대). 가까이선 고부각이라 작고, 멀리선 크게 벌어짐.
|
||||
- 즉 **짐벌 각도 보정 오프셋(Yaw±/Pitch±)이 0** 이라 잔여 편차 미보정 상태(Python 튜너가
|
||||
찾던 값). 1회 보정하면 전 거리에서 정렬됨.
|
||||
|
||||
### 수정 — 보정값 영속화 (StationOverlay.tsx)
|
||||
오프셋이 새로고침마다 0으로 초기화되던 문제 → **데이터셋(baseName)별 localStorage 저장·복원**:
|
||||
```ts
|
||||
// 로드: ghivideo:calib:<baseName> → params + 표시설정 복원 (baseName당 1회)
|
||||
// 저장: params/maxPoiFront/maxPoiSide/smoothHalf/emaAlpha 변경 시 디바운스 저장
|
||||
```
|
||||
→ Yaw±/Pitch± 등 한 번 맞추면 유지되어 "처음부터 맞게" 표시됨.
|
||||
|
||||
### 사용자 보정 절차
|
||||
1. 멀리 어긋난 POI가 보이는 프레임에서 일시정지.
|
||||
2. **Yaw±**(좌우 어긋남) / **Pitch±**(상하 어긋남)를 조금씩 움직여 POI를 실제 위치에 맞춤.
|
||||
3. 자동 저장됨(다음 로드 시 적용). (브라우저별. 휴대는 POI 내보내기와 별개 — 필요 시 확장.)
|
||||
|
||||
### 한계
|
||||
- 자동 보정은 정답 화면좌표(대응점)가 없어 불가 → 1회 수동 보정 필요.
|
||||
- 향후: 먼 POI를 드래그하면 그 편차로 yaw/pitch 오프셋을 역산하는 캘리브레이션 가능.
|
||||
|
||||
---
|
||||
|
||||
## 18. [수정] 멀리서 앞으로 밀려보이는 슬라이드 = 표고 오차 → 드래그 표고 역산
|
||||
|
||||
### 재진단 (앞 §17 정정)
|
||||
증상: POI가 진행방향으로 **앞에(멀리) 표시**되고, 드론이 다가갈수록 **뒤로 밀려** 실제
|
||||
위치에 수렴. → **표고(z) 오차가 맞음.** 지면 어긋남은 **Δ앞 = D·Δz/(H−Δz)** 로 거리 D에
|
||||
**비례**(천정점 0). §17에서 "각도 문제"로 본 건 화면 각도만 봐서 틀렸고, 사용자 관점의
|
||||
지면 거리로 보면 정확히 표고. 방향상 **z가 과대**(POI를 둑·다리로 솟은 선로 표고에 배치 →
|
||||
실지면보다 높음)라 앞으로 밀림.
|
||||
|
||||
### KML 절대고도 재확인 → 사용 불가
|
||||
KMZ/doc.kml 96개 placemark 전부 `절대고도=67.391/392`(상수), coords z=0.
|
||||
**per-POI 실지면고도 아님**(출발점 고도 일괄). 주차장은 그 상당값에서 ~6~9m 더 낮아져야
|
||||
하므로 상수로는 못 고침.
|
||||
|
||||
### 수정 — 드래그로 표고 z 역산 (lat/lon 고정)
|
||||
데이터에 per-POI 지면고도가 없으므로, **드래그로 POI를 실제 지면에 내려놓으면 그 세로
|
||||
위치에 맞는 z를 역산**해 저장. lat/lon은 고정(수평은 보통 정확) → 한 번 맞추면 전 거리에서
|
||||
정렬되어 슬라이드가 사라짐. 보정은 override 파일/영속화로 유지.
|
||||
- `geoProjection.ts solveZForPixelY()`: py=0.5+cy0+(Yc/Zc)(f/sH), Yc·Zc 가 z에 1차 →
|
||||
`z = z0 + (R·Zc0 − Yc0)/(dYc − R·dZc)`, `R=(py−0.5−cy0)·sH/f`, dYc=−b2w[2][2], dZc=b2w[2][1].
|
||||
(왕복검증: z=42→py→solveZ=42.000. 화면 아래로 끌면 z↓=지면 낮춤.)
|
||||
- `StationOverlay.tsx`: 드래그 종료 시 `worldFromPixel`(거리유지) → `solveZForPixelY`(lat/lon
|
||||
고정·z풀이)로 교체. 드롭 세로위치로 z 산출 후 override 저장.
|
||||
|
||||
### 사용
|
||||
편집 모드 → 어긋난 POI를 **실제 지면 위치(보통 아래로)로 드래그** → 표고가 맞춰져 슬라이드
|
||||
제거. 한 번 하면 영속화(§10 override + §17 calib 저장).
|
||||
|
||||
### 한계/대안
|
||||
- per-POI라 다수 POI는 수동. 공통적으로 높으면 "POI 표고"(poiZOffset) 음수로 일괄 하향 가능.
|
||||
- 90m DEM은 절벽/구조물 인접에서 부정확(주차장 50m 오판) → 자동 적용 안 함.
|
||||
|
||||
---
|
||||
|
||||
## 19. [기능] DEM 표고 자동적용 (좌표→지면고도) + 보정 자동저장
|
||||
|
||||
### 배경
|
||||
"POI 좌표를 아니 절대고도(지면고도)를 구할 수 있지 않나?" → 맞음. 데이터엔 per-POI 지면이
|
||||
없지만(KML/SRT는 상수 67.391로 환원) **좌표로 DEM을 조회**하면 됨.
|
||||
|
||||
### 검증 (open-meteo, Copernicus 90m)
|
||||
| POI | DEM | 측점(현재) | 차이 | 해석 |
|
||||
|---|---|---|---|---|
|
||||
| 회덕화물역/주차장 등 선로변 | ~42 | ~41.4 | +0.6 | 거의 동일(정상) |
|
||||
| 경부고속도로 | 35 | 40.8 | −5.8 | 도로는 더 낮음(정상) |
|
||||
| 갑천(하천) | 29 | 50.5 | −21.5 | 하천 훨씬 낮음(정상) |
|
||||
중앙값 차이≈0(선로변), 골짜기 지형은 적절히 하향. DEM 정표고 ≈ 측점 정표고(신대천교 42.0 vs 41.3) → datum 일치, +geoid(25.8)로 타원체고 변환.
|
||||
|
||||
### 구현 (StationOverlay.tsx)
|
||||
- **"🌐 DEM 표고 자동적용"** 버튼: 현재 POI+구조물 좌표를 100개씩 묶어 open-meteo
|
||||
`/v1/elevation` 배치 조회(CORS 허용 확인) → 각 POI override `{lat,lon,z:DEM}` 설정.
|
||||
override z는 정표고로 저장되어 투영 시 geoid 가산(선로 POI와 동일 경로).
|
||||
- **보정 자동저장**: poiOverrides 를 `ghivideo:poiov:<baseName>` localStorage 에 저장·복원
|
||||
(드래그/DEM 결과가 새로고침해도 유지). 파일 내보내기/가져오기는 그대로(휴대용).
|
||||
|
||||
### 한계
|
||||
- 공개 90m DEM이라 **절벽·구조물 인접(예: 신대지구 주차장)은 부정확**(주차장을 50m로 과대) →
|
||||
그런 소수는 **드래그 표고 보정**(§18)으로 미세조정.
|
||||
- 고정밀이 필요하면 국토지리정보원 5m DEM/VWorld 고도API(키 필요)로 교체 가능.
|
||||
|
||||
### 사용
|
||||
편집 모드 → **"DEM 표고 자동적용"** 1클릭 → 전 POI 지면고도 적용(자동저장). 멀리서 밀리는
|
||||
현상이 대부분 완화됨. 남는 소수만 드래그.
|
||||
|
||||
---
|
||||
|
||||
## 카메라 파라미터 패널 정리 (현재 노출되는 튜닝값)
|
||||
|
||||
| 슬라이더 | 코드 키 | 의미 | 기본값 |
|
||||
|---|---|---|---|
|
||||
| smooth | `smoothHalf` | 드론 자세 이동평균 반폭(프레임) | 10 |
|
||||
| EMA α | `emaAlpha` | 화면좌표 평활(↑반응/↓부드러움) | 0.4 |
|
||||
| **POI 앞쪽** | `maxPoiFront` | **진행방향 앞쪽 유효거리(평면)** | **60m** |
|
||||
| **POI 옆쪽** | `maxPoiSide` | **진행방향 옆쪽(좌우) 유효거리(평면)** | **40m** |
|
||||
|
||||
> POI 거리필터는 **평면(수평) 거리**(고도차 0 간주)를 **진행방향 앞/옆으로 분해**해 비등방
|
||||
> 적용. `toCameraCoords`가 yaw 기준 `fwd`(+앞), `side`(좌우)를 채우고, precompute가
|
||||
> `fwd > maxPoiFront || |side| > maxPoiSide` 면 제외. (이전: 단일 평면반경 → 앞/옆 분리.)
|
||||
| Yaw/Pitch/Roll ± | `yawOffset/pitch/roll` | 자세 오프셋(SRT값에 가산) | 0 |
|
||||
| off X/Y/Z | `offX/offY/offZ` | 드론 위치 보정(ENU, m) | 0 |
|
||||
| 지오이드 | `geoidOffset` | 정표고→타원체고(전체) | 25.8 |
|
||||
| **POI 표고** | `poiZOffset` | **POI만 표고 보정** | **0** |
|
||||
| f / cx₀ / cy₀ / senW / senH | `focalLen/cx0/cy0/sensorW/sensorH` | 내부표정(FOV/주점) | 24/0/0/36/20.25 |
|
||||
|
||||
---
|
||||
|
||||
## 빌드 & 테스트 절차 (중요)
|
||||
|
||||
이 프로젝트는 **HMR 개발서버가 아니라** pm2가 빌드 산출물(`client/dist`)을 정적 서빙한다.
|
||||
→ 소스 수정 후 **반드시 재빌드해야** 브라우저에 반영된다(새로고침만으론 안 됨).
|
||||
|
||||
```bash
|
||||
# 시스템 node는 v12라 빌드 불가 → nvm node20 사용
|
||||
export PATH=~/.nvm/versions/node/v20.20.2/bin:$PATH
|
||||
cd ~/projects/gitea/b23042/GhiVideo
|
||||
npm run build -w client # tsc + vite build → client/dist 갱신
|
||||
# 서버(pm2 ghiVideo)는 요청마다 dist를 읽으므로 재시작 불필요
|
||||
# 브라우저에서 Ctrl+Shift+R (하드 새로고침)
|
||||
```
|
||||
> 자주 반복하면 `npm run dev -w client`(vite HMR, 포트 5173)가 편함.
|
||||
|
||||
---
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` / `vite build` 통과 (node20).
|
||||
- proj4로 수직 기하 재계산 → 부각 11° 및 POI 부각 32°(지면기준 정확) 확인.
|
||||
- yaw 바이어스 -0.74° (1194 이동샘플) → yaw 오프셋 불필요.
|
||||
|
||||
## 남은 이슈 / 다음 단계
|
||||
- 드론 `abs_alt`가 구간별로 노이즈 있음(지면 위 높이 17m→35m→스파이크 68m). 정밀 수직은
|
||||
per-프레임 abs_alt 신뢰도에 의존. 필요 시 SRT `rel_alt` 병합 고려.
|
||||
- POI 실제 표고(DEM/건물높이) 확보 시 `poiZOffset` 수동튜닝 대신 자동화 가능.
|
||||
- FOV(focal/sensorW)는 16:9 영상 화각이 24mm/36mm 가정과 다를 수 있어 미세 좌우오차 시 튜닝 대상.
|
||||
|
||||
**소요 시간**: 90분
|
||||
**Context 사용량**: input ~180k / output ~25k tokens
|
||||
@@ -0,0 +1,43 @@
|
||||
# 특허 발굴 기술문서 작성 — 스테이션(측점) 기반 주행영상 플레이어 (HTML)
|
||||
|
||||
**소요 시간**: 약 20분
|
||||
**Context 사용량**: input ~70k / output ~12k tokens
|
||||
|
||||
---
|
||||
|
||||
## 작업 개요
|
||||
|
||||
`docs/특허_추출_프롬프트.md`의 5단계 특허 발굴 절차를, 본 프로젝트(GhiVideo)가 실제로 구현한
|
||||
**스테이션(측점)/위치 기반 주행영상 플레이어**에 적용하여 특허 발굴 기술문서를 HTML로 작성했다.
|
||||
|
||||
- 산출물: `docs/특허_스테이션기반_주행영상_플레이어.html` (자체완결 인라인 CSS, 다크 테마, 인쇄 가능)
|
||||
|
||||
## 근거 자료 (실구현 기반)
|
||||
|
||||
프롬프트의 "입력 자료"를 시간기반 추상 개념이 아니라 실제 코드/히스토리에서 추출:
|
||||
- `2026-06-22_POI투영오차-개선.md` — 투영 파이프라인(TM→ENU→카메라→핀홀), 측점표고+지오이드,
|
||||
역투영 드래그 보정, 비등방 거리필터, title짝짓기/이상치거부
|
||||
- `2026-04-01_RoutePanel-미니맵-추가.md` — 측점/㎞ 기반 미니맵·seek
|
||||
- `2026-06-19_v2.0-import전환.md` — 측량 CSV 융합, 방향전환 추출
|
||||
- `2026-04-01_StationOverlay-렌더링분석.md` — labelMap 사전계산 + RAF 보간
|
||||
|
||||
## 문서 구성 (5단계)
|
||||
|
||||
1. **기술요소 인벤토리** E1~E9 (측점색인, geo-registration, DEM-free 고도, 역투영보정, 비등방필터, 노이즈강건 등)
|
||||
2. **특허적격 필터** — 과제/해결수단/비자명성/측정효과 표 + 부적합(E7·E8·E9) 판정
|
||||
3. **발명 후보** A(측점기반 색인·탐색), B(측량측점 표고 DEM-free 정합), C(슬랜트거리보존 역투영 보정), D·E(중)
|
||||
4. **청구항 초안** — B(방법+매체), C(시스템) 독립항 + 종속항, 카테고리 선택 근거
|
||||
5. **선행기술 키워드** 국문/영문 각 5+ , IPC/CPC 추정 분류
|
||||
|
||||
## 결론 (출원 우선순위)
|
||||
|
||||
- 1순위: B+C 결합(정합 정확도 — 부각 42.7°→11°, DEM 대비 0.2m 검증된 효과)
|
||||
- 2순위: A(측점 색인·탐색·위치고정 주석) — 다중회차 동기화 구현범위 [확인 필요]
|
||||
- 3순위: D·E → 1·2순위 종속항으로 흡수
|
||||
- 제외: E7·E8·E9(통상의 데이터 처리, 진보성 곤란)
|
||||
|
||||
## 출력 규칙 준수
|
||||
|
||||
- 추정 부분은 `[확인 필요]` 명시(다중회차 동기화, 위치앵커 주석 데이터모델, 청구 용어)
|
||||
- 특허 부적합 요소는 사유와 함께 명시
|
||||
- 결론에 1~3순위 + 제외 제시
|
||||
@@ -0,0 +1,29 @@
|
||||
# LX(한국국토정보공사) 특허 조사 — 증강현실 / 영상정합 / GIS / 표고
|
||||
|
||||
**소요 시간**: 약 75분
|
||||
**Context 사용량**: input ~95k / output ~12k tokens (추정)
|
||||
|
||||
## 작업 개요
|
||||
한국국토정보공사(LX, Korea Land and Geospatial InformatiX Corp.)가 출원한 KR 특허 중
|
||||
증강현실 / 영상 정합 / GIS 오버레이 / 지오이드·표고 관련 특허 조사.
|
||||
|
||||
## 방법 / 우회
|
||||
- Google Patents XHR API(`/xhr/query`)는 본 환경 IP에서 "Sorry..." 503(봇 차단)으로 직접 접근 불가.
|
||||
- 개별 특허 페이지도 동일하게 차단됨.
|
||||
- **우회 1**: PatentGuru(`patentguru.com/search?q=Korea+Land+and+Geospatial+Informatix`)는 차단 없이
|
||||
LX 출원인 결과 82건(첫 2페이지=20건만 무료 노출) 제공. 제목/번호 확보.
|
||||
- **우회 2**: jina reader 프록시(`r.jina.ai/https://patents.google.com/...`)로 Google Patents 차단 우회.
|
||||
XHR JSON으로 `assignee=한국국토정보공사` total=71건 확인, 첫 10건 풀 메타데이터(출원인/발명자/날짜/PDF) 확보.
|
||||
단, 다량 요청 후 jina 공유 IP도 Google에 차단되어 추가 페이지/키워드 쿼리는 실패.
|
||||
- **우회 3**: `patentimages.storage.googleapis.com` PDF는 차단 없음 → pymupdf로 출원인/초록 직접 검증.
|
||||
|
||||
## 핵심 발견
|
||||
- LX 포트폴리오 핵심은 **공간정보·디지털트윈·지적측량·드론영상·지하공간** 분야.
|
||||
- **증강현실(증강현실) 전용 특허는 확인되지 않음.** LX의 AR 지적측량은 2023.5 음성군 현장 시연
|
||||
(드론·MMS·GNSS·AR) 등 *서비스* 형태이며 AR 특허로 매칭되지 않음.
|
||||
- **지오이드(지오이드) 전용 특허도 확인되지 않음.** 표고 관련은 DTM 특허(KR102199940B1)가 유일한 근접 매칭.
|
||||
검색 시 표고/DEM은 대부분 형제기관인 **국토지리정보원(NGII)** 자료였음.
|
||||
- 영상/모델 정합 매칭: **KR20220135789A**(건물형상정합), **KR20230055533A**(CCTV→공간정보),
|
||||
**KR102376912B1**(정사영상+AI 영상판독), **KR102199940B1**(DTM/디지털트윈).
|
||||
|
||||
자세한 목록과 출처는 최종 보고 참조.
|
||||
@@ -0,0 +1,60 @@
|
||||
# 2026-06-23 — 드론 경로 맨앞(지평선) 오차 컷 + 경로표고 슬라이더 동적 상한
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~155k / output ~17k tokens (추정)
|
||||
|
||||
---
|
||||
|
||||
## 요청
|
||||
1. 드론 궤적이 **맨 앞(진행방향 먼 쪽)** 에서 휘어/튀는 오차. 해결?
|
||||
2. 경로 표고 슬라이더 상한 120의 근거 없음 → **데이터 기반 동적 상한(옵션 1)** 적용.
|
||||
|
||||
---
|
||||
|
||||
## 1. 맨 앞(지평선) 오차 — 전방 거리 컷
|
||||
|
||||
### 원인
|
||||
z=42(지면)면 드론은 지면 위 ~20m뿐. 먼 앞쪽 점일수록 내려보는 각이 얕아져(지평선 근접) GPS·표고 오차가 화면에서 크게 증폭 → 끝이 휘어/튐. 기하적으로 신뢰 불가 구간.
|
||||
|
||||
### 해결 — `StationOverlay.tsx` 드론 경로 빌드 루프
|
||||
진행방향 전방거리(`cc.fwd`)가 `MAX_FWD=200m`를 넘으면 그리기 중단. (i 증가=시간 진행=전방으로 이동, fwd 단조증가 → `break`로 조기 종료, 연산도 절약.)
|
||||
|
||||
```ts
|
||||
const MAX_FWD = 200; // 전방 거리 컷(m)
|
||||
for (let i = lo; i <= hi; i += STEP) {
|
||||
const [la, lo2] = sm(i);
|
||||
const cc = toCameraCoords(drone, la, lo2, Z, params, worldOrigin);
|
||||
if ((cc.fwd ?? 0) > MAX_FWD) break; // 지평선쪽 중단
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
> 더 멀리 보고 싶으면 MAX_FWD↑, 더 깔끔히 자르려면 ↓. (드론 고도가 낮을수록 작게.)
|
||||
|
||||
---
|
||||
|
||||
## 2. 경로 표고 슬라이더 동적 상한 (옵션 1)
|
||||
|
||||
기존 `max={120}`은 근거 없는 라운드 값. → **중심선(centerline) 데이터의 최대 지면표고 + 10m** 로 자동 설정. 데이터 없으면 60 폴백.
|
||||
|
||||
```ts
|
||||
import { ..., useMemo } from 'react';
|
||||
const pathZMax = useMemo(() => {
|
||||
let m = 0;
|
||||
for (const c of storeCenterline) if (typeof c.z === 'number' && c.z > m) m = c.z;
|
||||
return m > 0 ? Math.ceil(m + 10) : 60;
|
||||
}, [storeCenterline]);
|
||||
// 슬라이더:
|
||||
<ParamRow label="경로 표고" ... max={pathZMax} ... />
|
||||
```
|
||||
|
||||
데이터셋이 바뀌어도 그 구간 지형 최고점에 맞춰 상한이 자동 조정됨.
|
||||
|
||||
---
|
||||
|
||||
## 변경 파일
|
||||
- `client/src/components/overlay/StationOverlay.tsx`
|
||||
- 드론 경로 루프: `MAX_FWD=200` 전방 컷(`cc.fwd` 기준 break)
|
||||
- `useMemo` import + `pathZMax`(중심선 max z +10) + 경로표고 슬라이더 `max={pathZMax}`
|
||||
|
||||
빌드: `index-BzoF-8z_.js` ✓
|
||||
@@ -0,0 +1,32 @@
|
||||
# 2026-06-23 — 드론 궤적 끊김 제거(연속 폴리라인)
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~165k / output ~19k tokens (추정)
|
||||
|
||||
---
|
||||
|
||||
## 요청
|
||||
드론 궤적이 **끊겨(구슬처럼) 보임** → 부드럽게.
|
||||
|
||||
## 원인
|
||||
- 기존: `segFrom`이 점 쌍마다 **조각 선분**을 만들고, RAF에서 조각마다 `moveTo`/`lineTo` → 각 조각이 별도 서브패스라 **연결부가 둥글게 안 이어짐**.
|
||||
- 선이 가늘고(2px, α0.55) 노란 점(r3)이 도드라져 **점선/구슬**처럼 보였다.
|
||||
|
||||
## 해결 — `StationOverlay.tsx`
|
||||
1. **데이터 일원화**: `RenderCache`에서 `dronePathSegs` 제거, **진행방향 순서 점 배열 `dronePathPts`** 만 유지. 빌드 루프는 카메라 앞쪽(`Zc>=CLIP_Z`) 점만 순서대로 수집(`continue`로 뒤쪽 제외, `MAX_FWD` break 유지). `STEP 5→3`(더 촘촘), `BACK 150→60`.
|
||||
2. **RAF: 하나의 연속 폴리라인**으로 그림 — `moveTo(pts[0])` 후 전부 `lineTo`, `lineJoin/lineCap='round'`.
|
||||
- 어두운 밑선(width5, 검정 α0.35·dpa) + 본선(width3, 하늘색 dpa) 2겹 → 배경 위에서도 끊김 없이 선명.
|
||||
3. **점**: r3→**r1.8**, 4개마다 1개만 → 선 흐름 방해 없이 측정점 흔적만.
|
||||
|
||||
```ts
|
||||
// 빌드: 앞쪽 점만 순서대로
|
||||
for (let i=lo;i<=hi;i+=STEP){ const cc=...;
|
||||
if((cc.fwd??0)>MAX_FWD) break;
|
||||
if(cc.Zc<CLIP_Z) continue;
|
||||
const p=pixelFromCamera(cc,params); dronePathPts.push([p.pxRaw,p.pyRaw]); }
|
||||
// RAF: 연속선
|
||||
ctx.lineJoin=ctx.lineCap='round';
|
||||
ctx.beginPath(); ctx.moveTo(pts[0]); for(k=1..) ctx.lineTo(pts[k]); ctx.stroke();
|
||||
```
|
||||
|
||||
빌드: `index-C3L5Fc0X.js` ✓
|
||||
@@ -0,0 +1,35 @@
|
||||
# 2026-06-23 — 선형/드론궤적 화면갱신 부드럽게 (RAF 60fps 보간 투영)
|
||||
|
||||
**소요 시간**: 약 25분
|
||||
**Context 사용량**: input ~210k / output ~26k tokens (추정)
|
||||
|
||||
## 문제
|
||||
드론 궤적·선형(중심선)이 재생 중 "드드득" 끊김.
|
||||
|
||||
## 원인
|
||||
- **라벨(측점/POI)**: RAF에서 `timeRef`(60fps 부드러운 시간) 기반 `estFrame`으로 두 프레임 사이를 **매 프레임 보간** → 부드러움.
|
||||
- **선/궤적**: `renderCacheRef`를 **`currentTime`(timeupdate, ~10Hz 불규칙) → `panelDroneFrame` 변경 → effect 재실행** 경로로만 재생성. 즉 **~10Hz 이산 갱신** → 끊김.
|
||||
|
||||
## 해결 — 선/궤적도 RAF에서 매 프레임 보간 포즈로 직접 투영
|
||||
`StationOverlay.tsx`:
|
||||
|
||||
1. **`buildLines(drone)`** useCallback 신설 — 투영 헬퍼(oc/segFrom) + 중심선 + 드론경로 투영을 캡슐화, refs만 읽어 stable. `{centerlineSegs, dronePathPts}` 반환.
|
||||
2. **`poseAt(estFrame)`** useCallback — 연속 프레임번호에서 앞/뒤 `smoothFrame`을 **선형보간**(yaw는 최단각)해 **서브프레임 드론 포즈** 생성.
|
||||
3. **RAF**: `estFrame`을 (라벨과 공통으로) 먼저 계산 → `buildLines(poseAt(estFrame))`로 선/궤적을 **매 프레임 재투영** → 그림. 라벨 블록은 같은 `estFrame` 재사용.
|
||||
4. **캐시 effect 축소**: `renderCacheRef`는 이제 선/궤적을 보관하지 않고 **나침반/메타데이터(effectiveYaw·hFovRad·count)만**. `RenderCache`에서 `centerlineSegs/dronePathPts` 제거.
|
||||
|
||||
```ts
|
||||
// RAF
|
||||
const estFrame = (timeRef ? timeRef.current : …) * VIDEO_FPS;
|
||||
const lines = buildLines(poseAt(estFrame) ?? smoothFrame(...));
|
||||
// draw lines.centerlineSegs / lines.dronePathPts
|
||||
```
|
||||
|
||||
## 성능
|
||||
- 중심선 = **224점**(center.csv), 드론경로 윈도 = 수백 점. 60fps 투영 ≈ 초당 1.3만~수만 회 → 무시 가능.
|
||||
- 포즈 보간은 `smoothFrame` 2회/프레임(±smoothHalf 평균) — 가벼움.
|
||||
|
||||
## 효과
|
||||
선형·드론궤적이 라벨과 **동일한 60fps 부드러운 시간축**으로 움직여 끊김 제거. 일시정지 시 `estFrame` 고정 → 정지.
|
||||
|
||||
빌드: `index-kWDkGw69.js` ✓
|
||||
@@ -0,0 +1,38 @@
|
||||
# 특허 선행기술 조사 (글로벌 + 한국 KIPRIS) 및 HTML 문서 반영
|
||||
|
||||
**소요 시간**: 약 30분
|
||||
**Context 사용량**: input ~140k / output ~18k tokens (deep-research 워크플로 subagent ~2.5M 별도)
|
||||
|
||||
---
|
||||
|
||||
## 작업 개요
|
||||
|
||||
[특허_스테이션기반_주행영상_플레이어.html](../특허_스테이션기반_주행영상_플레이어.html)의 발명 후보 A·B·C에 대해
|
||||
실제 선행기술을 조사하여 신규성을 검증하고, 결과를 HTML 문서 §6 "선행기술 조사 결과" 섹션으로 추가했다.
|
||||
|
||||
## 조사 방법
|
||||
1. **글로벌**: deep-research 워크플로(5각도 fan-out 검색 → 20소스 fetch → 25주장 3표 교차검증)
|
||||
2. **한국(KIPRIS)**: general-purpose 에이전트 3개 병렬(후보 A/B/C 각각), Google Patents KR 본문 확인
|
||||
|
||||
## 핵심 결론
|
||||
|
||||
| 후보 | 판정 | 가장 가까운 선행기술 |
|
||||
|------|------|---------------------|
|
||||
| 큰 틀 E1·E2 | ❌ 공지 확정 | Esri ArcGIS Pro FMV, US9996976B2, Sensors 2018, Mandli, LineVision, ENSCO VTW |
|
||||
| **A** 측점 탐색 | 🟡 부분 선행(중) | **KR102268318B1**(위치동기 비교재생), **KR100411587B1**(위치→영상 색인) — 광의청구 거절 위험 |
|
||||
| **B** 측점표고 정합 | 🟢 신규성 있음 | DEM-free는 전부 flat-plane 대체, 지오이드 datum 결합 미공개. KR도 동일(LX는 DTM 의존) |
|
||||
| **C** 역투영 보정 | 🟢 신규성 있음 | US9188444B2(depth map 의존, **KR 패밀리 없음**), KR102422292B1(2영상 의존) — 슬랜트거리 보존 미공개 |
|
||||
|
||||
→ **B+C 1순위 유지·강화, A는 2순위 중급으로 하향**(시간축 LRS 비선형 재좌표화+회차간 위치앵커+다중회차 동기 셋으로 좁게 청구 필수).
|
||||
|
||||
## HTML 변경
|
||||
- TOC에 "⑥ 선행기술 조사결과" 추가
|
||||
- §6 신규 섹션: 공지 확정 배너 + 후보별 판정표(글로벌+한국) + 출처/한계 2단 카드
|
||||
- 결론: A 뱃지 "강·요확인"→"중(조사 후 하향)", 선행기술 반영 배너, 한계/footer 갱신
|
||||
|
||||
## 미해소(출원 전 필수)
|
||||
- **KIPRIS 전문 DB 직접검색 미수행**(Google Patents 색인 기반) → 18개월 미공개·미색인 KR 출원 누락 가능. 변리사 전문검색 필요.
|
||||
- 후보 A의 "LRS + 프레임별 카메라자세 AR투영 결합" 선행기술(Esri Roadway/LRS+FMV) 존재 여부 미확인 — A 운명 좌우.
|
||||
- 다중회차 위치동기 비교재생의 실제 구현 범위 확인 필요.
|
||||
|
||||
> 참고: 하위 에이전트가 `docs/history/2026-06-23_LX-한국국토정보공사-특허조사.md`를 별도 생성함.
|
||||
@@ -0,0 +1,30 @@
|
||||
# 2026-06-23 — 화면표시 옵션 현재 튜닝값을 코드 기본값으로
|
||||
|
||||
**소요 시간**: 약 8분
|
||||
**Context 사용량**: input ~175k / output ~21k tokens (추정)
|
||||
|
||||
## 요청
|
||||
사용자가 화면표시 옵션 패널에서 맞춘 현재 값을 코드 기본값으로.
|
||||
|
||||
## 반영 (StationOverlay.tsx useState 기본값)
|
||||
| 항목 | 이전 | 변경 |
|
||||
|------|------|------|
|
||||
| smoothHalf | 10 | **60** |
|
||||
| emaAlpha | 0.4 | **1.0** |
|
||||
| maxPoiFront | 60 | **240** |
|
||||
| maxPoiSide | 40 | **80** |
|
||||
| droneHeightDrop | 20 | **30** |
|
||||
| showDronePath | false | **true** |
|
||||
| dronePathZ | 42 | **78** |
|
||||
| dronePathAlpha | 0.55 | **0.75** |
|
||||
| showCenterline | true | true(유지) |
|
||||
|
||||
## 판단 — poiDroneHeight는 기본 OFF 유지
|
||||
패널에 "POI높이=드론−30m (비교중)"이 ON이었으나, 이는 **비교/테스트 모드**(모든 POI를 실제 DEM/선로표고 대신 드론−N 높이에 배치). 기본 ON이면 그동안 작업한 표고가 무시되므로 **기본 false 유지**, `droneHeightDrop`만 30으로. (원하면 true로 변경 가능.)
|
||||
|
||||
## 참고 — 영속화 범위
|
||||
- `smoothHalf/emaAlpha/maxPoiFront/maxPoiSide`, `params`는 **baseName별 localStorage(calib)** 에 저장되어, 현재 데이터셋은 기존 저장값이 우선. 코드 기본값 변경은 **신규 데이터셋·보정 초기화 시** 적용.
|
||||
- `dronePathZ/dronePathAlpha/showDronePath/droneHeightDrop/poiDroneHeight`는 calib에 미저장 → **코드 기본값이 매 로드 적용**.
|
||||
- (옵션) 이들도 calib 저장에 추가하면 데이터셋별 튜닝이 영구 저장됨 — 필요 시 진행.
|
||||
|
||||
빌드: `index-DHgVDyhN.js` ✓
|
||||
@@ -0,0 +1,111 @@
|
||||
# 2026-06-23 — 드론경로 평활·투명도 + 화면표시 옵션 패널 분리
|
||||
|
||||
**소요 시간**: 약 60분
|
||||
**Context 사용량**: input ~140k / output ~14k tokens (추정)
|
||||
|
||||
---
|
||||
|
||||
## 배경 / 요청
|
||||
|
||||
QGIS 드론 궤적이 계단/Z로 보이는 원인 분석(= GPS 10Hz를 30fps에 복사·유지하여 생긴 계단 + 02:05 방향전환) 이후, 다음을 요청받음:
|
||||
|
||||
1. 드론 경로를 **실제 GPS 측정점(동그라미)+선**으로 표시 → 표시했더니 **먼 쪽에서 톱니로 튐**(영상은 매끄러운데).
|
||||
2. 드론 경로에 **투명도** 추가.
|
||||
3. **선형(중심선)** 과 **드론 궤적** 을 **각각 독립 on/off**.
|
||||
4. 카메라 파라미터를 제외한 **모든 표시 설정**을 **"화면표시 옵션" 별도 버튼 → 팝업**으로 분리.
|
||||
|
||||
---
|
||||
|
||||
## 1. 드론 경로 — 측정점 점+선 → 이동평균 평활
|
||||
|
||||
`client/src/components/overlay/StationOverlay.tsx` 캐시 빌드부.
|
||||
|
||||
### 문제
|
||||
GPS 노이즈(±0.5m) + 10Hz 계단이 **z=0(드론 58m 아래·지평선 쪽) 비스듬 투영**에서 화면상 큰 톱니로 증폭. 실제 비행/영상은 매끄러움.
|
||||
|
||||
### 해결
|
||||
경로점을 **±15프레임 이동평균(`sm`)** 으로 펴서 투영. 5프레임마다 서브샘플, 점(dot)+선 동시 생성.
|
||||
|
||||
```ts
|
||||
const BACK = 150, FWD = 1500, STEP = 5, PW = 15;
|
||||
const sm = (i) => { // ±PW 프레임 lat/lon 평균
|
||||
let la=0, lo2=0, n=0;
|
||||
for (let k=max(0,i-PW); k<=min(len-1,i+PW); k++){ la+=frames[k].lat; lo2+=frames[k].lon; n++; }
|
||||
return [la/n, lo2/n];
|
||||
};
|
||||
for (let i=lo; i<=hi; i+=STEP) {
|
||||
const [la,lo2] = sm(i);
|
||||
const cc = toCameraCoords(drone, la, lo2, Z, params, worldOrigin);
|
||||
if (prevCc) { const s=segFrom(prevCc,cc); if (s) dronePathSegs.push(s); }
|
||||
if (cc.Zc>=CLIP_Z) { const p=pixelFromCamera(cc,params); if(화면안) dronePathPts.push([p.pxRaw,p.pyRaw]); }
|
||||
prevCc = cc;
|
||||
}
|
||||
```
|
||||
|
||||
`RenderCache`에 `dronePathPts: [number,number][]` 추가.
|
||||
|
||||
---
|
||||
|
||||
## 2. 투명도 (dronePathAlpha)
|
||||
|
||||
상태/ref 추가, 기본 0.55. RAF에서 선·점 색의 alpha에 적용.
|
||||
|
||||
```ts
|
||||
const [dronePathAlpha, setDronePathAlpha] = useState(0.55);
|
||||
const dronePathAlphaRef = useRef(0.55);
|
||||
// RAF:
|
||||
const dpa = dronePathAlphaRef.current;
|
||||
ctx.strokeStyle = `rgba(80,200,255,${dpa})`; // 선
|
||||
ctx.fillStyle = `rgba(255,230,90,${dpa})`; // 점
|
||||
```
|
||||
|
||||
화면표시 옵션 팝업에 "투명도" 슬라이더(0.1~1.0).
|
||||
|
||||
---
|
||||
|
||||
## 3. 선형(중심선)·드론 궤적 독립 토글
|
||||
|
||||
기존: 중심선·드론경로·측점·POI 모두 `visible`(부모 측점선 토글)에 묶임 → 측점선 OFF면 전부 사라짐.
|
||||
|
||||
### 변경
|
||||
- `showCenterline` 상태/ref 신설(기본 true).
|
||||
- **캐시 빌드 게이트**: `!visible` → `!(visible || showCenterline || showDronePath)` (effect deps에 `showCenterline` 추가).
|
||||
- **RAF 그리기 분리**:
|
||||
```
|
||||
if (cache) {
|
||||
if (showCenterlineRef.current) → 중심선
|
||||
if (dronePathSegs/Pts) → 드론 경로 (showDronePath 시에만 캐시에 채워짐)
|
||||
if (visibleRef.current) → 측점/POI 라벨
|
||||
}
|
||||
```
|
||||
(기존 `if (visibleRef.current && cache)` 한 덩어리를 위 3개로 분해. 라벨 블록을 `if(visibleRef.current){…}`로 감싸고 닫는 중괄호 1개 추가.)
|
||||
|
||||
### 동작 변화(주의)
|
||||
부모의 **측점선 토글(visible)** 은 이제 **측점/POI 라벨만** 제어. **중심선(선형)은 화면표시 옵션의 "선형(중심선) 표시" 토글**(기본 ON)로 별도 제어. 드론 궤적도 측점선과 무관하게 단독 표시 가능.
|
||||
|
||||
---
|
||||
|
||||
## 4. 패널 분리 — 카메라 파라미터 / 화면표시 옵션
|
||||
|
||||
우측(나침반 아래) 버튼 2개:
|
||||
- **▼ 화면표시 옵션**(`showDisplay`): 표시 토글(선형/드론궤적+표고+투명도), 스무딩(smooth/EMA), POI 필터/높이(앞·옆·드론고도비교), POI 위치 편집(편집모드/표고/내보내기·가져오기/DEM/보정초기화).
|
||||
- **▼ 카메라 파라미터**(`showControls`): 자세 보정(Yaw/Pitch/Roll), 위치 보정(offX/Y/Z·지오이드), 내부표정(f/cx₀/cy₀/senW/senH), 실시간 readout, params 초기화.
|
||||
|
||||
분류 기준: **드론 카메라 투영 파라미터 = 카메라 파라미터**, 그 외 표시/필터/편집 = 화면표시 옵션.
|
||||
|
||||
`const [showDisplay, setShowDisplay] = useState(false);` 추가. 두 팝업은 독립 토글(동시 열림 가능, 각 `max-h-[80vh] overflow-y-auto`).
|
||||
|
||||
---
|
||||
|
||||
## 변경 파일
|
||||
- `client/src/components/overlay/StationOverlay.tsx`
|
||||
- 상태/ref: `showCenterline`, `dronePathAlpha`, `showDisplay`
|
||||
- `RenderCache.dronePathPts`
|
||||
- 캐시 빌드: 경로 평활(sm)+점 수집, 게이트 `(visible||showCenterline||showDronePath)`
|
||||
- RAF: 중심선/드론경로/라벨 독립 그리기 + 투명도
|
||||
- 패널 JSX: 1개 → 화면표시 옵션 + 카메라 파라미터 2팝업
|
||||
|
||||
빌드: `index-BbLxqCEp.js` ✓ (node20 `npm run build -w client`)
|
||||
|
||||
## 확인
|
||||
Ctrl+Shift+R → 우측 나침반 아래 버튼 2개. "화면표시 옵션"에서 선형/드론궤적 독립 토글·투명도·표고 조절. "카메라 파라미터"는 투영 보정만.
|
||||
@@ -0,0 +1,40 @@
|
||||
# 교량(신대천교(복)) 영상 오버레이 라벨 표출
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 증상
|
||||
영상에서 다리 위가 신대천교(복)인데 **신대천교(복) 라벨이 영상에 표시되지 않음**.
|
||||
|
||||
## 원인 (버그 아님 — 의도된 라우팅)
|
||||
- 신대천교(복)은 `building/03)교량.csv` 출신 **카테고리 `교량`** 구조물
|
||||
(`_POI.csv`/지장물엔 없음 — [2026-06-22_POI투영오차-개선.md](2026-06-22_POI투영오차-개선.md) §13 참조).
|
||||
- §15에서 구조물 표출 위치를 분리: **교량(03)·터널(04)=하단 스테이션바**, 구교(06)·지장물·_POI=영상.
|
||||
- `StationOverlay`의 영상 구조물 필터가 `category === '구교'`만 통과시켜 `교량`은 영상에서 제외됨.
|
||||
|
||||
## 조치 (사용자 결정: 영상은 라벨 모두 표출 / 시설등급 필터는 스테이션바 전용)
|
||||
`StationOverlay.tsx`의 영상 구조물 필터를 **모든 선로 구조물(교량/터널/구교)** 허용으로 변경:
|
||||
```diff
|
||||
+ const OVERLAY_STRUCT_CATEGORIES = new Set(['구교', '교량', '터널']);
|
||||
...
|
||||
- s.category === '구교' && typeof s.lat === 'number' && typeof s.lon === 'number'
|
||||
+ s.category != null && OVERLAY_STRUCT_CATEGORIES.has(s.category) &&
|
||||
+ typeof s.lat === 'number' && typeof s.lon === 'number'
|
||||
```
|
||||
- 교량/터널/구교 모두 기존 POI 파이프라인(평면 거리필터·title 보간·EMA·드래그/DEM 보정)으로 표출.
|
||||
- 역사(철도역, type='station')는 미포함 → 스테이션바 전용 유지.
|
||||
|
||||
### 시설등급(시설종별) 필터 — 영상 무관, 스테이션바 전용 (기존 구조 확인)
|
||||
- grade 필터는 `isGradeVisible(s.grade, gradeFilter)`로 **StationBar.tsx**·**RoutePanel.tsx에만** 적용.
|
||||
영상 오버레이는 grade를 보지 않음 → **영상은 시설등급과 무관하게 항상 모두 표출**(요구사항 그대로).
|
||||
- 따라서 코드 변경은 영상 필터(위)만으로 충분. 스테이션바 측은 미변경.
|
||||
|
||||
## 참고/한계
|
||||
- 영상 POI 거리필터 기본값(앞 60m/옆 40m)이 적용됨. 신대천교(복)이 멀리 있으면
|
||||
카메라 파라미터 패널의 **POI 앞쪽/옆쪽** 슬라이더를 키워야 보일 수 있음.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 20분
|
||||
**Context 사용량**: input ~95k / output ~6k tokens
|
||||
@@ -0,0 +1,47 @@
|
||||
# 화면표시 옵션 — "기본값" 버튼 + 설명문구 툴팁화
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 요구
|
||||
1. 화면표시 옵션 창 우측 하단에 **"기본값"** 버튼 → 누르면 옵션 일괄 복귀.
|
||||
2. 컨트롤 사이의 **설명문구**가 세로 공간을 차지 → **마우스 오버 툴팁**으로 바꿔 패널 높이 축소.
|
||||
|
||||
## 구현
|
||||
|
||||
### 1. 기본값(DISPLAY_DEFAULTS) + 버튼
|
||||
- 사용자 확정 기본값(스크린샷 기준)을 모듈 상수로 정의, **useState 초기값과 단일 출처 공유**:
|
||||
```ts
|
||||
const DISPLAY_DEFAULTS = {
|
||||
showCenterline: true, showDronePath: true,
|
||||
dronePathZ: 58, dronePathAlpha: 0.75,
|
||||
smoothHalf: 60, emaAlpha: 1.0,
|
||||
maxPoiFront: 310, maxPoiSide: 135,
|
||||
poiDroneHeight: true, droneHeightDrop: 24,
|
||||
};
|
||||
```
|
||||
(이전 코드 기본값 dronePathZ 78 / maxPoiFront 240 / maxPoiSide 80 → 스크린샷 값으로 갱신.)
|
||||
- `resetDisplayDefaults()` 콜백이 위 10개 상태를 일괄 set. POI 보정/편집모드는 제외(별도 "보정 초기화" 유지).
|
||||
- 패널 최하단에 카메라 패널의 "초기화"와 동일한 스타일의 **"기본값"** 버튼(우측 정렬) 추가.
|
||||
- 참고: localStorage 영속화 대상(maxPoiFront/Side·smoothHalf·emaAlpha·params)은 버튼 클릭 후 디바운스 저장으로 기본값이 유지됨. 나머지(dronePathZ 등)는 원래 세션 한정.
|
||||
|
||||
### 2. 설명문구 → 툴팁
|
||||
- `ParamRow`에 `tip?: string` 추가: 있으면 라벨에 **점선 밑줄 + 마우스오버 네이티브 툴팁**(`title`).
|
||||
- 별도 `InfoTip` 컴포넌트(작은 `ⓘ`, `title` 툴팁) 추가 → 섹션 헤더용.
|
||||
- 기존 `text-gray-600` 설명 div 6개 제거하고 해당 컨트롤/헤더의 tip으로 이전:
|
||||
| 기존 설명 div | 이전 위치 |
|
||||
|---|---|
|
||||
| GPS 측정점 표고/α | 경로표고·투명도 ParamRow tip |
|
||||
| ±fr=±ms | smooth ParamRow tip |
|
||||
| α→lag ms | EMA α ParamRow tip |
|
||||
| 진행방향 앞/옆 이내 표시 | POI 앞쪽·옆쪽 ParamRow tip |
|
||||
| POI 드론 아래 배치 | 이격거리 ParamRow tip |
|
||||
| 편집/DEM 안내 | "POI 위치 편집" 헤더 InfoTip |
|
||||
- 편집모드 안내문(긴 1줄)도 헤더 툴팁으로 이전, 상태줄은 `보정된 POI N개`/`드래그 중`만 표시.
|
||||
- 동적 수치(ms·lag·현재값)는 툴팁 문자열에 그대로 보간 → 정보 손실 없음.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 25분
|
||||
**Context 사용량**: input ~140k / output ~9k tokens
|
||||
@@ -0,0 +1,30 @@
|
||||
# POI 거리필터 — 앞/옆 분리 → 단일 표시 범위(직선거리)로 통합
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 요구
|
||||
"POI 앞쪽/옆쪽"을 **"POI 표시 범위"** 하나로 통합. 드론 위치와의 **직선거리**를 기준으로
|
||||
범위 안이면 표시, 밖이면 제외.
|
||||
|
||||
## 구현
|
||||
- 비등방 필터(`fwd > MAX_FRONT || |side| > MAX_SIDE`)를 **단일 반경 필터**로 교체:
|
||||
```ts
|
||||
if ((cc.distH ?? 0) > MAX_RANGE) continue; // distH = 드론↔POI 수평 직선거리(m, 고도차 무시)
|
||||
```
|
||||
`cc.distH`는 `toCameraCoords`가 항상 채우는 수평 평면 직선거리.
|
||||
- 상태 `maxPoiFront`/`maxPoiSide` → **`maxPoiRange`** 단일화(ref/effect/precompute 인자/호출부/의존성 일괄).
|
||||
- 패널 UI: 슬라이더 2개("POI 앞쪽"/"POI 옆쪽") → **1개 "POI 범위"**(10~1000m, 툴팁: "드론 위치와의 수평 직선거리 — 범위 안만 표시").
|
||||
- 기본값(`DISPLAY_DEFAULTS`): `maxPoiFront 310 / maxPoiSide 135` → **`maxPoiRange 300`**. "기본값" 버튼·초기 useState 공유.
|
||||
- 영속화(localStorage `ghivideo:calib`): `maxPoiRange` 저장. **구버전 마이그레이션** — 저장값에 `maxPoiRange`가 없고 `maxPoiFront`만 있으면 그 값으로 복원.
|
||||
|
||||
## 참고
|
||||
- 직선거리는 수평 평면거리(distH) 사용(기존 앞/옆도 평면 분해였음 → 일관). 드론 높이(~35m)는 무시되나
|
||||
100m+ 범위에선 3D 직선거리와 거의 동일.
|
||||
- 트레이드오프: 단일 반경이라 "앞으로 멀리 + 옆으로 좁게"는 더 이상 불가(사용자 요청대로 단순화).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~165k / output ~5k tokens
|
||||
@@ -0,0 +1,39 @@
|
||||
# 드론 회전 시 중심선·궤적선이 뒤늦게 따라오는 어긋남 — 적응형 평활
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 증상
|
||||
드론이 회전하고 나면 스테이션 선(중심선)과 드론 궤적선이 **뒤늦게 따라와** 회전 구간 중간에
|
||||
영상과 어긋나 보임.
|
||||
|
||||
## 원인
|
||||
선/궤적은 RAF에서 `poseAt → smoothFrame`의 **평활된 드론 자세**로 매 프레임 투영한다.
|
||||
`smoothHalf` 기본값이 **60프레임(±2초)** 중심이동평균이라, 회전이 끝난 직후에도 평균창의
|
||||
뒷부분(trailing)에 회전 전 낮은 yaw 프레임들이 최대 ~2초간 포함됨 → **오버레이 yaw가
|
||||
실제보다 지연**되어 선이 뒤처졌다가 천천히 따라잡음. 영상은 평활 안 된 실제 자세라 어긋남.
|
||||
(라벨은 점이라 덜 띄지만 긴 선은 각도 오차가 크게 보임.)
|
||||
|
||||
## 조치 — `smoothFrame` 적응형 창
|
||||
국부 yaw 변화율로 평균창을 가변:
|
||||
```ts
|
||||
// ±k(=3) 프레임 yaw 변화율(deg/frame, 최단각)
|
||||
const rate = |Δyaw| / (2k);
|
||||
// rate=0 → 풀창 유지, 회전 클수록 축소(하한 5프레임). 게인 8: 1°/fr ≈ halfWin/9.
|
||||
effHalf = Math.max(5, Math.round(halfWin / (1 + 8 * rate)));
|
||||
```
|
||||
- **회전 중**: 창이 좁아져 실제 자세를 즉시 추종 → 지연/어긋남 제거.
|
||||
- **직선(저변화율)**: 풀창(±smoothHalf) 유지 → GPS/자세 노이즈는 그대로 억제.
|
||||
- 모든 소비처(선·궤적 `poseAt`, 라벨 precompute, 나침반/캐시)가 같은 `smoothFrame`을 쓰므로
|
||||
일괄 적용. `smooth` 슬라이더는 이제 **최댓값**(직선 기준) 의미 → 툴팁에 "회전 중 자동 축소" 명시.
|
||||
|
||||
## 참고
|
||||
- 적응형이라 `halfWin > 6`일 때만 동작(작은 값은 그대로). yaw는 raw 프레임 배열에서 산출.
|
||||
- 위치(GPS) 평활도 함께 좁아져 회전 시 궤적 코너 라운딩도 완화됨.
|
||||
- 더 민감/둔감 조절은 게인(8)·하한(5)·k(3) 상수로. 떨림이 재발하면 게인↓.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~185k / output ~5k tokens
|
||||
@@ -0,0 +1,39 @@
|
||||
# 회전 종료 후 중심선·궤적선이 한 번 튀는 현상 — 에지보존 평활로 교체
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**선행**: [2026-06-24_0957_회전시-선오버레이-지연어긋남-적응형평활.md](2026-06-24_0957_회전시-선오버레이-지연어긋남-적응형평활.md)
|
||||
|
||||
## 증상
|
||||
드론 회전이 끝난 직후 스테이션선·드론궤적선이 **한 번 튐**.
|
||||
|
||||
## 원인
|
||||
직전 작업에서 넣은 **변화율 기반 적응형 창**(`effHalf = halfWin/(1+8·rate)`)의 부작용.
|
||||
회전이 끝나 yaw 변화율이 급감하면 `effHalf`가 **한꺼번에 5~7 → 60프레임으로 재확장**됨.
|
||||
그 순간 trailing window에 회전 프레임들이 다시 포함되어 평균 yaw가 급변 → 1회 튐.
|
||||
|
||||
## 조치 — 에지(회전 경계) 보존 적응형 창
|
||||
변화율로 창 크기를 정하지 않고, **중심 yaw 와 차이가 임계 이내인 인접 프레임까지만** 확장:
|
||||
```ts
|
||||
const YAW_EDGE = 8; // deg
|
||||
const near = idx => |최단각(frames[idx].yaw - frames[i].yaw)| <= YAW_EDGE;
|
||||
let lo = i, hi = i;
|
||||
while (lo > i-halfWin && near(lo-1)) lo--; // 회전 프레임 만나면 정지
|
||||
while (hi < i+halfWin && near(hi+1)) hi++;
|
||||
```
|
||||
- 회전 구간이 직선 평균에 **섞이지 않음**(시간축 양방향 에지보존 = bilateral).
|
||||
- 회전 중: 창이 자연히 좁아 실제 자세 즉시 추종(지연 없음).
|
||||
- 회전 종료 후: 회전 프레임을 배제한 채 창이 **한 프레임씩** 부드럽게 재확장 → 튐 없음.
|
||||
- 직선: yaw 거의 일정 → 풀창(±halfWin)으로 노이즈 억제(기존 효과 유지).
|
||||
|
||||
## 튜닝
|
||||
- `YAW_EDGE`(8°): 작게 → 회전 추종↑·직선 노이즈억제↓, 크게 → 반대. GPS yaw 지터(보통 <8°)는
|
||||
창 확장을 막지 않도록 8°로 설정.
|
||||
- 단일 프레임 yaw 스파이크가 8° 초과하면 그 지점에서 창이 끊길 수 있으나, 창 경계가
|
||||
중심 이동에 따라 1프레임씩만 변하므로 **튐은 발생하지 않음**(완만).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~200k / output ~4k tokens
|
||||
@@ -0,0 +1,41 @@
|
||||
# 재생 직후 라벨이 몇 초 뒤 나타나는 지연 — 증분 게시 + 현재구간 우선 계산
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 증상
|
||||
재생을 누르고 **몇 초 뒤에야** 화면 라벨(측점/POI)이 나타남. 재생 즉시 보이길 원함.
|
||||
|
||||
## 원인
|
||||
`startLabelPrecompute`가 전 프레임 라벨을 청크(200프레임)로 사전계산하는데,
|
||||
완성된 `newMap`을 **모든 프레임 계산이 끝난 뒤에야** `labelMapRef.current`에 1회 게시.
|
||||
→ 긴 영상은 전체 사전계산(수 초)이 끝날 때까지 RAF가 빈 맵을 조회 → 라벨 미표시.
|
||||
또 항상 idx 0부터 계산해, 중간에서 재생(seek 후)하면 그 구간이 더 늦게 준비됨.
|
||||
|
||||
## 조치
|
||||
1. **현재 재생 프레임부터 순환 계산**: `startIdx = currentFrameIdxRef.current` 에서 시작해
|
||||
`(startIdx + processed) % total` 순서로 처리 → 화면에 보이는 구간을 가장 먼저 계산.
|
||||
2. **첫 청크 후 즉시 게시**(초기 로드 한정): 맵이 비어 있으면(`labelMapRef.current.size===0`)
|
||||
첫 200프레임(현재 구간)을 채우자마자 `labelMapRef.current = newMap` 게시 → 재생 즉시 라벨.
|
||||
이후 같은 Map 에 **추가만**(삭제 없음) 하므로 나머지 구간이 깜빡임 없이 보강됨.
|
||||
3. **재계산(파라미터 변경)은 기존 방식 유지**: 기존 맵이 있으면 완료 시 한 번에 교체
|
||||
(`livePublish=false`) → 슬라이더 조정 중 기존 라벨이 사라지지 않음(깜빡임 방지).
|
||||
|
||||
```ts
|
||||
const livePublish = labelMapRef.current.size === 0; // 초기 로드 여부
|
||||
// 청크 후:
|
||||
if (livePublish && !published) { labelMapRef.current = newMap; published = true; }
|
||||
// 완료 시:
|
||||
if (!livePublish) labelMapRef.current = newMap;
|
||||
```
|
||||
|
||||
## 효과
|
||||
- 초기 로드/재생: 첫 청크(≈수~수십 ms 계산, idle timeout 200ms 상한) 후 바로 라벨 표시.
|
||||
- seek 후 재생: 그 위치부터 계산되어 즉시 표시.
|
||||
- 파라미터 재계산: 종전처럼 완성 후 일괄 교체(무깜빡임).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 12분
|
||||
**Context 사용량**: input ~215k / output ~5k tokens
|
||||
@@ -0,0 +1,42 @@
|
||||
# 라벨이 영상보다 먼저 뜨는 현상 — 영상 첫 프레임 표시 후로 게이트
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/store/playerStore.ts`, `client/src/hooks/useVideoPlayer.ts`,
|
||||
`client/src/components/player/VideoPlayer.tsx`, `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1019_라벨-재생즉시표시-증분게시.md](2026-06-24_1019_라벨-재생즉시표시-증분게시.md)
|
||||
|
||||
## 증상
|
||||
재생 시 라벨이 **영상보다 살짝 먼저** 표시됨. (직전 작업으로 라벨이 즉시 게시되면서,
|
||||
영상 첫 프레임이 디코드·표시되기 전 검은 화면 위에 라벨이 먼저 보임.)
|
||||
|
||||
## 원인
|
||||
StationOverlay RAF는 데이터/캐시만 준비되면 영상 표시 여부와 무관하게 캔버스에 그림.
|
||||
영상 `<video>`의 첫 프레임 표시 시점과 동기화하는 신호가 없었음.
|
||||
|
||||
## 조치 — "영상 첫 프레임 표시 가능" 신호로 오버레이 게이트
|
||||
1. **playerStore**: `videoReady: boolean`(기본 false) + `setVideoReady` 추가.
|
||||
`setSource` 시 false 로 리셋(영상 교체 때 다시 게이트).
|
||||
2. **useVideoPlayer**: Video.js 이벤트 연결
|
||||
- `loadstart` → `setVideoReady(false)` (새 소스 로드 시작)
|
||||
- `loadeddata` → `setVideoReady(true)` (첫 프레임 표시 가능)
|
||||
- `playing` → `setVideoReady(true)` (폴백)
|
||||
3. **VideoPlayer**: store `videoReady` 를 StationOverlay 에 prop 전달.
|
||||
4. **StationOverlay**: `videoReady` prop(기본 true=하위호환) → `videoReadyRef`. RAF draw 에서
|
||||
`clearRect` 직후 **`if (!videoReadyRef.current) return;`** → 첫 프레임 전엔 라벨/선/나침반 미표시.
|
||||
|
||||
```ts
|
||||
// StationOverlay draw()
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
if (!videoReadyRef.current) return; // 영상 첫 프레임 전 오버레이 차단
|
||||
```
|
||||
|
||||
## 효과
|
||||
- 영상 첫 프레임이 표시된 뒤에야 오버레이가 그려짐 → 라벨이 먼저 뜨는 깜빡임 제거.
|
||||
- 영상 교체(폴더 재로드) 시 `loadstart` 로 다시 게이트되어 새 영상 첫 프레임까지 대기.
|
||||
- prop 기본값 true 라 StationOverlay 단독 사용처(있다면)는 기존대로 동작.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 12분
|
||||
**Context 사용량**: input ~235k / output ~5k tokens
|
||||
@@ -0,0 +1,33 @@
|
||||
# 히스토리 파일명에 시간(HHMM) 추가 + POI 라벨 원근 현상 분석
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `CLAUDE.md`, `docs/history/*` (파일명 변경)
|
||||
|
||||
## 1) 히스토리 파일명 규칙: 날짜 → 날짜+시간
|
||||
요구: 각 작업 파일명에 날짜와 **시간**을 함께 기록.
|
||||
- 규칙 변경: `docs/history/YYYY-MM-DD_{작업명}.md` → **`YYYY-MM-DD_HHMM_{작업명}.md`** (CLAUDE.md 갱신).
|
||||
- 오늘 작성한 7개 파일을 작성 시각(mtime) 기준으로 일괄 리네임(0926~1026), 파일 간 상호 링크(선행/관련) 갱신.
|
||||
|
||||
## 2) "멀리선 뒤로 밀리고 가까이선 빨리 아래로 빠짐" — 원근 현상(버그 아님)
|
||||
사용자 관찰: 라벨이 멀 땐 살짝씩 밀리고, 바로 밑을 지날 땐 드론 이동보다 빨리 아래로 빠져 사라짐.
|
||||
|
||||
### 결론(정정)
|
||||
이는 **정상적인 원근 투영 효과**다. 수직간격 Δh 일정·드론고도 일정이어도 화면 각속도는
|
||||
`dβ/dt = v·Δh/(D²+Δh²)` — 멀리(D 큼) 느리고, 천정점(D→0) 부근 `v/Δh`로 최대. 즉 머리 위를
|
||||
지나가는 고정 지물은 원래 그렇게 보인다. (앞서 "far/near 부호 뒤집힘" 진단은 과했음 — 정정.)
|
||||
|
||||
### 진짜 잔차의 판별
|
||||
라벨이 **영상 속 실제 대상물에 붙어 있으면 정상**(고치면 오히려 틀림). 대상에서 **벗어나면**
|
||||
'드론 24m 아래 상수' 가정이 그 지점 실제 간격과 달라서 생기는 잔차:
|
||||
- 가까이서 라벨이 대상보다 먼저 빠짐 → 실제 간격 < 24(과대) → `이격거리`↓ 또는 지형표고 사용.
|
||||
- 멀리서 라벨이 대상보다 떠 보임 → 24 과소 → 반대 조정.
|
||||
- 지형표고(측점 z/DEM)는 구간별로 변하므로 상수 가정의 잔차를 근본 제거(POI투영오차 문서 §18·§19).
|
||||
|
||||
### 조치
|
||||
- 사용자에게 "라벨이 실제 지물에 붙는지/벗어나는지" 확인 요청. 코드 변경은 보류(현상 정상).
|
||||
|
||||
## 검증
|
||||
- 코드 변경 없음(문서/파일명만). 빌드 불요.
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~255k / output ~6k tokens
|
||||
@@ -0,0 +1,31 @@
|
||||
# 라벨이 멀리선 뒤로/가까이선 먼저 — 세로 화각(focal·sensorH) 보정 진단
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: 없음 (진단/가이드)
|
||||
|
||||
## 증상 (사용자 확인)
|
||||
라벨이 지물에 정확히 붙지 않음. **멀리 있을 때 영상 위치보다 뒤(위)로 조금씩 밀리고,
|
||||
가까이 있을 때 영상보다 먼저(아래로) 지나감.** → 화면 중심 기준 위/아래가 **반대 방향**으로 벌어짐.
|
||||
|
||||
## 진단: z/이격거리/pitch 아님 → focal·sensorH(세로 FOV)
|
||||
- z·이격거리 오차: 위아래가 **같은 방향**으로 밀림(한쪽으로). 교차 안 생김 → 배제.
|
||||
- pitch 오차: 전체가 같은 화면방향으로 시프트. 교차 안 생김 → 배제.
|
||||
- **focal/sensorH(세로 배율) 오차**: `py = 0.5 + cy0 + (Yc/Zc)·(f/sensorH)`.
|
||||
카메라 −30° 틸트 → 먼 POI는 화면 중심 위, 가까운 POI는 중심 아래.
|
||||
`f/sensorH`가 과대 → 중심에서 멀수록 과확대 → 위(먼 것)는 더 위로, 아래(가까운 것)는 더 아래로
|
||||
= **멀리 뒤로 / 가까이 먼저**. 사용자 증상과 정확히 일치.
|
||||
|
||||
## 가이드 (코드 변경 없음 — 패널 튜닝)
|
||||
카메라 파라미터 패널에서 POI가 멀→근 통과하는 구간을 보며:
|
||||
- `f`(초점)를 낮추거나 `sen H`를 키워 세로 배율↓ → 위/아래가 지물로 수렴.
|
||||
- 반대로 안쪽으로 과수렴하면 `f`↑.
|
||||
- 값은 `ghivideo:calib:<baseName>` 로 자동 저장.
|
||||
|
||||
현재 기본 `f=24 / sen W=36 / sen H=20.25`(16:9 가정). 실제 4K 세로 화각이 다르면 본 증상 발생.
|
||||
사용자가 패널에서 맞춘 `f`/`sen H` 값을 알려주면 **기본값(DEFAULT_CAMERA_PARAMS)으로 반영** 예정.
|
||||
|
||||
## 다음 단계
|
||||
- 사용자 튜닝값 수령 시 `geoProjection.ts` 기본 focal/sensorH 갱신.
|
||||
|
||||
**소요 시간**: 약 8분
|
||||
**Context 사용량**: input ~275k / output ~5k tokens
|
||||
@@ -0,0 +1,45 @@
|
||||
# 오버레이가 영상과 어긋남 — object-fit:cover 크롭 정렬 (영상 해상도 사용)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/store/playerStore.ts`, `client/src/hooks/useVideoPlayer.ts`,
|
||||
`client/src/components/player/VideoPlayer.tsx`, `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1051_라벨-멀리뒤로-가까이먼저-FOV진단.md](2026-06-24_1051_라벨-멀리뒤로-가까이먼저-FOV진단.md)
|
||||
|
||||
## 발단
|
||||
"영상이 재생되는 브라우저의 해상도를 알 수 있나?" → 영상 표시 방식 점검 중 근본 원인 발견.
|
||||
|
||||
## 원인 (핵심)
|
||||
영상은 `index.css`에서 **`object-fit: cover`** 로 표시 → 컨테이너 비율 ≠ 영상 비율(3840×2160=16:9)
|
||||
이면 영상이 **확대·크롭**되어 채워짐. 그런데 **오버레이 캔버스는 컨테이너 전체**에 정규좌표(0~1)를
|
||||
매핑하고 있었음. 즉 영상은 크롭된 영역에 보이는데 오버레이는 컨테이너 기준 → **가로/세로 배율·오프셋
|
||||
불일치**. 이것이 라벨/선이 영상과 어긋나(멀리·가까이 다르게) 보이던 핵심 원인.
|
||||
|
||||
## 조치 — 오버레이를 영상 cover 사각형에 정렬 (영상 원본 해상도 사용)
|
||||
1. **영상 해상도 확보**: playerStore `videoWidth/videoHeight` + `setVideoSize`. useVideoPlayer 에서
|
||||
`loadedmetadata`/`loadeddata` 시 `player.videoWidth()/videoHeight()` 저장. VideoPlayer→StationOverlay prop 전달.
|
||||
2. **cover 변환**: RAF draw 에서 영상 해상도로 cover 사각형 계산
|
||||
```ts
|
||||
const s = Math.max(W/vW, H/vH); // cover 배율(넘침=크롭)
|
||||
dispW=vW*s; dispH=vH*s; offX=(W-dispW)/2; offY=(H-dispH)/2;
|
||||
const vx = nx => offX + nx*dispW; // 정규(영상프레임) → 화면 px
|
||||
const vy = ny => offY + ny*dispH;
|
||||
```
|
||||
중심선/드론궤적/측점·POI 라벨/드래그 피드백의 좌표를 모두 `*W,*H` → `vx(),vy()` 로 교체.
|
||||
(나침반 HUD 는 화면 코너 고정이라 컨테이너 W/H 유지.)
|
||||
3. **포인터 역변환**: `clientToVideoNorm()` 추가 — 클릭 client px → 영상프레임 정규좌표(cover 역변환).
|
||||
POI 드래그 hit-test/이동이 크롭 상태에서도 정확히 일치.
|
||||
4. **해상도 표시**: 카메라 파라미터 패널 진단줄에 `영상 W×H (비율 · cover 정렬)` 노출(사용자 확인용).
|
||||
|
||||
## 효과
|
||||
- 컨테이너 비율이 16:9가 아니어도 오버레이가 **크롭된 영상과 픽셀 단위로 정렬**됨.
|
||||
- 영상 해상도 미확정(vW/vH=0) 시 컨테이너 기준 폴백(기존 동작).
|
||||
- 앞서 'FOV 의심'(§1051) 잔차의 상당부분이 실은 이 cover 불일치였을 가능성 큼 → 적용 후 재확인 필요.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
## 다음 단계
|
||||
- 적용 후에도 멀·근 잔차가 남으면 그때 focal/sensorH(세로 FOV) 미세조정(§1051).
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~300k / output ~9k tokens
|
||||
@@ -0,0 +1,47 @@
|
||||
# 1-클릭 FOV(초점) 보정 — POI를 실제 위치로 드래그해 focal 역산
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1051_라벨-멀리뒤로-가까이먼저-FOV진단.md](2026-06-24_1051_라벨-멀리뒤로-가까이먼저-FOV진단.md),
|
||||
[2026-06-24_1106_오버레이-objectfit-cover정렬.md](2026-06-24_1106_오버레이-objectfit-cover정렬.md)
|
||||
|
||||
## 배경
|
||||
cover 정렬 후에도 "멀리 뒤로 / 가까이 먼저" 잔차 동일 → 투영 수식 버그 아님(검증). 남은 원인은
|
||||
**FOV(초점 f) 미스매치**(중심 기준 배율 오차 → 위/아래 반대로 벌어짐). 손으로 f 맞추는 대신
|
||||
**1-클릭 보정** 추가.
|
||||
|
||||
## 기능
|
||||
편집 모드와 별개의 **🎯 FOV(초점) 보정** 모드. POI 라벨 하나를 영상 속 **실제 위치로 드래그**하면,
|
||||
그 대응점으로 focal 을 역산해 즉시 적용 → 전 거리(멀·근) 정렬.
|
||||
|
||||
### 역산 수식
|
||||
```
|
||||
px = 0.5 + cx0 + (Xc/Zc)·(f/sW), py = 0.5 + cy0 + (Yc/Zc)·(f/sH)
|
||||
a=Xc/Zc, b=Yc/Zc, u=px*-0.5-cx0, v=py*-0.5-cy0
|
||||
f = ((a/sW)·u + (b/sH)·v) / ((a/sW)² + (b/sH)²) // 두 축 공통 f 의 최소제곱
|
||||
```
|
||||
- f 는 가로·세로 공통 → 한 점이면 화각 배율 결정(센서·픽셀 정방, 16:9 가정).
|
||||
- POI 카메라좌표는 렌더와 동일한 poiZ(드론높이 모드면 드론−이격거리, 아니면 선택 z)로 계산.
|
||||
- `setParam('focalLen', clamp(10..100))` → 카메라 파라미터 패널에 반영·데이터셋별 영속화.
|
||||
|
||||
## 구현
|
||||
- 상태 `fovMode`(+ref), `poiDroneHeightRef`/`droneHeightDropRef`(역산용).
|
||||
- `onPoiPointerDown`: editMode **또는** fovMode 에서 POI 선택. 캔버스 pointer-events 도 동일 조건.
|
||||
- `onPoiPointerUp`: fovMode 면 focal 역산 분기(위 수식), 아니면 기존 위치(lat/lon) 보정.
|
||||
- 드래그 피드백 마커: editMode/fovMode 모두 표시.
|
||||
- UI: "🎯 FOV(초점) 보정" 토글(편집 모드와 상호 배타) + 안내 InfoTip + 현재 f 표시.
|
||||
|
||||
## 사용법
|
||||
1. 화면표시 옵션 → **🎯 FOV(초점) 보정** 클릭.
|
||||
2. 멀거나 가까운 POI 하나를 **영상 속 실제 위치로 드래그**(일시정지 권장).
|
||||
3. f 자동보정 → 멀·근 라벨이 함께 지물에 정렬. 값은 자동 저장.
|
||||
|
||||
## 한계
|
||||
- z(표고)가 크게 틀리면 세로 대응에 z 오차가 섞일 수 있음 → 먼저 DEM/표고 보정 후 FOV 보정 권장.
|
||||
- 한 점 기준이라, 가능하면 화면 중심에서 충분히 떨어진(위/아래) POI 로 보정하면 안정적.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 18분
|
||||
**Context 사용량**: input ~330k / output ~9k tokens
|
||||
@@ -0,0 +1,35 @@
|
||||
# FOV 보정을 세로화각(sensorH) 전용으로 전환 — 가로(중심선) 안 틀어지게
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1117_1클릭-FOV초점-보정.md](2026-06-24_1117_1클릭-FOV초점-보정.md)
|
||||
|
||||
## 결정적 단서 (사용자)
|
||||
- 카메라 패널에 **영상 해상도 정상 표시** → cover 정렬 동작 중(가로/세로 매핑 OK).
|
||||
- 현재 **f = 24.0mm**.
|
||||
- **f 보정 시 빨간 중심선(스테이션선)이 좌우로 벗어남.**
|
||||
|
||||
## 해석
|
||||
중심선이 가로로 잘 맞고 있다 = 가로(f/sW)는 정확. f 는 가로·세로 공통 배율이라 세로를
|
||||
맞추려고 f 를 바꾸면 **멀쩡한 가로가 틀어짐**. 따라서 잔차는 **세로 화각만 어긋남 → sensorH**.
|
||||
|
||||
## 조치 — FOV 보정을 sensorH 역산으로 교체
|
||||
드롭점의 세로(py)만 맞추고 가로(f/sW)는 그대로:
|
||||
```
|
||||
py = 0.5 + cy0 + (Yc/Zc)·(f/sH) → sH = (Yc/Zc)·f / (py* − 0.5 − cy0)
|
||||
```
|
||||
- f·sensorW(가로) 미변경 → 중심선 좌우 위치 불변.
|
||||
- sensorH 만 갱신(clamp 6~36, 슬라이더 범위) → 상하 벌어짐만 교정.
|
||||
- 세로 중심 근처(|py−0.5−cy0|<0.03)면 역산 불안정 → 무시 + 안내(위/아래 떨어진 POI 사용).
|
||||
- UI: "🎯 세로화각 보정", 현재 senH 표시, 안내 InfoTip 갱신.
|
||||
|
||||
## 사용법
|
||||
1. 🎯 **세로화각 보정** 모드 ON.
|
||||
2. **세로 중심에서 위(먼 POI)/아래(가까운 POI)로 충분히 떨어진** POI 하나를 영상 속 실제 위치로 드래그.
|
||||
3. sensorH 자동보정 → 상하 정렬, 빨간 중심선 좌우는 그대로. 데이터셋별 저장.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 8분
|
||||
**Context 사용량**: input ~360k / output ~5k tokens
|
||||
@@ -0,0 +1,35 @@
|
||||
# POI 높이를 지면고도 기준으로 — 고정 이격거리 → 기본 OFF (드론 고도 변동 대응)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 요구
|
||||
드론 고도가 오르내리는데, POI 이격거리를 고정값(24m)으로 두지 말고
|
||||
**드론고도 − 지면고도**로 POI 높이를 표시하고 싶다.
|
||||
|
||||
## 해석
|
||||
투영 수직간격 `gap = 드론고도(alt) − (POI 지면고도 z + 지오이드)`.
|
||||
- 고정 이격거리(ON): `gap=24m` 상수 → POI가 드론에 매달려 고도 변동 시 같이 떠/내려감(지물 이탈).
|
||||
- **지면고도 기준(OFF)**: POI를 지면고도 z 에 고정 → `gap`이 매 프레임 `드론고도−지면고도`로
|
||||
자동 계산 → 드론이 오르내려도 POI가 고정 지상점에 그대로 붙음. (= 사용자가 원한 동작)
|
||||
|
||||
즉 별도 신규 모드 불필요 — 기존 `poiDroneHeight=false`(지면고도 모드)가 정확히 이 계산.
|
||||
|
||||
## 조치
|
||||
- `DISPLAY_DEFAULTS.poiDroneHeight` **true → false** (지면고도 기준을 기본값으로).
|
||||
드론 고도 변동 환경에서 고정 이격거리는 부정확하므로 기본 off.
|
||||
- `poiDroneHeightRef` 초기값도 동일 상수 참조 → 자동 반영. 토글은 유지(원하면 고정모드로 복귀).
|
||||
- 지면고도 z 출처(false 모드): POI override(DEM/드래그) → 없으면 `nearestCL.z`(최근접 측점 표고).
|
||||
· 선로변 POI·교량(신대천교 등)은 측점 표고가 곧 지면/교량고도라 양호.
|
||||
· 선로에서 먼 POI는 **🌐 DEM 표고 자동적용**으로 실제 지면고도 채우는 걸 권장.
|
||||
|
||||
## 사용
|
||||
- 기본값이 지면고도 기준으로 시작. 멀리·가까이 모두 고정 지상점에 붙어 자연스럽게 추종.
|
||||
- 정밀이 필요하면 편집 모드 → 🌐 DEM 표고 자동적용(또는 표고 슬라이더/드래그).
|
||||
- 고정 이격거리로 되돌리려면 "드론 높이와 POI 이격거리" 토글 ON.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 7분
|
||||
**Context 사용량**: input ~385k / output ~4k tokens
|
||||
@@ -0,0 +1,40 @@
|
||||
# 라벨 클릭 → 속성 팝업 (라벨 정보 모드)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 요구
|
||||
영상의 라벨을 클릭하면 속성 팝업이 열리게.
|
||||
|
||||
## 구현 — "ℹ️ 라벨 정보" 모드
|
||||
편집/세로화각 보정과 상호 배타인 토글 모드. 켜면 캔버스가 클릭을 받고, 라벨을 누르면 속성 팝업 표시.
|
||||
|
||||
- 상태 `infoMode`(+ref) + `infoPopup`(클릭한 라벨 속성·화면좌표). 모드 끄면 팝업 자동 닫힘.
|
||||
- `onPoiPointerDown` 에 info 분기: 클릭점을 영상정규좌표로 변환(`clientToVideoNorm`) 후
|
||||
표시 중인 POI(`displayedPoiRef`)·측점(`displayedStRef`) 중 최근접(반경 ~0.04) 히트테스트.
|
||||
- 데이터 조회: POI=`allPoisRef`+`allStructuresRef`, 측점=`allGeoStationsRef`.
|
||||
- 표고 z = override(DEM/드래그) → 없으면 최근접 측점표고(`nearestCL`) → obj.z.
|
||||
- 드론 수평거리 = `toCameraCoords(...).distH`.
|
||||
- 빈 곳 클릭 시 팝업 닫힘.
|
||||
- 캔버스 `pointer-events`/커서: editMode·fovMode(이동) 또는 **infoMode(pointer)** 일 때 활성.
|
||||
- 팝업 UI: 제목(이모지)·종류·카테고리·위도·경도·표고(보정 표시)·드론 거리. ✕로 닫기.
|
||||
위치는 클릭한 라벨 화면좌표 옆(화면 밖으로 안 나가게 클램프).
|
||||
|
||||
## 표시 속성
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| 제목 | cleanTitle (괄호 상/하 제거) |
|
||||
| 종류·카테고리 | POI/측점 · 교량/터널/구교/지장물/측점 등 |
|
||||
| 위도·경도 | 6자리 |
|
||||
| 표고 | groundZ(m), override면 "(보정)" |
|
||||
| 거리 | 드론 기준 수평거리(distH, m) |
|
||||
|
||||
## 참고
|
||||
- 라벨이 보일 때(측점선 표시 ON)만 클릭 가능(숨김 상태면 히트 대상 없음).
|
||||
- 팝업은 클릭 시점 위치에 고정(재생 중 라벨은 움직여도 팝업은 정보창으로 유지).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 14분
|
||||
**Context 사용량**: input ~410k / output ~7k tokens
|
||||
@@ -0,0 +1,38 @@
|
||||
# 라벨 정보 — 모드 버튼 제거, 호버 시 손커서·항상 클릭 가능
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1150_라벨정보-클릭팝업.md](2026-06-24_1150_라벨정보-클릭팝업.md)
|
||||
|
||||
## 요구
|
||||
- 라벨 위 → 손모양 커서, 라벨 밖 → 화살표 커서.
|
||||
- "라벨 정보 모드" 버튼 클릭 없이 항상 동작.
|
||||
|
||||
## 핵심 — 라벨 위에서만 캔버스가 입력을 받게 동적 토글
|
||||
오버레이 캔버스가 항상 입력을 먹으면 영상 클릭(재생/일시정지)이 막힌다. 그래서:
|
||||
- 부모(플레이어 컨테이너)에 `mousemove` 리스너 → 매 이동마다 라벨 히트테스트.
|
||||
- **라벨 위**: `canvas.pointerEvents='auto'` → 캔버스가 받음 → 커서 `pointer`(손), 클릭 시 팝업.
|
||||
- **라벨 밖**: `canvas.pointerEvents='none'` → 영상이 받음 → 커서 화살표, 클릭이 영상으로 통과.
|
||||
- 부모 `pointerdown`: target≠canvas(라벨 밖)면 팝업 닫기. 라벨 클릭(target=canvas)은 유지.
|
||||
- 팝업 자신은 `onPointerDown stopPropagation` → 팝업 내부 클릭으로 안 닫힘.
|
||||
- 편집/세로화각 보정 모드는 종전대로 캔버스 항상 입력(드래그) + 커서 `move`(이 모드 진입 시 팝업 닫음).
|
||||
|
||||
## 변경
|
||||
- `infoMode` 상태/토글 버튼 **제거** → 라벨 정보 항상 활성. 패널엔 안내문만.
|
||||
- `hitTestLabel(nx,ny)` 헬퍼 추출(POI+측점 최근접) — 클릭/호버 공용.
|
||||
- `onPoiPointerDown` 재구성: 편집/보정 모드면 드래그, 아니면 라벨 팝업.
|
||||
- 캔버스 className 에서 pointer-events/cursor 제거 → 새 useEffect 가 모드별로 `style` 직접 관리.
|
||||
- 편집/FOV 버튼의 상호배타에서 infoMode 제거.
|
||||
|
||||
## 동작 요약
|
||||
| 상황 | 커서 | 클릭 |
|
||||
|---|---|---|
|
||||
| 라벨 위(기본 모드) | 손(pointer) | 속성 팝업 |
|
||||
| 라벨 밖(기본 모드) | 화살표 | 영상 재생/일시정지 |
|
||||
| 편집/세로화각 모드 | 이동(move) | POI 드래그 |
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 12분
|
||||
**Context 사용량**: input ~440k / output ~7k tokens
|
||||
@@ -0,0 +1,47 @@
|
||||
# 라벨 팝업 — 다중 유지 + 라벨 추적 이동 + 빈클릭(팝업닫기/재생토글)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`, `client/src/components/player/VideoPlayer.tsx`
|
||||
**관련**: [2026-06-24_1159_라벨정보-항상활성-호버커서.md](2026-06-24_1159_라벨정보-항상활성-호버커서.md)
|
||||
|
||||
## 요구 (4가지)
|
||||
1. 라벨 클릭 시 기존 팝업 닫지 말고 계속 표시(다중).
|
||||
2. 팝업 있을 때 화면 클릭 → 팝업 닫힘.
|
||||
3. 팝업 없을 때 화면 클릭 → 재생/정지.
|
||||
4. 팝업이 라벨처럼 영상 재생에 따라 이동.
|
||||
|
||||
## 구현
|
||||
### 다중 팝업 + 라벨 추적(1·4)
|
||||
- `infoPopup`(단일) → `infoPopups: InfoPopup[]`(id=`${kind}:${title}`). 라벨 클릭 시 중복 id 아니면 append.
|
||||
- 각 팝업 DOM 에 ref 등록(`popupElsRef`). **RAF draw 에서** 해당 라벨의 현재 표시좌표(`displayedPoiRef`/
|
||||
`displayedStRef`)로 `el.style.left/top` 매 프레임 갱신 → 재생 중 라벨 따라 이동. 라벨이 화면에서
|
||||
사라지면 팝업 `visibility:hidden`, 재등장 시 표시.
|
||||
- ✕ 로 개별 닫기.
|
||||
|
||||
### 클릭 분기(2·3)
|
||||
- Video.js `controls:false` → 영상이 클릭을 처리 안 함. **캔버스가 모든 클릭을 받아** 직접 분기:
|
||||
- 라벨 클릭 → 팝업 추가.
|
||||
- 빈 곳 클릭 → 팝업 있으면 모두 닫기(`setInfoPopups([])`), 없으면 `onTogglePlay()`(재생/정지).
|
||||
- `onTogglePlay` prop 신설 → VideoPlayer 의 `handleTogglePlay` 연결. `hasPopupRef`로 팝업 유무 판별.
|
||||
- 캔버스 커서: 라벨 위 `pointer`(손) / 그 외 `default`(화살표) / 편집·보정 `move`.
|
||||
|
||||
### 입력 처리 단순화
|
||||
- 캔버스 `pointer-events` 를 항상 `auto`(편집/보정/기본 모두)로 통일 — 이전의 '라벨 위에서만 auto'
|
||||
토글 제거. 영상은 어차피 클릭 처리 안 하므로 캔버스가 전담. 패널(z-30)·스테이션바(z-20 후순위)는
|
||||
캔버스 위라 각자 클릭 정상.
|
||||
|
||||
## 동작표
|
||||
| 클릭 위치 | 팝업 유무 | 결과 |
|
||||
|---|---|---|
|
||||
| 라벨 | — | 팝업 추가(기존 유지) |
|
||||
| 빈 곳 | 있음 | 팝업 전체 닫기(재생 토글 안 함) |
|
||||
| 빈 곳 | 없음 | 재생/정지 토글 |
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
## 참고
|
||||
- 팝업의 '거리' 값은 클릭 시점 고정(위치만 추적). 필요 시 RAF 에서 거리도 실시간 갱신 가능.
|
||||
|
||||
**소요 시간**: 약 20분
|
||||
**Context 사용량**: input ~470k / output ~9k tokens
|
||||
@@ -0,0 +1,28 @@
|
||||
# 폴더 선택 불가 회귀 수정 — 영상 준비 전 캔버스 입력 비활성
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**원인 도입**: [2026-06-24_1212_라벨팝업-다중-추적-클릭동작.md](2026-06-24_1212_라벨팝업-다중-추적-클릭동작.md)
|
||||
|
||||
## 증상
|
||||
폴더 선택이 안 되어 시작 불가.
|
||||
|
||||
## 원인 (회귀)
|
||||
직전 작업에서 오버레이 캔버스를 **항상 `pointer-events:auto`** 로 변경(영상이 클릭을 처리 안 해
|
||||
캔버스가 전담하도록). 그런데 영상이 없을 때 표시되는 **폴더 선택/드롭 안내 UI**(z-auto)를
|
||||
캔버스(z-20)가 덮어 클릭을 가로채서, 폴더 선택 버튼이 안 눌림.
|
||||
|
||||
## 조치
|
||||
캔버스 입력 토글 effect에 **영상 준비 게이트** 추가:
|
||||
```ts
|
||||
if (!videoReady) { canvas.style.pointerEvents = 'none'; return; } // 폴더 선택/드롭 통과
|
||||
```
|
||||
- 영상 없음/로딩 중(`videoReady=false`) → 캔버스 입력 off → 하위 폴더 선택 UI 정상 클릭.
|
||||
- 영상 준비 후(`videoReady=true`) → 캔버스 auto → 라벨 팝업/빈클릭 재생토글 등 정상.
|
||||
- effect 의존성에 `videoReady` 추가.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 4분
|
||||
**Context 사용량**: input ~490k / output ~3k tokens
|
||||
@@ -0,0 +1,29 @@
|
||||
# 팝업을 라벨 아래로 + 라벨 글자도 클릭 가능(바운딩박스 히트테스트)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1212_라벨팝업-다중-추적-클릭동작.md](2026-06-24_1212_라벨팝업-다중-추적-클릭동작.md)
|
||||
|
||||
## 요구
|
||||
1. 팝업을 POI 라벨 **아래**에 위치.
|
||||
2. POI **아이콘뿐 아니라 글자**도 클릭으로 선택 가능.
|
||||
|
||||
## 구현
|
||||
### 글자 클릭(바운딩박스 히트테스트)
|
||||
기존 히트테스트는 마커 점 기준 반경(0.04 정규)이라 글자(마커 오른쪽 텍스트)는 안 잡혔음.
|
||||
- RAF draw 에서 라벨을 그릴 때 **아이콘+글자 영역의 화면 px 박스**를 수집(`ctx.measureText`로 글자폭):
|
||||
· 측점: `x0=마커−8 … x1=글자끝+2`, `y=중심±12`.
|
||||
· POI: `x0=십자마커−r … x1=글자끝+2`, `y=중심±13`.
|
||||
- `labelHitRef`(프레임마다 갱신)에 박스 배열 저장. 라벨 없으면 빈 배열.
|
||||
- `hitTestLabel(nx,ny)`: 정규좌표를 cover px 로 변환 후 박스 포함 검사(나중에 그린 라벨 우선).
|
||||
→ 클릭/호버 모두 아이콘·글자 어디를 눌러도 선택. 호버 손모양 커서도 글자 위에서 동작.
|
||||
|
||||
### 팝업 라벨 아래 배치
|
||||
- 위치 공식 `left=sx−10, top=sy+16`(마커 중심 아래). 초기 렌더·RAF 추적 모두 동일.
|
||||
(이전 `sx+14, sy−10`(우측) → 라벨 아래로 변경.)
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~510k / output ~5k tokens
|
||||
@@ -0,0 +1,34 @@
|
||||
# 팝업이 라벨보다 늦게 사라지고, 닫힘 후 첫 클릭이 무시되던 문제
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1220_팝업-라벨아래-글자클릭.md](2026-06-24_1220_팝업-라벨아래-글자클릭.md)
|
||||
|
||||
## 증상
|
||||
1. 라벨이 화면에서 사라졌는데 팝업이 ~1초 더 보이다가 사라짐.
|
||||
2. 팝업이 (라벨 따라) 사라진 뒤, 화면 첫 클릭은 영상이 안 멈추고 두 번째 클릭에 멈춤.
|
||||
|
||||
## 원인 (동일 뿌리)
|
||||
`object-fit:cover` 로 영상이 컨테이너 밖까지 확장(크롭)되므로, 라벨이 **보이는 컨테이너에서
|
||||
잘려 사라져도 영상-정규좌표상으론 아직 존재**(pyRaw<1.02). 그래서:
|
||||
- 팝업이 화면 가장자리에 클램프된 채 라벨이 영상 끝(1.02)에 갈 때까지 남음(~1초).
|
||||
- 그 팝업이 `infoPopups` state 에 살아 있어 `hasPopupRef=true` → 빈 곳 첫 클릭이 그 '유령 팝업'을
|
||||
닫는 데 쓰이고(토글 안 함), 두 번째 클릭에야 재생 토글.
|
||||
|
||||
## 조치 — 라벨이 컨테이너를 벗어나면 팝업 state 에서 제거
|
||||
RAF 팝업 위치 갱신에서, 라벨이 없거나 마커 화면좌표(sx,sy)가 **컨테이너 밖**(±16px)이면 '사라짐':
|
||||
```ts
|
||||
const off = !disp || sx < -16 || sx > W+16 || sy < -16 || sy > H+16;
|
||||
if (off) { miss++; if (miss>=4) toRemove.push(id); else hide; return; }
|
||||
...
|
||||
if (toRemove.length) setInfoPopups(prev => prev.filter(p => !toRemove.includes(p.id)));
|
||||
```
|
||||
- 4프레임(~0.07s) 연속 밖이면 **state 에서 제거**(라벨과 거의 동시에 사라짐). 짧은 깜빡임 유예.
|
||||
- state 에서 빠지므로 `hasPopupRef` 즉시 false → 다음 빈 클릭이 바로 재생 토글(2번 문제 해결).
|
||||
- `popupMissRef`(id별 연속 밖 카운터)로 일시적 갭에 의한 조기 제거 방지.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 8분
|
||||
**Context 사용량**: input ~535k / output ~4k tokens
|
||||
@@ -0,0 +1,36 @@
|
||||
# POI/측점 라벨 이동 부드럽게 — 라인처럼 매 프레임 연속 포즈로 직접 투영
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 증상
|
||||
재생 중 드론 카메라가 움직일 때 POI/측점 라벨 이동이 부드럽지 않음(라인은 부드러움).
|
||||
|
||||
## 원인
|
||||
- **라인(중심선·궤적)**: RAF 가 매 프레임 `poseAt(estFrame)`(연속 보간 포즈)로 직접 투영 → 부드러움.
|
||||
- **라벨**: precompute 가 **정수 프레임별 화면좌표**를 저장하고, RAF 는 baseFrame↔baseFrame+1
|
||||
사이를 선형 보간 + EMA. 프레임키가 estFrame 과 안 맞으면(밀도/오프셋) 폴백(frac=0, 보간 없음)
|
||||
→ 동기 프레임 단위(거친 timeupdate)로 점프 → 덜 부드러움.
|
||||
|
||||
## 조치 — 라벨도 라인과 동일하게 연속 포즈로 직접 투영
|
||||
precompute 의 역할을 **'가시성/겹침 결정'만** 으로 한정하고, 좌표는 RAF 가 매 프레임 재투영.
|
||||
- `LabelCache` 저장 형식: 화면좌표(sx,sy)/(x,y) → **월드좌표**.
|
||||
· 측점: `{title, lat, lon, z}` (선로 스냅 좌표).
|
||||
· POI: `{title, category, lat, lon, gz}` (gz=지면고도; override/최근접 선로표고).
|
||||
- precompute: 기존대로 거리·겹침·클립 판정(화면좌표는 판정용 임시), 통과분만 월드좌표로 저장.
|
||||
- RAF: `dronePose = poseAt(estFrame)` 한 번 구해 라인·라벨 공통 사용.
|
||||
· 측점: `toCameraCoords(dronePose, lat, lon, z)` → `pixelFromCamera` → 화면좌표.
|
||||
· POI: `pz = 드론높이모드 ? dronePose.alt−지오이드−이격 : gz` → 동일 투영.
|
||||
· 기존 `smoothStep`(이상치 거부+EMA) 유지 → emaAlpha 슬라이더로 추가 평활 가능(기본 1.0=통과).
|
||||
- 정수프레임 보간/labelsB/title 짝짓기/프레임키 의존 제거.
|
||||
|
||||
## 효과
|
||||
- 라벨이 라인과 동일한 연속 포즈로 움직여 **부드럽게 이동**(드론 밀도/프레임키 무관).
|
||||
- 성능: RAF 는 가시집합(소수)만 투영(겹침/최근접은 precompute) → 가벼움.
|
||||
- 가시집합 멤버십만 baseFrame 기준(최대 ~1프레임 지연)·위치는 연속 → 체감 영향 없음.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 18분
|
||||
**Context 사용량**: input ~580k / output ~9k tokens
|
||||
@@ -0,0 +1,33 @@
|
||||
# 출입문(출입문번호)을 영상에 POI처럼 표출
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/utils/geoData.ts`, `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 배경
|
||||
QGIS(qgs)·gpkg 의 **출입문(출입문번호)** 레이어를 영상에 표시하고 싶다는 요청. 데이터는
|
||||
`building/05)출입문번호.csv` 에 존재(기존엔 기본 표시 제외). D:\ 데이터 직접 확인:
|
||||
컬럼 `본부,선별,역구간,latitude,longitude,시설팀,출입문번호,...,z,측점`.
|
||||
|
||||
## 구현
|
||||
- **`parseAccessDoors(files)`** 추가(`geoData.ts`): `building/05)출입문번호.csv`(UTF-8 BOM) →
|
||||
`GeoPoint{ title=출입문번호, category='출입문', lat/lon, z, type='poi' }`.
|
||||
헤더명 우선(폴백 인덱스 latitude=3/longitude=4/출입문번호=6/z=12), 위경도 범위검증, 번호 빈 행 스킵.
|
||||
- `loadFolderGeoData`: Promise.all 에 `parseAccessDoors` 추가, **pois 에 concat** → 영상 오버레이가
|
||||
POI 와 동일 파이프라인(거리필터·연속포즈 투영·클릭 팝업·지면고도)으로 표출.
|
||||
- `StationOverlay`: `CATEGORY_EMOJI['출입문']='🚪'`.
|
||||
|
||||
## 결과
|
||||
- 이 영상 구간(회덕-대전조차장) CSV엔 번호 보유 출입문 4건(5-122,6-007,6-009,6-010) → 🚪 라벨 표출.
|
||||
(번호 없는 행은 라벨 대상 아님 → 스킵.)
|
||||
- 표고는 기본(지면고도) 모드에서 최근접 측점표고 사용(CSV z=67.391 상수는 미사용).
|
||||
- POI 거리 범위(기본 300m)·겹침억제·클릭 속성팝업 모두 동일 적용.
|
||||
|
||||
## 데이터 접근 참고 (WSL /mnt/d)
|
||||
- gpkg `…_layers.gpkg`(출입문 526행 전체)·DEM `(B080)공개DEM/36710.img`(KGD2002/Unified, 90m,
|
||||
구간 포함)·xlsx 모두 판독 가능(sqlite3/rasterio/python3). 필요 시 로컬 DEM 으로 지면고도 대체 가능.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~620k / output ~7k tokens
|
||||
@@ -0,0 +1,38 @@
|
||||
# POI 클릭 팝업에 원본 CSV 속성 표시 (KMZ 정보표 형태)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/types/geo.ts`, `client/src/utils/geoData.ts`,
|
||||
`client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 요구
|
||||
KMZ 팝업처럼 POI(출입문 등)의 본부·선별·역구간·시설팀·측점 등 **원본 속성**을 라벨 클릭 팝업에 표시.
|
||||
|
||||
## 구현
|
||||
### 데이터: 원본 CSV 컬럼을 props 로 보존
|
||||
- `GeoPoint`/`RouteStructure` 에 `props?: { k: string; v: string }[]` 추가(순서 보존).
|
||||
- 헬퍼 `cleanHeader()`(트림+BOM)·`rowProps(header,row)`(비어있지 않은 헤더:값 배열) 추가.
|
||||
- 적용 파서(전부 비어있지 않은 모든 컬럼 저장):
|
||||
· `parseAccessDoors`(출입문) · `parsePois`(_POI.csv + 02)지장물) · `parseStructures`(03)교량/04)터널/06)구교).
|
||||
- `StationOverlay` 구조물→GeoPoint 매핑에 `props: s.props` 전달.
|
||||
|
||||
### 팝업 렌더 (StationOverlay)
|
||||
- `InfoPopup` 에 `props?` 추가, 클릭 시 `obj.props` 캡처.
|
||||
- 팝업 본문을 `grid grid-cols-[auto_1fr]`(키/값) 로:
|
||||
· props 있으면 **모든 속성**(예 본부·선별·역구간·latitude·longitude·시설팀·출입문번호·5186x/y·X/Y/Z좌표·z·측점) + 드론거리.
|
||||
· props 없으면 기존 기본(종류/위도/경도/표고/거리).
|
||||
- `max-h-[45vh] overflow-y-auto`(속성 많을 때 스크롤).
|
||||
|
||||
## 효과
|
||||
- 출입문 6-007 클릭 → KMZ와 동일 항목(본부 대전충남 / 선별 경부선 / 역구간 회덕-대전조 / 시설팀
|
||||
대전시설팀 / 출입문번호 6-007 / 측점 (158K600) / z 67.391 …)이 팝업에 표시.
|
||||
- 지장물·_POI·교량/터널/구교도 각 CSV 속성 표시.
|
||||
|
||||
## 참고 (미구현)
|
||||
- KMZ의 "지면 고도 46.12m"(구글 DEM 실측)는 CSV에 없음 → 로컬 공개 DEM(`36710.img`, rasterio)으로
|
||||
좌표별 지면고도를 채워 팝업/표고에 쓰는 건 별도 작업으로 가능.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 12분
|
||||
**Context 사용량**: input ~660k / output ~7k tokens
|
||||
@@ -0,0 +1,20 @@
|
||||
# 팝업 — KML(원본 CSV) 속성만 표시 (계산값 제거)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1453_POI팝업-원본CSV속성표시.md](2026-06-24_1453_POI팝업-원본CSV속성표시.md)
|
||||
|
||||
## 요구
|
||||
팝업에는 KML 파일에 있는 정보만 표출. 그 외(계산값)는 표출 불필요.
|
||||
|
||||
## 조치
|
||||
팝업 본문에서 앱 계산/부가 항목 제거, **원본 속성(props)만** 표시:
|
||||
- 제거: `거리(드론 기준)`, `종류·카테고리`, `표고(보정)` 등 계산/가공 행.
|
||||
- props 있는 항목(출입문·지장물·_POI·교량/터널/구교): **props(원본 CSV=KML 항목)만** 렌더.
|
||||
- props 없는 항목(측점 등): 좌표(위도/경도)만 최소 표시.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 4분
|
||||
**Context 사용량**: input ~690k / output ~2k tokens
|
||||
@@ -0,0 +1,38 @@
|
||||
# KMZ 직접 로딩 — POI/구조물을 원본 KMZ에서 추출 (CSV 추출 중복 제거)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/utils/geoData.ts`, `client/package.json`(+fflate)
|
||||
|
||||
## 배경/요구
|
||||
지금까지 KMZ → CSV 추출(중복 작업) → 앱이 CSV 로드. 사용자: "KMZ가 원본이니 앱이 KMZ를 직접
|
||||
읽으면 끝날 일." → 앱이 KMZ를 직접 파싱하도록 변경.
|
||||
|
||||
## KMZ 구조 (확인)
|
||||
`<base>.kmz` = `doc.kml`(zip, deflate). 폴더가 building CSV와 1:1:
|
||||
`02)지장물·03)교량·04)터널·05)출입문번호·06)구교`. 각 Placemark = 좌표 + description HTML표(속성).
|
||||
측점(01)은 KMZ에 **없음** → 측점은 계속 CSV.
|
||||
- 02)지장물 69 · 03)교량 13(신대천교(복) 등) · 04)터널 2 · 05)출입문 8 · 06)구교 9 = 101 Placemark.
|
||||
|
||||
## 구현
|
||||
- `fflate` 추가(브라우저 zip 해제, 8KB).
|
||||
- **`parseKmz(files)`**: 루트 .kmz → `unzipSync` → doc.kml → `DOMParser`.
|
||||
폴더 트리 재귀(`walk`)로 Placemark 의 **최내곽 폴더명**을 분류 키로 사용:
|
||||
· 교량/터널/구교 → `RouteStructure`(연장(m)=lengthM, 시설종별=grade).
|
||||
· 출입문 → `GeoPoint{category:'출입문', title=출입문번호}`.
|
||||
· 그 외(02)지장물) → `GeoPoint{category=category_clean}` (철도역/역사는 StationOverlay가 영상 제외).
|
||||
좌표는 `<coordinates>` 우선, 속성은 description CDATA(HTML표)를 `<b>키</b>…<td>값</td>` 정규식 →
|
||||
`props`(팝업 표시·KML 항목 그대로).
|
||||
- `loadFolderGeoData`: `parseKmz` 추가. **KMZ 있고 내용 있으면** 그걸로 pois/structures 사용
|
||||
(지장물·출입문·교량/터널/구교), 없으면 기존 CSV 폴백. **측점·역사·route.json·드론프레임은 CSV 유지.**
|
||||
|
||||
## 결과
|
||||
- KMZ 로드 시: POI 77(지장물 69 + 출입문 8) · 구조물 24(교량 13 + 터널 2 + 구교 9).
|
||||
- 팝업 속성 = KMZ description표 그대로. 신대천교(복) 등 교량도 영상 표출.
|
||||
- 더 이상 02~06 CSV 추출 불필요(측점 CSV/소스, 지장물_역사 CSV만 유지).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
- Python으로 동일 폴더→분류 로직 검증(101 Placemark, 폴더별 수 일치).
|
||||
|
||||
**소요 시간**: 약 22분
|
||||
**Context 사용량**: input ~740k / output ~12k tokens
|
||||
@@ -0,0 +1,36 @@
|
||||
# KMZ source=KAKAO_RAIL(철도역) → 스테이션바 표출
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/utils/geoData.ts`
|
||||
**관련**: [2026-06-24_1603_KMZ직접로딩-CSV중복제거.md](2026-06-24_1603_KMZ직접로딩-CSV중복제거.md)
|
||||
|
||||
## 요구
|
||||
KMZ에서 POI를 가져올 때 `source` 타입이 **KAKAO_RAIL**(철도역)인 항목은 영상이 아니라
|
||||
**하단 스테이션바**에 표시.
|
||||
|
||||
## 조치 (`parseKmz`)
|
||||
02)지장물 폴더 Placemark 처리에서 source/category 로 분기:
|
||||
```ts
|
||||
const src = pget('source');
|
||||
const cat = pget('category_clean') || '지장물';
|
||||
if (src === 'KAKAO_RAIL' || cat === '철도역' || cat === '역사') {
|
||||
// 역사 구조물로 → 스테이션바(Timeline 원형 마커). 영상 미표시.
|
||||
structures.push({ id:`역사-…`, type:'station', category:'역사', name, lat, lon, props });
|
||||
} else {
|
||||
pois.push({ ... category: cat ... }); // 일반 POI(영상)
|
||||
}
|
||||
```
|
||||
- `RouteStructure{type:'station', category:'역사'}` 는 StationBar 가 이미 처리(`cats=['교량','터널','역사']`).
|
||||
- 위치 배치: 역사는 `station`값 없이 **lat/lon → 드론 최근접 통과 px**(`pxPassesTo`)로 배치(기존 역사와 동일).
|
||||
- pois 에서 빠지므로 영상엔 안 뜨고(StationOverlay 가 역사 미표출), 스테이션바에만 표출.
|
||||
|
||||
## 참고
|
||||
- 현재 샘플 KMZ(`하행_회덕-대전조차장.kmz`)는 source 가 VWORLD_ROAD/KAKAO_POI 뿐이라 KAKAO_RAIL 없음
|
||||
→ 이 데이터엔 변화 없음. 철도역이 포함된 데이터(이전/타 구간 KMZ에 회덕화물역=KAKAO_RAIL 존재)에서 적용됨.
|
||||
- category_clean='철도역'/'역사' 도 함께 역사로 라우팅(태깅 방식 무관하게 철도역 포착).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 12분
|
||||
**Context 사용량**: input ~770k / output ~5k tokens
|
||||
@@ -0,0 +1,43 @@
|
||||
# 구글 새 KML 형식 파싱 + 스테이션바 구조물 클릭 팝업
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/utils/geoData.ts`, `client/src/stationbar/StationBar.tsx`,
|
||||
`client/src/stationbar/components/Timeline/Timeline.tsx`
|
||||
**관련**: [2026-06-24_1630_KMZ-KAKAO_RAIL-스테이션바라우팅.md](2026-06-24_1630_KMZ-KAKAO_RAIL-스테이션바라우팅.md)
|
||||
|
||||
## 증상
|
||||
- 회덕화물역 속성이 위도/경도만 보임(=props 비어 no-props 폴백). 영상에 남아있음(스테이션바로 안 감).
|
||||
- 구글에서 다시 받은 KML의 형식이 달라짐.
|
||||
|
||||
## 원인 (새 KML 형식)
|
||||
구글어스에서 재저장된 Placemark description 표가 변형됨:
|
||||
- 옛: `<b>source</b></td><td>VWORLD_ROAD</td>`
|
||||
- 새: `<table><tbody>…<b>source</b><br></td><td>KAKAO_RAIL<br></td>` (`<tbody>`·`</b>` 뒤 `<br>`·`>` 엔티티)
|
||||
기존 정규식 `<b>키</b>\s*</td>…` 이 `</b>` 뒤 `<br>` 때문에 매칭 실패 → props 빈값 →
|
||||
source 못 읽어 KAKAO_RAIL 라우팅도 실패(영상에 남음).
|
||||
또 사용자는 .kmz 가 아니라 **bare `doc.kml`** 을 폴더에 받음.
|
||||
|
||||
## 조치
|
||||
### 1) description 파싱을 행/셀 단위로 견고화 (`geoData.ts`)
|
||||
`<tr>` 안 두 `<td>` 셀에서 태그 제거+엔티티 디코드(`stripHtml`) → (키,값). 옛/새 형식 모두 처리.
|
||||
→ 회덕화물역 17개 속성 전부 추출(source=KAKAO_RAIL 포함) 검증. KAKAO_RAIL→스테이션바 라우팅 정상화.
|
||||
|
||||
### 2) bare .kml 지원 (`parseKmz`)
|
||||
폴더 루트의 **`.kml` 우선**(구글 직접 다운로드, UTF-8 `await file.text()`), 없으면 `.kmz`(zip) 해제.
|
||||
|
||||
### 3) 스테이션바 구조물 클릭 팝업 (`StationBar.tsx`·`Timeline.tsx`)
|
||||
역사(회덕화물역)는 스테이션바 원형 아이콘만 있어 속성이 안 보였음 →
|
||||
- `StructMark` 에 `props` 추가(구조물 마크 빌드 시 `s.props` 전달).
|
||||
- Timeline: 구조물 마커 위 클릭 핫스팟(zIndex 21, seekArea 위) → 클릭 시 **속성 팝업**.
|
||||
팝업은 클릭 좌표 기준 `position:fixed`(트랙 래퍼 transform 영향 회피), 배경 클릭 시 닫힘.
|
||||
- 역사/교량/터널 모두 적용(구교는 영상에서 처리).
|
||||
|
||||
## 결과
|
||||
- 회덕화물역: KAKAO_RAIL → 스테이션바(원형) + 클릭 시 전체 KML 속성 팝업.
|
||||
- 새 형식 KML(또는 .kmz) 모두 속성 정상 추출.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) 통과. Python으로 새 형식 행/셀 파싱 → 17속성 추출 확인.
|
||||
|
||||
**소요 시간**: 약 25분
|
||||
**Context 사용량**: input ~820k / output ~12k tokens
|
||||
@@ -0,0 +1,39 @@
|
||||
# 철도역(KAKAO_RAIL) — 영상 POI + 스테이션바 양쪽 표출 (모든 POI 영상 표시)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/utils/geoData.ts`, `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1647_새KML형식파싱+스테이션바구조물팝업.md](2026-06-24_1647_새KML형식파싱+스테이션바구조물팝업.md)
|
||||
|
||||
## 요구 (정정)
|
||||
- 회덕화물역 POI 가 영상에 안 보임 → **모든 POI 는 영상에 보여야 함**(철도역 포함).
|
||||
- KAKAO_RAIL 은 영상 POI 에 **더해** 스테이션바에도 표시(둘 다).
|
||||
|
||||
## 직전 오류
|
||||
KAKAO_RAIL 을 스테이션바로 **라우팅(영상 제외)** 했음 → 회덕화물역이 영상에서 빠짐. 요구는 양쪽 표출.
|
||||
|
||||
## 조치
|
||||
### parseKmz — 철도역을 pois 에 항상 + 스테이션바에 추가
|
||||
```ts
|
||||
pois.push({ title:name, category:cat, lat, lon, z, type:'poi', props }); // 모든 POI 영상
|
||||
if (src === 'KAKAO_RAIL' || cat === '철도역' || cat === '역사')
|
||||
structures.push({ type:'station', category:'역사', name, lat, lon, props }); // + 스테이션바
|
||||
```
|
||||
### StationOverlay — 철도역/역사 영상 제외 필터 제거
|
||||
```diff
|
||||
- const allPoi = allPoisRef.current
|
||||
- .filter(p => p.category !== '철도역' && p.category !== '역사')
|
||||
- .concat(allStructuresRef.current);
|
||||
+ const allPoi = allPoisRef.current.concat(allStructuresRef.current); // 모든 POI 표출
|
||||
```
|
||||
- `CATEGORY_EMOJI['철도역']='🚉'` 추가.
|
||||
|
||||
## 결과
|
||||
- 회덕화물역: **영상 POI(🚉 + 전체 KML 속성 팝업)** + **스테이션바(원형 마커, 클릭 시 속성 팝업)** 동시.
|
||||
- 역사 구조물은 영상 구조물 필터(구교/교량/터널)에 미포함이라 영상엔 POI 1개만(중복 없음).
|
||||
- 다른 모든 POI(지장물 등)도 영상 표출(제외 없음).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 8분
|
||||
**Context 사용량**: input ~880k / output ~5k tokens
|
||||
@@ -0,0 +1,23 @@
|
||||
# POI 라벨 = KML 구분 → title → name 순
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/utils/geoData.ts`
|
||||
|
||||
## 요구
|
||||
POI 라벨을 KML의 `구분` 또는 `title` 로 표시, 그런 항목이 없으면 지금처럼 `name`(placemark name).
|
||||
|
||||
## 조치 (`parseKmz`)
|
||||
지오코딩 POI(02)지장물·철도역) 라벨 우선순위:
|
||||
```ts
|
||||
const label = pget('구분') || pget('title') || name;
|
||||
pois.push({ title: label, ... });
|
||||
```
|
||||
- 철도역의 스테이션바(역사) name 도 동일 label 사용(일관).
|
||||
- 출입문(=출입문번호)·구조물(교량/터널=구분, 구교=시설물명)은 각자 라벨 유지(이미 적절).
|
||||
- CSV 폴백(`parsePois`)은 이미 'title'/'명칭' 컬럼 사용.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 4분
|
||||
**Context 사용량**: input ~920k / output ~2k tokens
|
||||
@@ -0,0 +1,33 @@
|
||||
# 1배속에서 POI 라벨·팝업 떨림 제거 — 속도 적응형 평활(One Euro 방식)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## 증상
|
||||
빠른 배속에선 POI 라벨/팝업이 부드러운데, **1배속(느린 이동)에선 흔들려 보임**.
|
||||
|
||||
## 원인
|
||||
라벨 화면좌표에 **일정 진폭의 떨림(GPS/투영 잔여 노이즈)**이 있음. 빠른 배속은 프레임당 이동량이
|
||||
커서 떨림이 묻히고, 1배속은 이동이 작아 떨림이 도드라짐. 화면 EMA(`emaAlpha`)가 1.0(평활 거의 없음)
|
||||
이라 그대로 노출됨.
|
||||
|
||||
## 조치 — `smoothStep` 을 속도 적응형으로(One Euro 필터 방식)
|
||||
고정 alpha → **속도에 따라 가변**:
|
||||
- 속도 벡터를 평활(`vx,vy` EMA, β=0.25). 떨림은 매 프레임 방향이 번갈아 → 평활속도≈0,
|
||||
실제 이동은 방향 일관 → 평활속도 큼. (떨림과 실제 이동을 속도로 구분)
|
||||
- `alpha = min(maxAlpha, MIN(0.12) + (maxAlpha−0.12)·min(1, speed/SPEED_REF(0.01)))`
|
||||
- 정지/떨림(speed≈0) → alpha≈0.12 → **강한 평활(1배속 안정)**.
|
||||
- 빠른 이동(speed≥0.01) → alpha→maxAlpha(패널 EMA α=1.0) → **즉시 추종(빠른 배속 지연 없음)**.
|
||||
- 이상치 거부(REJECT_DIST)·연속한계는 유지. `DispPos` 에 vx/vy 추가.
|
||||
|
||||
## 효과
|
||||
- 1배속·먼 POI(떨림 잘 보임) → 강하게 평활돼 안정.
|
||||
- 가까운/빠른 이동 POI → 즉시 추종(지연 없음).
|
||||
- 팝업도 라벨 displayed 좌표를 따라가므로 함께 안정.
|
||||
- 패널 **EMA α** 슬라이더 = 빠를 때의 추종 상한(기본 1.0).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~960k / output ~4k tokens
|
||||
@@ -0,0 +1,24 @@
|
||||
# 속도적응 평활 튜닝값을 패널 슬라이더로 노출 (떨림억제·반응속도)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/components/overlay/StationOverlay.tsx`
|
||||
**관련**: [2026-06-24_1758_라벨팝업-속도적응평활-1배속떨림제거.md](2026-06-24_1758_라벨팝업-속도적응평활-1배속떨림제거.md)
|
||||
|
||||
## 요구
|
||||
직전에 추가한 `SMOOTH_MIN_ALPHA(0.12)`/`SMOOTH_SPEED_REF(0.01)` 을 어디서 조절? (코드 상수였음)
|
||||
→ 패널에서 실시간 조절 가능하게.
|
||||
|
||||
## 조치
|
||||
- `smoothStep` 시그니처에 `minAlpha, speedRef` 파라미터 추가(모듈 상수 제거). `SMOOTH_VEL_BETA`만 상수 유지.
|
||||
- 상태/ref `smoothMinAlpha`(0.12)·`smoothSpeedRef`(0.010) + `DISPLAY_DEFAULTS` 추가.
|
||||
- RAF 의 smoothStep 2개 호출에 `smoothMinAlphaRef.current, smoothSpeedRefRef.current` 전달.
|
||||
- 화면표시 옵션 패널 "스무딩" 섹션에 슬라이더 2개:
|
||||
· **떨림억제**(min α, 0.02~0.6) — 낮을수록 1배속 떨림 강하게 평활.
|
||||
· **반응속도**(speedRef, 0.002~0.04) — 클수록 느린 이동까지 부드럽게, 줄이면 빨리 반응.
|
||||
- "기본값" 버튼 리셋 + `ghivideo:calib` 영속화(저장/복원)에 포함. EMA α 툴팁을 "빠를 때 추종 상한"으로 정리.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~1010k / output ~5k tokens
|
||||
@@ -0,0 +1,34 @@
|
||||
# 스테이션바 구조물명 — 긴 명칭 2줄 + 첫/마지막 항목 양끝 정렬
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/stationbar/components/Timeline/Timeline.tsx`,
|
||||
`client/src/stationbar/components/Timeline/Timeline.module.scss`
|
||||
|
||||
## 요구
|
||||
1. 스테이션바 구조물명이 길면 두 줄로 표시.
|
||||
2. 첫 항목과 마지막 항목은 바 끝에 위치(끝에서 잘리지 않게).
|
||||
|
||||
## 조치
|
||||
### 1) 긴 명칭 2줄 (`.structName`)
|
||||
구조물명 라벨에 `structName` 클래스 추가:
|
||||
```scss
|
||||
.structName { width:96px; white-space:normal; text-align:center; font-size:12px;
|
||||
line-height:12.5px; word-break:break-all; overflow-wrap:anywhere;
|
||||
display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
|
||||
```
|
||||
- 폭 96px 고정 → 길면 자동 줄바꿈, 공백 없는 한글도 끊어 2줄. 최대 2줄(넘으면 …).
|
||||
- 폰트 16.5→12px 로 줄여 2줄이 라벨행(26px)에 들어가게.
|
||||
|
||||
### 2) 첫/마지막 항목 양끝 정렬
|
||||
`structs`(px 정렬)의 i===0 은 `anchorStart`(좌측 정렬, 좌끝이 px), 마지막은 `anchorEnd`(우측
|
||||
정렬, 우끝이 px) → 가운데정렬 시 바 가장자리에서 잘리던 것 방지(기존 anchor 클래스 재사용).
|
||||
|
||||
## 참고
|
||||
- 요구2는 "첫/마지막 라벨을 바 끝에서 안 잘리게 정렬"로 해석. 만약 '첫/마지막 시설을 바의
|
||||
물리적 좌우 끝 좌표로 이동'을 의도했다면 다른 구현 필요(확인 후 조정 가능).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 12분
|
||||
**Context 사용량**: input ~1060k / output ~5k tokens
|
||||
@@ -0,0 +1,27 @@
|
||||
# 스테이션바 첫/마지막 항목을 바 시작·끝 지점으로 이동(px 스냅)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/stationbar/components/Timeline/Timeline.tsx`
|
||||
**관련**: [2026-06-24_1921_스테이션바-구조물명2줄+양끝정렬.md](2026-06-24_1921_스테이션바-구조물명2줄+양끝정렬.md)
|
||||
|
||||
## 요구 (요청2 확정)
|
||||
첫 번째 항목은 스테이션바 **시작점**, 마지막 항목은 **끝점**으로 각각 이동해 표시.
|
||||
|
||||
## 조치
|
||||
dedup(px 정렬) 후 첫/마지막 항목의 px 를 바 양끝 좌표로 스냅:
|
||||
```ts
|
||||
const BAR_START = TRACK_START_PX; // 297
|
||||
const BAR_END = TRACK_START_PX + TRACK_WIDTH_PX; // 1806.5
|
||||
const structsRaw = dedup(structures, 90);
|
||||
const structs = structsRaw.map((s, i) =>
|
||||
i === 0 ? { ...s, px: BAR_START } : i === structsRaw.length - 1 ? { ...s, px: BAR_END } : s);
|
||||
```
|
||||
- s.px 좌표계는 트랙 래퍼 공간(297~1806.5, seekArea/pxAtTime 와 동일).
|
||||
- 아이콘·측점값·구조물명·클릭핫스팟이 모두 s.px 사용 → 항목 전체가 양끝으로 함께 이동.
|
||||
- 직전 작업의 첫=좌측정렬/마지막=우측정렬(anchorStart/End)과 결합 → 끝에서 안 잘림.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 5분
|
||||
**Context 사용량**: input ~1110k / output ~3k tokens
|
||||
@@ -0,0 +1,23 @@
|
||||
# 스테이션바 구조물명 — 아이콘 가운데 정렬 + 폰트 원래 크기 복원
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/stationbar/components/Timeline/Timeline.tsx`,
|
||||
`client/src/stationbar/components/Timeline/Timeline.module.scss`
|
||||
**관련**: [2026-06-24_1933_스테이션바-첫마지막항목-바양끝스냅.md](2026-06-24_1933_스테이션바-첫마지막항목-바양끝스냅.md)
|
||||
|
||||
## 요구
|
||||
- 글자도 해당 아이콘 가운데로 이동(가운데 정렬).
|
||||
- 글자 크기를 이전(2줄 작업)처럼 작게 하지 말 것.
|
||||
|
||||
## 조치
|
||||
- Timeline: 첫/마지막 좌·우 정렬(anchorStart/End) 제거 → 모든 구조물명 라벨 **가운데 정렬**
|
||||
(segmentLabel 기본 translateX(-50%)). 아이콘 px 스냅(첫=바시작/마지막=바끝)은 유지 →
|
||||
라벨이 아이콘 가운데에 함께 위치.
|
||||
- `.structName`: `font-size:12px`/`line-height:12.5px` 작은 폰트 제거 → **라벨행 기본 16.5px 유지**.
|
||||
2줄 래핑(width 92px·line-clamp 2·word-break)·line-height 18px 는 유지.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 4분
|
||||
**Context 사용량**: input ~1150k / output ~3k tokens
|
||||
@@ -0,0 +1,30 @@
|
||||
# 측점/역사 파일을 root 에서도 인식 (building 폴더 삭제 가능)
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/utils/geoData.ts`
|
||||
|
||||
## 배경
|
||||
사용자가 v3.0 폴더에서 `01)측점.csv` 를 root(영상 옆)로 옮기고 building 폴더(02~06)를 지우려 함.
|
||||
그런데 기존 코드는 측점/역사를 `building/` 안에서만 찾아 → root 이동 시 측점 미인식(중심선 깨짐).
|
||||
|
||||
## 조치
|
||||
- `parseStations`: 측점 파일을 building → **root 어디든** 찾도록(`includes('01)측점')`/`'측점'`).
|
||||
- `parseHistoricStations`: 역사 파일도 `isInBuilding` 제한 제거(root 허용).
|
||||
- 드론 CSV 선택(`parseDroneFrames`)은 `<base>.csv` 우선이라 root 에 측점 CSV 가 같이 있어도 영향 없음.
|
||||
|
||||
## 파일 필요 여부 정리 (웹앱 기준)
|
||||
| 파일 | 필요 |
|
||||
|---|---|
|
||||
| `<영상>.MP4` | ✅ 필수 |
|
||||
| **`<영상>.csv`** (frame_cnt,lat,lon,alt,yaw,pitch,roll,focal) = 드론 프레임 | ✅ **필수** |
|
||||
| `01)측점.csv` (root 또는 building) | ✅ 필수(KMZ에 없음) |
|
||||
| `<영상>.kmz` | ✅ POI/구조물 |
|
||||
| building/02~06)*.csv | ⭕ 삭제 가능(KMZ 대체, 폴백용) |
|
||||
| `<영상>_POI.csv` | ⭕ 폴백(삭제 가능) |
|
||||
| `.srt` / `.qgs` / `.qgz` / `_구조물추출_Z.xlsx` | ⭕ 웹앱 미사용 |
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 8분
|
||||
**Context 사용량**: input ~1210k / output ~5k tokens
|
||||
@@ -0,0 +1,24 @@
|
||||
# 스테이션바 라벨 폭 확대(짧은 명칭 1줄) + POI 팝업 1배속 물결 제거
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/stationbar/components/Timeline/Timeline.tsx`,
|
||||
`client/src/stationbar/components/Timeline/Timeline.module.scss`,
|
||||
`client/src/components/overlay/StationOverlay.tsx`
|
||||
|
||||
## A. 짧은 명칭은 한 줄로
|
||||
증상: "법동가도교 (하)" 처럼 한 줄에 들어갈 명칭이 2줄로 줄바꿈됨(폭 92px 가 좁아서).
|
||||
- `.structName` width 92→**140px** (≈8자 1줄). 긴 명칭만 2줄로 래핑.
|
||||
- 겹침 방지: `dedup(structures, 90→130)` (라벨 폭에 맞춤).
|
||||
|
||||
## B. POI 팝업 1배속 물결(떨림) 제거
|
||||
증상: 1배속 재생 시 팝업(큰 텍스트 박스)이 물결침.
|
||||
원인: 라벨의 잔떨림 + 매 프레임 **소수픽셀** 위치 갱신 → 텍스트 안티앨리어싱 셰이딩이 물결로 보임.
|
||||
- 팝업 위치에 **별도 EMA(α=0.18, 무겁게)** 적용(`popupPosRef`) + **정수 픽셀 반올림**(`Math.round`).
|
||||
→ 라벨을 따라가되 잔떨림은 흡수, 소수픽셀 셰이딩 제거. (팝업 제거 시 ref 정리)
|
||||
- 팝업은 정보 박스라 약간의 지연은 무방.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 12분
|
||||
**Context 사용량**: input ~1290k / output ~6k tokens
|
||||
@@ -0,0 +1,35 @@
|
||||
# 스테이션바 시/종점 역명 = route.json name 의 '-' 좌/우 + 좌상단 타이틀 routeInfo 매핑 확인
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/stationbar/StationBar.tsx`
|
||||
|
||||
## 요구
|
||||
1. 스테이션바 시작은 측점값(157K900 등)이 아니라 route.json `name`("회덕-대전조차장")의 '-' **왼쪽=회덕**,
|
||||
끝은 **오른쪽=대전조차장**.
|
||||
2. 좌측 상단 타이틀은 각 항목에 맞는 routeInfo 변수로.
|
||||
|
||||
## 조치
|
||||
### 1) 시/종점 역명 = name '-' 분리 (StationBar)
|
||||
```ts
|
||||
const nm = routeMeta?.routeInfo?.name ?? ''; // "회덕-대전조차장"
|
||||
const dash = nm.indexOf('-');
|
||||
const leftName = dash >= 0 ? nm.slice(0, dash).trim() : ''; // 회덕
|
||||
const rightName = dash >= 0 ? nm.slice(dash + 1).trim() : ''; // 대전조차장
|
||||
startStationName: leftName || routeInfo.startStationName || 측점첫;
|
||||
endStationName: rightName || routeInfo.endStationName || 측점끝;
|
||||
```
|
||||
- name 분리값 우선(요구). 없으면 startStationName/endStationName → 측점 첫/끝 폴백.
|
||||
|
||||
### 2) 좌상단 타이틀 (변경 없음 — 이미 매핑)
|
||||
`RouteInfoOverlay` 가 이미 routeInfo 각 변수를 매핑: 방향=direction, 노선명=name, 연장=lengthKm,
|
||||
소요=durationSec. route.json 만 폴더에 있으면 표출됨 → 추가 작업 불필요.
|
||||
|
||||
## 확인
|
||||
- 폴더의 `하행)회덕-대전조차장.route.json` 은 UTF-8 정상. `parseRouteMeta`(file.text, base.route.json 매칭)로 로드.
|
||||
- name="회덕-대전조차장", direction="하 행", lengthKm=4.25, durationSec=672(=11분12초).
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~1350k / output ~5k tokens
|
||||
@@ -0,0 +1,28 @@
|
||||
# 시/종점 역명 = route.json start/endStationName + 구조물 스냅 제거 + 종점 역명 안 잘림
|
||||
|
||||
**날짜**: 2026-06-24
|
||||
**수정 파일**: `client/src/stationbar/StationBar.tsx`,
|
||||
`client/src/stationbar/components/Timeline/Timeline.tsx`,
|
||||
`client/src/stationbar/components/Timeline/Timeline.module.scss`
|
||||
**관련**: [2026-06-24_2032_스테이션바시종점-routejson-name분리.md](2026-06-24_2032_스테이션바시종점-routejson-name분리.md)
|
||||
|
||||
## 요구 (갱신)
|
||||
- 시작 = route.json `startStationName`, 끝 = `endStationName`.
|
||||
- 구조물 아이콘/라벨은 이전처럼(실제 위치).
|
||||
|
||||
## 조치
|
||||
1. **시/종점 역명**: name 분리 → `startStationName`/`endStationName` 직접 사용으로 되돌림(없으면 측점 첫/끝 폴백).
|
||||
→ route.json 에 원하는 값(예 startStationName:"회덕", endStationName:"대전조차장")을 넣으면 그대로 표시.
|
||||
2. **구조물 px 스냅 제거**: 첫/마지막을 바 양끝으로 옮기던 로직 삭제 → 구조물은 실제 통과 px 그대로.
|
||||
(시/종점 역명이 바 끝을 담당하므로 구조물 스냅 불필요. 시작점 겹침도 해소.)
|
||||
3. **종점 역명 안 잘림 유지**: `.stationEnd` 를 `left: track-end+40` → `right: 8px` 로 변경
|
||||
(긴 종점명 "대전조차장" 등도 우측 끝 기준 안쪽으로 펼쳐 화면 밖 안 잘림).
|
||||
|
||||
## 유지 (이전 사용자 요청)
|
||||
- 구조물명 긴 건 2줄/짧은 건 1줄(width 140), 아이콘 가운데 정렬, 폰트 16.5px, 겹침간격 130.
|
||||
|
||||
## 검증
|
||||
- `npm run build -w client` (node20) tsc + vite build 통과.
|
||||
|
||||
**소요 시간**: 약 8분
|
||||
**Context 사용량**: input ~1410k / output ~5k tokens
|
||||
@@ -0,0 +1,38 @@
|
||||
# 스테이션바 종점역(대전조차장) 누락 수정 + 라벨 우선순위 정리
|
||||
|
||||
**소요 시간**: 25분
|
||||
**Context 사용량**: input 약 130k / output 약 9k tokens
|
||||
|
||||
## 증상
|
||||
하단 스테이션바의 마지막 항목에 종점역 **대전조차장**(한국철도공사대전조차장역)이 표시되지 않음.
|
||||
|
||||
## 원인
|
||||
[Timeline.tsx](../../client/src/stationbar/components/Timeline/Timeline.tsx)의 구조물 라벨 겹침 방지 로직:
|
||||
|
||||
```ts
|
||||
const structs = dedup(structures, 130);
|
||||
```
|
||||
|
||||
기존 `dedup`은 px 오름차순으로 정렬해 **130px 클러스터마다 가장 왼쪽 1개만** 유지했다.
|
||||
|
||||
실데이터(하행)회덕-대전조차장 v2.0) 기준:
|
||||
- 종점역 `대전조차장역` 좌표: lat 36.371101 / lon 127.421776
|
||||
- 그 직전 `법동가도교`(상·하·인상 3건): lat ~36.3730 (≈200m 앞, 트랙 1509.5px 중 ~70px)
|
||||
|
||||
→ 법동가도교가 먼저(왼쪽) 유지되고, 130px 이내인 종점역이 dedup으로 탈락.
|
||||
|
||||
## 사용자 확정 규칙 (라벨 겹침 우선순위)
|
||||
- **화면상(영상) 라벨**: 겹침 처리 동작 + 겹칠 경우 **KAKAO_RAIL(철도역/역사) 우선 표출**.
|
||||
- **하단 스테이션바**: 겹쳐도 무방 → 역사는 dedup 대상에서 제외, 항상 표출.
|
||||
|
||||
## 조치
|
||||
1. 하단 스테이션바 [Timeline.tsx](../../client/src/stationbar/components/Timeline/Timeline.tsx)
|
||||
- 역사(역, KAKAO_RAIL)는 dedup에서 제외하고 **항상 표출**.
|
||||
- 교량/터널만 기존 130px 겹침 방지 유지(라벨 텍스트가 길어 겹침 방지 필요).
|
||||
2. 영상 오버레이 [StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx)
|
||||
- POI 겹침 억제 정렬을 `dist`만 → **railRank(철도역/역사=0) → dist** 로 변경.
|
||||
- 겹치면 KAKAO_RAIL 라벨이 유지되고 일반 POI가 숨겨짐.
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit -p client/tsconfig.json` → 통과(exit 0)
|
||||
- 실데이터 좌표·거리 계산으로 클러스터링 원인 확인
|
||||
@@ -0,0 +1,27 @@
|
||||
# 스테이션바 첫·마지막 역 양끝 고정 + 누락 원인(빌드) 해결 + pm2 배포
|
||||
|
||||
**소요 시간**: 70분
|
||||
**Context 사용량**: input 약 320k / output 약 22k tokens
|
||||
|
||||
## 1. 대전조차장 누락의 진짜 원인 = 빌드 미반영
|
||||
- 데이터(v3.0 KMZ)·파싱(parseKmz)·코드 수정 모두 정상이었음. 실데이터 시뮬레이션으로 마커가 px 1777 생성·유지됨을 검증.
|
||||
- 화면에 안 뜬 이유: 서버가 **6/24 빌드된 `client/dist`** 를 서빙 → 6/25 수정 미반영.
|
||||
- 조치: `npm run build -w client` 재빌드 후 `pm2 start ecosystem.config.js` (PORT 55000). 정상 표출 확인.
|
||||
|
||||
## 2. 렌더 필터 수정 (앞 단계, 누적 반영)
|
||||
- [Timeline.tsx](../../client/src/stationbar/components/Timeline/Timeline.tsx): 역사(역, KAKAO_RAIL)는 dedup 제외 → 항상 표출.
|
||||
- [StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx): POI 겹침 억제 정렬을 railRank(철도역/역사 우선)→dist 로 변경.
|
||||
|
||||
## 3. 첫·마지막 스테이션 양끝 고정 (이번 요청)
|
||||
- [StationBar.tsx](../../client/src/stationbar/StationBar.tsx) `structureMarks` 에 `snapStationEnds` 헬퍼 추가.
|
||||
- 역사 마커 중 **최좌측 → TRACK_START_PX(297)**, **최우측 → TRACK_END_PX(1806.5)** 로 스냅.
|
||||
- 역사 2개 미만이면 미적용. 동일 역 복수 통과 시 극단 마커만 이동.
|
||||
- 두 return 경로(storeStructures / POI 폴백) 모두 적용.
|
||||
- 예: 회덕화물역(353.9→297), 한국철도공사대전조차장역(1777→1806.5).
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과, 클라이언트 재빌드 성공, pm2 `ghiVideo` online, HTTP 200.
|
||||
|
||||
## 운영 메모
|
||||
- 실행: nvm Node 20 활성화 → `npm run build -w client` → `pm2 restart ghiVideo`.
|
||||
- 서버는 `client/dist` 정적 서빙 → **소스 수정 후 반드시 클라이언트 재빌드**해야 화면 반영됨.
|
||||
@@ -0,0 +1,27 @@
|
||||
# 스테이션바 구조물명 라벨 2줄 균등 분할
|
||||
|
||||
**소요 시간**: 15분
|
||||
**Context 사용량**: input 약 350k / output 약 25k tokens
|
||||
|
||||
## 요청
|
||||
스테이션바 항목 이름이 2줄로 표출될 때 위/아래 글자 수가 최대한 같게 배치(옆 라벨과 간격 최대 확보).
|
||||
|
||||
## 조치
|
||||
1. [Timeline.tsx](../../client/src/stationbar/components/Timeline/Timeline.tsx) `splitStructLabel` 추가
|
||||
- 추정 폭 ≤ 8.2자(≈140px/16.5px, CJK 1.0·기타 0.55)면 1줄 유지.
|
||||
- 길면 글자수 절반 지점에서 2줄 분할(균등) → 최대 줄 폭 최소화.
|
||||
- 분할점이 괄호 `(...)` 내부를 가르면 괄호 시작 앞으로 이동(괄호 통째 아랫줄).
|
||||
- 라벨 렌더: `lines.length===1 ? text : <>{l0}<br/>{l1}</>`.
|
||||
2. [Timeline.module.scss](../../client/src/stationbar/components/Timeline/Timeline.module.scss) `.structName`
|
||||
- `width:140px` 자동 줄바꿈 → `width:max-content; max-width:140px; white-space:nowrap`.
|
||||
- JS가 `<br>`로 줄을 제어하므로 word-break/line-clamp 제거. 박스가 내용 폭에 맞게 줄어 간격 확보.
|
||||
|
||||
## 검증
|
||||
- 한국철도공사대전조차장역 → 한국철도공사 / 대전조차장역 (6/6)
|
||||
- 법동가도교(상,인상,고속) → 법동가도교 / (상,인상,고속)
|
||||
- 회덕제1가도교(인상) → 회덕제1가도 / 교(인상) (6/5)
|
||||
- 회덕화물역·신대천교(복)·회덕터널(상) 등 → 1줄
|
||||
- `tsc` 통과, 클라이언트 재빌드, pm2 `ghiVideo` 재기동, HTTP 200.
|
||||
|
||||
## 비고
|
||||
- 한 줄 임계값(8.2자)·괄호 처리 방식은 조정 가능.
|
||||
@@ -0,0 +1,26 @@
|
||||
# POI 라벨(구분) · 시설등급 기타 · 회덕화물역 km 검증 · 컴팩트 팝업
|
||||
|
||||
**소요 시간**: 40분
|
||||
**Context 사용량**: input 약 470k / output 약 38k tokens
|
||||
|
||||
## 1. 화면 POI 라벨 — '구분' 우선
|
||||
- [StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx) `guboonOf()` 추가.
|
||||
- poiCand/poiMarkers/LabelCache 에 `label` 필드(=구분 값 있으면 그 값, 없으면 title) 추가.
|
||||
- 캔버스 라벨이 `poiA.label` 사용. (matching/override 키는 title 유지.)
|
||||
|
||||
## 2. 시설등급에 '기타' 추가
|
||||
- [settingsStore.ts](../../client/src/store/settingsStore.ts) `KNOWN_GRADES`에 '기타' 추가, `DEFAULT_GRADE_FILTER['기타']=true`.
|
||||
- VideoPlayer 필터 UI는 `KNOWN_GRADES.map`이라 자동 노출. isGradeVisible 규칙상 '기타'도 토글 대상이 됨.
|
||||
|
||||
## 3. 회덕화물역 스테이션바 값 = 157K990 (검증)
|
||||
- 실데이터 검증: 회덕화물역 최근접 프레임 762(16.6m)·재접근 3647(44.7m) **둘 다 chain→157k990**. 마커 km 라벨은 이미 157k990 산출.
|
||||
- 주의(미해결): 회덕화물역이 양끝 스냅으로 t=0(좌측 끝)에 붙어 있어, **마커로 seek 시 커서는 영상 시작(≈157k900) 위치**를 보임 → 마커값(157k990)과 불일치. 시작점도 방식1(시간축 재기준화)로 맞추려면 추가 작업 필요(사용자 확인 대기).
|
||||
|
||||
## 4. 화면 POI 컴팩트 팝업 + 클릭 시 전체
|
||||
- `compactFieldsOf()` 추가: 시설종별 / 구조형식(상부구조형식) / 연장(m) 중 존재하는 것만.
|
||||
- poiMarkers에 `compact` 미리계산. 캔버스에서 라벨 아래 반투명 박스로 **항상 표시**.
|
||||
- 히트박스를 컴팩트 박스까지 확장 → 클릭 시 기존 전체 속성 DOM 팝업 오픈.
|
||||
- 성능: DOM 팝업 남발 대신 캔버스 드로잉(라벨과 함께 매 프레임, 구조물만 대상).
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과, 클라이언트 재빌드, pm2 ghiVideo 재기동, HTTP 200.
|
||||
@@ -0,0 +1,313 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>발명신고서 — 측점표고 기반 영상정합(B) · 슬랜트거리 보존 위치보정(C)</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#ffffff; --ink:#1a1d23; --muted:#5b6472; --line:#d8dde6; --soft:#f4f6f9;
|
||||
--accent:#1c5fd6; --accent2:#0a7d52; --warn:#b4690e; --bad:#c0362c;
|
||||
--chip:#eef2f8;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html{scroll-behavior:smooth}
|
||||
body{margin:0;background:#e9edf2;color:var(--ink);
|
||||
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Apple SD Gothic Neo","Noto Sans KR",Roboto,"Malgun Gothic",sans-serif;
|
||||
line-height:1.72;font-size:14.5px}
|
||||
.page{max-width:900px;margin:24px auto;background:var(--bg);padding:54px 60px 80px;
|
||||
box-shadow:0 2px 18px rgba(0,0,0,.12);border-radius:4px}
|
||||
.doc-head{border-bottom:3px solid var(--ink);padding-bottom:16px;margin-bottom:8px}
|
||||
.doc-head .t1{font-size:13px;letter-spacing:.3em;color:var(--muted);font-weight:700}
|
||||
h1{font-size:25px;margin:8px 0 4px;line-height:1.35}
|
||||
.doc-head .sub{color:var(--muted);font-size:13.5px}
|
||||
.formrow{display:flex;flex-wrap:wrap;gap:0;border:1px solid var(--line);border-radius:6px;overflow:hidden;margin:16px 0}
|
||||
.formrow .cell{flex:1 1 50%;display:flex;border-bottom:1px solid var(--line)}
|
||||
.formrow .cell .k{flex:0 0 120px;background:var(--soft);padding:7px 12px;font-weight:600;font-size:12.5px;color:var(--muted);border-right:1px solid var(--line)}
|
||||
.formrow .cell .v{padding:7px 12px;font-size:13px;flex:1}
|
||||
.fill{color:var(--bad);font-weight:600}
|
||||
|
||||
nav{background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:10px 16px;margin:18px 0 26px;font-size:12.5px}
|
||||
nav b{color:var(--muted);font-weight:700;margin-right:8px}
|
||||
nav a{color:var(--accent);text-decoration:none;margin-right:14px;white-space:nowrap}
|
||||
nav a:hover{text-decoration:underline}
|
||||
|
||||
section{margin:30px 0;scroll-margin-top:14px}
|
||||
h2{font-size:19px;margin:0 0 6px;padding:7px 0 7px 12px;border-left:5px solid var(--accent);background:linear-gradient(90deg,var(--soft),transparent)}
|
||||
h2.inv{border-left-color:var(--accent2)}
|
||||
h3{font-size:15.5px;margin:20px 0 6px;color:#2a3340}
|
||||
h4{font-size:13.5px;margin:14px 0 4px;color:var(--muted);letter-spacing:.02em}
|
||||
p{margin:9px 0}
|
||||
.lead{color:var(--muted)}
|
||||
ul,ol{margin:8px 0;padding-left:22px}
|
||||
li{margin:5px 0}
|
||||
code{background:#f0f3f8;border:1px solid var(--line);border-radius:4px;padding:1px 5px;font-size:12.5px;
|
||||
color:#0d47a1;font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace}
|
||||
pre{background:#0f1320;color:#dfe6f2;border-radius:8px;padding:14px 16px;overflow-x:auto;font-size:12.5px;
|
||||
font-family:"SFMono-Regular",Consolas,Menlo,monospace;line-height:1.6;margin:10px 0}
|
||||
pre .c{color:#7f8aa3}
|
||||
table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13px}
|
||||
th,td{border:1px solid var(--line);padding:8px 11px;text-align:left;vertical-align:top}
|
||||
th{background:var(--soft);font-weight:600;color:#2a3340}
|
||||
.callout{border-left:4px solid var(--warn);background:#fdf6ec;padding:11px 15px;border-radius:0 8px 8px 0;margin:14px 0;font-size:13.5px}
|
||||
.callout.bad{border-color:var(--bad);background:#fcefee}
|
||||
.callout.ok{border-color:var(--accent2);background:#ecf7f1}
|
||||
.callout.key{border-color:var(--accent);background:#eef3fc}
|
||||
.tag{display:inline-block;font-size:11px;font-weight:700;padding:2px 9px;border-radius:999px;background:var(--chip);color:var(--accent);border:1px solid #cfe0f7}
|
||||
.tag.g{background:#e6f5ee;color:var(--accent2);border-color:#bfe6d2}
|
||||
.num{display:inline-block;min-width:26px;height:26px;line-height:26px;text-align:center;background:var(--accent);color:#fff;border-radius:50%;font-weight:800;font-size:12px;margin-right:8px}
|
||||
.num.g{background:var(--accent2)}
|
||||
.fig{border:1px dashed #b9c2d0;background:var(--soft);border-radius:8px;padding:12px 16px;margin:12px 0;font-size:12.8px;color:var(--muted)}
|
||||
.fig b{color:var(--ink)}
|
||||
footer{margin-top:50px;border-top:1px solid var(--line);padding-top:16px;color:var(--muted);font-size:12px}
|
||||
.chk{color:var(--warn);font-weight:600}
|
||||
@media print{body{background:#fff}.page{box-shadow:none;margin:0;max-width:100%;padding:0 8mm}nav{display:none}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<div class="doc-head">
|
||||
<div class="t1">발 명 신 고 서 · INVENTION DISCLOSURE</div>
|
||||
<h1>① 측량 측점 표고 기반 DEM-free 영상정합 방법<br>② 슬랜트거리 보존 역투영에 의한 단일 제스처 위치보정</h1>
|
||||
<div class="sub">변리사(특허사무소) 제출용 발명 설명자료 — 명세서·청구범위 작성 기초자료 · 대상 시스템: GhiVideo</div>
|
||||
</div>
|
||||
|
||||
<!-- 행정 정보 (출원인이 채울 것) -->
|
||||
<div class="formrow">
|
||||
<div class="cell"><div class="k">발명자</div><div class="v"><span class="fill">[성명/생년/주소 기재]</span></div></div>
|
||||
<div class="cell"><div class="k">출원인</div><div class="v"><span class="fill">[개인/법인/공동출원 — 권리귀속 확정 후 기재]</span></div></div>
|
||||
<div class="cell"><div class="k">작성일</div><div class="v">2026-06-23</div></div>
|
||||
<div class="cell"><div class="k">발명 완성일</div><div class="v"><span class="fill">[코드 최초 동작 확인일 기재]</span></div></div>
|
||||
<div class="cell" style="flex-basis:100%"><div class="k" style="flex-basis:120px">공개 여부</div><div class="v"><span class="fill">[미공개 / 공개일·공개처 기재]</span> — <b>출원 전 외부공개·시연·납품·논문 발표 여부를 반드시 확인(신규성 직결)</b></div></div>
|
||||
</div>
|
||||
|
||||
<div class="callout bad"><b>⚠️ 출원 전 필수 확인 2가지.</b>
|
||||
<b>(1) 신규성</b> — 이 발명을 출원 전에 외부에 공개(시연·납품·논문·블로그·판매)했다면 신규성 상실 위험. 공개 사실이 있으면 변리사에게 즉시 고지.
|
||||
<b>(2) 권리귀속</b> — 발주·용역 과제라면 발명자(개인)와 출원인(회사/발주처)의 권리 귀속, 직무발명·공동출원 여부를 출원 전에 정리.</div>
|
||||
|
||||
<nav>
|
||||
<b>목차</b>
|
||||
<a href="#field">기술분야</a>
|
||||
<a href="#bg">배경·종래문제</a>
|
||||
<a href="#prior">선행기술 조사</a>
|
||||
<a href="#invB">발명 ① (B)</a>
|
||||
<a href="#invC">발명 ② (C)</a>
|
||||
<a href="#figs">도면 목록</a>
|
||||
<a href="#claimpts">청구 포인트</a>
|
||||
<a href="#todo">변리사 인계 체크리스트</a>
|
||||
</nav>
|
||||
|
||||
<!-- 기술분야 -->
|
||||
<section id="field">
|
||||
<h2>1. 기술분야</h2>
|
||||
<p>본 발명은 이동체(드론·차량)가 철도·도로 노선을 따라 촬영한 <b>주행영상</b> 위에 측량 측점·시설물(POI)·선로중심선 등
|
||||
<b>지리객체를 실시간 정합(증강현실 오버레이)</b>하여 표시하는 영상 플레이어 기술에 관한 것이다. 구체적으로,
|
||||
① 대상의 표고/수치표고모델(DEM)이 없는 상황에서 정합 정확도를 확보하는 <b>고도 정합 방법</b>(발명 B)과,
|
||||
② 정합된 객체의 위치 오차를 사용자가 화면에서 <b>단일 드래그 제스처로 3차원 보정</b>하는 방법(발명 C)에 관한 것이다.</p>
|
||||
</section>
|
||||
|
||||
<!-- 배경 -->
|
||||
<section id="bg">
|
||||
<h2>2. 배경기술 및 종래기술의 문제점</h2>
|
||||
<p>프레임별 카메라 위치·자세(GPS/IMU)를 이용해 GIS 객체를 영상에 투영하는 정합 기술은 공지되어 있다(아래 §3 선행기술 참조).
|
||||
그러나 철도·도로 시설점검용 주행영상 정합에서는 다음의 구체적 문제가 남는다.</p>
|
||||
|
||||
<h4>문제 1 — 대상 표고/DEM 부재로 인한 수직 정합 오차 (발명 B가 해결)</h4>
|
||||
<ul>
|
||||
<li>화면에 표시할 시설물(POI)은 주소 기반 데이터가 많아 <b>표고(높이) 값이 없는</b> 경우가 대부분이다.</li>
|
||||
<li>표고가 없으면 카메라와 대상 간 수직각(부각)을 산출할 수 없어, 종래기술은 <b>지면을 평평한 면(flat plane)으로 가정</b>한다.</li>
|
||||
<li>노선은 실제로 기복이 있어(예: 정표고 41.6m→57.5m로 상승), 평면 가정 시 라벨이 화면 밖/지면 아래로 이탈한다.
|
||||
실측에서 잘못된 고도(≈0) 사용 시 <b>부각오차가 42.7°</b>에 달했다.</li>
|
||||
<li>별도 DEM/LiDAR를 취득·정합하는 것은 비용·datum 정합 부담이 크다.</li>
|
||||
</ul>
|
||||
|
||||
<h4>문제 2 — 정합 객체 위치오차의 3차원 수동보정 곤란 (발명 C가 해결)</h4>
|
||||
<ul>
|
||||
<li>주소 지오코딩 POI는 ±수십 m 오차가 있어, 정합 후에도 객체가 실제 위치와 어긋난다.</li>
|
||||
<li>사용자가 화면에서 드래그로 고치려 해도, <b>2D 화면 이동만으로는 카메라로부터의 거리(깊이)를 결정할 수 없다</b>(깊이 모호성).
|
||||
따라서 종래에는 수평위치와 높이를 따로 조정하거나, 별도의 깊이맵(LiDAR)을 요구한다.</li>
|
||||
<li>종래 정합 연구는 사용자 수동보정을 "부담이 커서 비현실적"이라며 포기하기도 한다.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- 선행기술 -->
|
||||
<section id="prior">
|
||||
<h2>3. 선행기술 조사 결과 (요약)</h2>
|
||||
<p class="lead">글로벌(Google Patents·USPTO·Esri·논문) 및 한국(Google Patents KR) 조사 후 교차검증한 결과. 상세·출처는
|
||||
별첨 <a href="특허_스테이션기반_주행영상_플레이어.html">특허 발굴 기술문서 §6</a> 참조. <b>KIPRIS 전문검색은 변리사 단계에서 보강 필요.</b></p>
|
||||
<table>
|
||||
<thead><tr><th style="width:24%">선행기술</th><th>개시 내용</th><th style="width:30%">본 발명과의 차이(신규 지점)</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Esri ArcGIS Pro FMV · US9996976B2 · Sensors 2018(PMC6263998)</td>
|
||||
<td>프레임별 카메라자세로 GIS 객체를 영상에 정합(AR 오버레이)하는 일반 기술 — <b>공지</b></td>
|
||||
<td>"정합 일반"은 공지. 본 발명은 그 위에서 <b>고도원(B)·보정수단(C)</b>이라는 구체적 개량.</td></tr>
|
||||
<tr><td>Sensors 2018 (DEM-free 처리)</td>
|
||||
<td>DEM 없을 때 <b>평면으로 대체</b>, 지오이드/정표고 변환 안 함</td>
|
||||
<td><b>측점 정표고 수열을 지면모델로 전용 + 지오이드 datum 정합</b> 미개시 → 발명 B 신규.</td></tr>
|
||||
<tr><td>Google US9188444B2 ("3D object positioning in street view")</td>
|
||||
<td>스트리트뷰에서 객체 드래그→lat/lon, 단 <b>저장된 픽셀별 depth map(LiDAR) 조회</b>로 깊이 결정. <b>한국 패밀리 없음</b></td>
|
||||
<td>본 발명은 <b>depth map 없이 슬랜트거리 보존</b>으로 깊이 해소 → 발명 C 신규.</td></tr>
|
||||
<tr><td>KR102422292B1 (김동욱)</td>
|
||||
<td>역투영 + 사용자보정이나 <b>두 영상 + 높이값 반복조정</b>으로 깊이 결정</td>
|
||||
<td>본 발명은 <b>단안·단일 제스처·거리보존</b> → 메커니즘 상이.</td></tr>
|
||||
<tr><td>KR102268318B1(트라웍스)·KR100411587B1(ETRI)·ENSCO VTW</td>
|
||||
<td>위치 동기 비교재생 / 위치→영상 색인 / milepost 영상색인</td>
|
||||
<td>발명 B·C와 직접 관련 낮음(이들은 후보 A 관련 — 본 신고서 범위 외).</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- 공통: 투영 파이프라인 -->
|
||||
<section>
|
||||
<h2>4. 공통 기반 — 영상정합 투영 파이프라인</h2>
|
||||
<p>발명 B·C가 공통으로 사용하는 좌표 변환(검증 완료). 발명자 자체 구현이며, Python 원본과 1:1 일치 검증됨.</p>
|
||||
<pre><span class="c"># 입력: 프레임별 드론 위치(lat/lon/alt)·자세(yaw/pitch/roll), 대상(lat/lon/z)</span>
|
||||
1) WGS84(lat,lon) → EPSG:5186 TM(easting,northing[m]) <span class="c"># proj4</span>
|
||||
2) rel = target_ENU − drone_ENU <span class="c"># 상대벡터[m]</span>
|
||||
3) R_b2w = Rz(-yaw)·Rx(pitch)·Ry(roll), R_w2c = R_align·R_b2wᵀ
|
||||
4) (Xc,Yc,Zc) = R_w2c · rel <span class="c"># 카메라좌표</span>
|
||||
5) px = 0.5 + (Xc/Zc)·(f/sensorW), py = 0.5 + (Yc/Zc)·(f/sensorH) <span class="c"># 핀홀 투영</span></pre>
|
||||
<p class="lead">실시 카메라(검증 데이터): 3840×2160, 29.97fps(=30000/1001), focal 24mm, sensor 36×20.25mm, pitch −29.8°(고정).</p>
|
||||
</section>
|
||||
|
||||
<!-- 발명 B -->
|
||||
<section id="invB">
|
||||
<h2 class="inv"><span class="tag g">발명 ①</span> 측량 측점 표고 기반 DEM-free 영상정합 방법 <span class="tag g">특허성: 상</span></h2>
|
||||
|
||||
<h3>4-1. 해결하고자 하는 과제</h3>
|
||||
<p>대상(POI)의 표고/DEM이 없을 때, 별도 DEM 취득 없이 <b>노선 기복을 반영한 지면고도</b>를 부여하여 카메라 고도와
|
||||
datum을 정합함으로써 수직 정합오차를 제거한다.</p>
|
||||
|
||||
<h3>4-2. 해결수단 (구성)</h3>
|
||||
<ol>
|
||||
<li><span class="num g">1</span><b>측점 표고 수열 보유:</b> 노선을 따라 실측된 복수의 측점에 대해 위치(lat/lon)와
|
||||
<b>정표고(EL, 해수면 기준)</b>를 보유한다. (실데이터: 측점 47개, 정표고 41.6→57.5m로 노선 진행에 따라 가변)</li>
|
||||
<li><span class="num g">2</span><b>최근접 측점 지면고도 선택:</b> 표고 미상 대상에 대해, 노선상 <b>가장 가까운 측점의 정표고</b>를
|
||||
그 대상의 지면고도로 채택한다. 프레임 진행에 따라 최근접 측점이 바뀌므로 <b>지면고도가 노선 기복을 따라 갱신</b>된다.</li>
|
||||
<li><span class="num g">3</span><b>지오이드 datum 정합:</b> 정표고(EL)에 <b>지오이드고 N</b>을 가산하여 타원체고로 변환,
|
||||
드론/차량 GPS의 타원체고와 동일 기준으로 맞춘다. <code>rel[2] = (targetZ + N) − droneAlt</code></li>
|
||||
<li><span class="num g">4</span><b>정합 투영:</b> datum 정합된 고도로 §4 파이프라인을 수행해 화면에 표지를 중첩한다.</li>
|
||||
<li><span class="num g">5</span><b>(종속) 대상별 표고보정 핸들:</b> 건물높이·지반차 보정을 위한 대상 전용 오프셋(poiZOffset)을 별도 제공하되,
|
||||
측점·중심선의 정확 표고에는 미적용한다.</li>
|
||||
</ol>
|
||||
|
||||
<h4>핵심 수식·실시값</h4>
|
||||
<pre><span class="c"># 지오이드 보정(전체 대상 공통)</span>
|
||||
타원체고 = 정표고(EL) + 지오이드고(N) <span class="c"># 대전지역 N ≈ 25.8m (검증)</span>
|
||||
rel_up = (targetZ + N) − droneAlt_ellipsoidal
|
||||
|
||||
<span class="c"># 검증(측점 157K900, frame0, 수평거리 91.3m)</span>
|
||||
정표고≈0 사용 → 수직차 −84.2m, 부각 42.7° ❌
|
||||
정표고 41.5 사용 → 수직차 −42.7m, 부각 25.1°
|
||||
정표고+N≈67 사용 → 수직차 −17.7m, 부각 11.0° ✅ (드론 rel_alt 16.85m와 일치)</pre>
|
||||
|
||||
<h3>4-3. 효과 (정량·검증됨)</h3>
|
||||
<table>
|
||||
<thead><tr><th>지표</th><th>종래(평면/표고0)</th><th>본 발명</th><th>비고</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>부각오차</td><td>42.7°</td><td><b>11.0°</b></td><td>드론 실측 고도와 일치</td></tr>
|
||||
<tr><td>지면고도 정확도</td><td>—</td><td><b>구글어스 DEM 대비 0.2m</b></td><td>신대천교: 측점방식 41.26m vs 구글 41.4566m (≈0.13°, 2160p에서 ~6px)</td></tr>
|
||||
<tr><td>DEM 취득비용</td><td>DEM 필요</td><td><b>불필요</b></td><td>측량 산출물(측점) 재활용</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>4-4. 실시예</h3>
|
||||
<p>(하행)회덕–대전조차장 구간, 드론 SRT 텔레메트리(프레임별 lat/lon/alt/yaw/pitch/roll) + 측량 측점 CSV(<code>Z좌표_한국</code>=정표고)
|
||||
+ POI CSV(표고 없음). 측점 정표고를 지면모델로, 지오이드 25.8m 가산하여 정합 → 부각 11° 달성, 구글 DEM과 0.2m 일치.
|
||||
공개 DEM(SRTM 30m)보다 측량 측점이 더 정밀하여 회귀 위험도 없음.</p>
|
||||
</section>
|
||||
|
||||
<!-- 발명 C -->
|
||||
<section id="invC">
|
||||
<h2><span class="tag">발명 ②</span> 슬랜트거리 보존 역투영에 의한 단일 제스처 위치보정 <span class="tag">특허성: 상</span></h2>
|
||||
|
||||
<h3>5-1. 해결하고자 하는 과제</h3>
|
||||
<p>정합된 객체가 지오코딩 오차로 어긋났을 때, 사용자가 <b>단일 화면 드래그</b>로 객체의 수평·수직(3D) 위치를
|
||||
동시에 보정하도록 한다. 단, 별도 깊이맵(LiDAR)에 의존하지 않는다.</p>
|
||||
|
||||
<h3>5-2. 해결수단 (구성)</h3>
|
||||
<ol>
|
||||
<li><span class="num">1</span><b>슬랜트거리 보존:</b> 객체 표지의 드래그 개시 시점에 카메라–객체 간 <b>슬랜트 거리(range)를 고정값으로 보존</b>한다.
|
||||
이것이 2D→3D 깊이 모호성을 해소하는 핵심 구속이다.</li>
|
||||
<li><span class="num">2</span><b>역투영:</b> 드롭한 화면 픽셀을 카메라 방향벡터로 환산하고 보존된 거리만큼 투사하여 카메라좌표를 정한다.</li>
|
||||
<li><span class="num">3</span><b>월드좌표 복원:</b> 자세 회전·평행이동의 역변환으로 lat/lon/z를 복원한다(투영 시 가산한 지오이드를 역으로 환원).
|
||||
<b>한 번의 드래그로 수평·수직이 동시 복원</b>된다.</li>
|
||||
<li><span class="num">4</span><b>영속·재적용:</b> 복원 좌표를 객체 식별자에 연계해 외부 보정파일(override)로 저장, 차기 로드시 자동 적용한다.</li>
|
||||
<li><span class="num">5</span><b>(종속) 가시영역 한정:</b> 화면에 보이는(카메라 전방, Zc>0) 객체에만 보정을 허용한다.</li>
|
||||
</ol>
|
||||
|
||||
<h4>핵심 수식 (역투영, 왕복오차 ≈0 검증)</h4>
|
||||
<pre>dir_cam ∝ ((px−0.5−cx0)·sW/f, (py−0.5−cy0)·sH/f, 1) <span class="c"># 픽셀→카메라 방향</span>
|
||||
cam = dir_cam/|dir_cam| · <b>range</b> <span class="c"># ★거리 보존(드래그 시작값)</span>
|
||||
rel = R_w2cᵀ · cam <span class="c"># 회전 역변환</span>
|
||||
E = droneE + offX + rel.x, N = droneN + offY + rel.y
|
||||
z = droneAlt + offZ + rel.up − N_geoid <span class="c"># 정표고로 복원</span>
|
||||
(lat,lon) = TM⁻¹(E,N)</pre>
|
||||
|
||||
<h3>5-3. 효과</h3>
|
||||
<ul>
|
||||
<li>2D 드래그 한 번으로 <b>3D(수평+수직) 동시 보정</b> → 보정 단계 절반.</li>
|
||||
<li>역투영 왕복오차 <b>≈0</b>(가시영역 Zc>0 검증).</li>
|
||||
<li>깊이맵(LiDAR) 불요 — 단안 영상 + 자세만으로 동작.</li>
|
||||
<li>보정의 파일 영속·가져오기/내보내기로 <b>재현성</b> 확보.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5-4. 실시예</h3>
|
||||
<p>지오코딩 오차로 선로 위에 잘못 표시된 POI(예: "88자원")를 편집모드에서 드래그 → 드롭 픽셀을 보존거리로 역투영하여
|
||||
올바른 lat/lon/z 복원 → <code><base>_poi_overrides.json</code> 저장 → 폴더 재로드 시 자동 반영.</p>
|
||||
</section>
|
||||
|
||||
<!-- 도면 -->
|
||||
<section id="figs">
|
||||
<h2>6. 도면 목록 (변리사 제도용 — 발명자 확인)</h2>
|
||||
<div class="fig"><b>도 1.</b> 시스템 구성도 — 영상소스, 텔레메트리, 측점/POI 카탈로그, 정합부, 표시부.</div>
|
||||
<div class="fig"><b>도 2.</b> 투영 파이프라인 순서도 — WGS84→TM→ENU→카메라→핀홀(§4).</div>
|
||||
<div class="fig"><b>도 3. (발명 B)</b> 측점 정표고→최근접 선택→지오이드 가산→타원체고 datum 정합 흐름도 + 부각오차 42.7°→11° 비교 기하도.</div>
|
||||
<div class="fig"><b>도 4. (발명 B)</b> 노선 진행에 따른 최근접 측점 갱신 개념도(지면고도가 기복을 따라감).</div>
|
||||
<div class="fig"><b>도 5. (발명 C)</b> 슬랜트거리 보존 역투영 기하도 — 드래그 개시점 거리 R 고정, 드롭픽셀 방향벡터 × R.</div>
|
||||
<div class="fig"><b>도 6. (발명 C)</b> 드래그→역투영→override 저장→재적용 시퀀스도.</div>
|
||||
<div class="fig"><b>도 7.</b> 정합 화면 예시(라벨 중첩 캡처) — <span class="chk">[실제 스크린샷 첨부 필요]</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 청구 포인트 -->
|
||||
<section id="claimpts">
|
||||
<h2>7. 청구범위 핵심 포인트 (변리사 작성 가이드)</h2>
|
||||
<div class="callout key"><b>발명 B 독립항 필수요소:</b> ① 노선 측점의 <b>실측 정표고 수열 보유</b> → ② 표고미상 대상에 <b>최근접 측점 정표고를 지면고도로 채택</b>(노선기복 반영) → ③ <b>지오이드고 가산으로 타원체고 변환·카메라 datum 정합</b> → ④ 정합 투영. <b>※ ②+③ 결합을 반드시 한 청구항에</b>(개별요소는 공지).</div>
|
||||
<div class="callout key"><b>발명 C 독립항 필수요소:</b> ① 드래그 개시 시 <b>슬랜트거리 보존</b> → ② 드롭픽셀 역투영(보존거리 투사) → ③ <b>단일 제스처로 수평·수직 동시 복원</b> → ④ 식별자 연계 영속. <b>※ "depth map 없이 거리보존"</b>을 명시하여 US9188444B2와 구별.</div>
|
||||
<ul>
|
||||
<li><b>카테고리:</b> B = 방법항 + 매체항, C = 시스템(장치)항 (필요시 방법항 병행).</li>
|
||||
<li><b>종속항 후보(방어용):</b> 비등방 거리필터(앞/옆 분해, 60m/40m), 사전계산 labelMap+보간, title-키 보간·이상치거부, 대상별 표고오프셋, 가시영역(Zc>0) 한정.</li>
|
||||
<li><b>미국 병행 시(§101 대비):</b> 부각오차 42.7°→11°, DEM 대비 0.2m 등 <b>구체적 기술개선 효과</b>를 명세서에 명시.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- 체크리스트 -->
|
||||
<section id="todo">
|
||||
<h2>8. 변리사 인계 전 체크리스트 (발명자 작업)</h2>
|
||||
<table>
|
||||
<thead><tr><th style="width:34px">☐</th><th>항목</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>☐</td><td><b>권리귀속 확정</b> — 발명자/출원인, 직무발명·공동출원 여부(발주처 계약서 확인)</td></tr>
|
||||
<tr><td>☐</td><td><b>공개이력 확인</b> — 출원 전 시연·납품·논문·전시 여부와 일자(신규성 직결)</td></tr>
|
||||
<tr><td>☐</td><td><b>발명 완성일</b> — 코드 최초 동작·검증 확인 일자(증빙: 커밋 로그·히스토리 문서)</td></tr>
|
||||
<tr><td>☐</td><td><b>실시예 스크린샷</b> — 정합 화면(도 7) 캡처 1~2장</td></tr>
|
||||
<tr><td>☐</td><td><b>검증 데이터</b> — 부각 42.7→11°, 0.2m 일치 산출 근거(이미 히스토리 문서에 있음)</td></tr>
|
||||
<tr><td>☐</td><td><b>변리사 선임</b> — 첫 출원은 변리사 강력 권장. 비용지원: 특허청 공익변리·지자체/TP 출원비 지원·중소기업 감면</td></tr>
|
||||
<tr><td>☐</td><td><b>KIPRIS 전문검색 의뢰</b> — 변리사에 정밀 선행기술조사(특히 KRRI·코레일·국가철도공단·도로공사·LX 명의) 요청</td></tr>
|
||||
<tr><td>☐</td><td><b>출원 우선순위</b> — B+C 1건 우선. A는 별건·좁은 청구로 후속 검토</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="callout ok"><b>일정 감각(참고):</b> 변리사 선임→자료전달→명세서 초안→검토·보정→출원까지 통상 <b>3~6주</b>.
|
||||
출원 후 심사청구(출원일로부터 3년 내, 보통 동시), 심사·등록까지 <b>1~2년</b>. 해외출원 필요 시 <b>최초 출원일로부터 12개월 내</b> PCT/개별국(우선권).</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
발명신고서 (변리사 제출용) · 발명 B(측점표고 DEM-free 정합)·C(슬랜트거리 보존 역투영 보정) · 작성 2026-06-23 ·
|
||||
기술근거: 구현 코드 및 개발 히스토리(POI 투영오차 개선 등) · 별첨: <a href="특허_스테이션기반_주행영상_플레이어.html">특허 발굴 기술문서(후보 A·D·E 및 선행기술 상세 포함)</a> ·
|
||||
본 문서는 변리사 명세서 작성을 위한 기술 설명자료이며 법적 의견이 아님. <span class="fill">[ ]</span> 부분은 발명자/출원인이 기재.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,564 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>스테이션(측점) 기반 주행영상 플레이어 — 특허 발굴 기술문서</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1115; --panel:#161a22; --panel2:#1c212c; --line:#2a3140;
|
||||
--text:#e6e9ef; --muted:#9aa4b2; --accent:#4ea1ff; --accent2:#7ee787;
|
||||
--warn:#ffcc66; --bad:#ff7b72; --good:#7ee787; --mid:#ffcc66;
|
||||
--chip:#222a36;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html{scroll-behavior:smooth}
|
||||
body{
|
||||
margin:0; background:var(--bg); color:var(--text);
|
||||
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Apple SD Gothic Neo","Noto Sans KR",Roboto,"Malgun Gothic",sans-serif;
|
||||
line-height:1.7; font-size:15px;
|
||||
}
|
||||
.wrap{max-width:1080px; margin:0 auto; padding:0 24px 120px}
|
||||
header.hero{
|
||||
background:linear-gradient(135deg,#1b2433,#11151d 60%);
|
||||
border-bottom:1px solid var(--line); padding:48px 24px 36px; margin-bottom:8px;
|
||||
}
|
||||
header.hero .inner{max-width:1080px;margin:0 auto}
|
||||
.kicker{color:var(--accent);font-weight:700;letter-spacing:.12em;font-size:12px;text-transform:uppercase}
|
||||
h1{font-size:30px;line-height:1.3;margin:10px 0 8px}
|
||||
.sub{color:var(--muted);font-size:15px;max-width:760px}
|
||||
.meta{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px}
|
||||
.meta .m{background:var(--chip);border:1px solid var(--line);border-radius:999px;padding:5px 12px;font-size:12.5px;color:var(--muted)}
|
||||
.meta .m b{color:var(--text);font-weight:600}
|
||||
|
||||
nav.toc{
|
||||
position:sticky;top:0;z-index:5;background:rgba(15,17,21,.92);backdrop-filter:blur(6px);
|
||||
border-bottom:1px solid var(--line);padding:10px 0;margin-bottom:28px
|
||||
}
|
||||
nav.toc .inner{max-width:1080px;margin:0 auto;padding:0 24px;display:flex;flex-wrap:wrap;gap:6px}
|
||||
nav.toc a{color:var(--muted);text-decoration:none;font-size:13px;padding:5px 10px;border-radius:7px}
|
||||
nav.toc a:hover{background:var(--panel2);color:var(--text)}
|
||||
|
||||
section{margin:38px 0;scroll-margin-top:64px}
|
||||
h2{font-size:22px;border-left:4px solid var(--accent);padding-left:12px;margin:0 0 8px}
|
||||
h2 .num{color:var(--accent);font-weight:800;margin-right:8px}
|
||||
h3{font-size:17px;margin:26px 0 8px;color:#cdd6e2}
|
||||
p{margin:10px 0}
|
||||
.lead{color:var(--muted)}
|
||||
|
||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px 22px;margin:16px 0}
|
||||
.card.tight{padding:16px 18px}
|
||||
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
||||
@media(max-width:760px){.grid2{grid-template-columns:1fr}}
|
||||
|
||||
table{width:100%;border-collapse:collapse;margin:14px 0;font-size:13.8px}
|
||||
th,td{border:1px solid var(--line);padding:9px 11px;vertical-align:top;text-align:left}
|
||||
th{background:var(--panel2);color:#cdd6e2;font-weight:600}
|
||||
tbody tr:nth-child(odd){background:rgba(255,255,255,.012)}
|
||||
td code,p code,li code{background:#0c0f14;border:1px solid var(--line);border-radius:5px;padding:1px 6px;font-size:12.5px;color:#9ad0ff;font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace}
|
||||
|
||||
.badge{display:inline-block;font-size:11.5px;font-weight:700;padding:2px 9px;border-radius:999px;border:1px solid transparent}
|
||||
.b-high{background:rgba(126,231,135,.13);color:var(--good);border-color:rgba(126,231,135,.35)}
|
||||
.b-mid{background:rgba(255,204,102,.13);color:var(--mid);border-color:rgba(255,204,102,.35)}
|
||||
.b-low{background:rgba(255,123,114,.13);color:var(--bad);border-color:rgba(255,123,114,.35)}
|
||||
.chk{color:var(--warn);font-weight:600}
|
||||
|
||||
.flow{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:13px;color:var(--muted);margin:8px 0}
|
||||
.flow .node{background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:7px 11px;color:var(--text)}
|
||||
.flow .arr{color:var(--accent)}
|
||||
|
||||
ol.steps{counter-reset:s;list-style:none;padding-left:0;margin:10px 0}
|
||||
ol.steps>li{counter-increment:s;position:relative;padding:10px 0 10px 44px;border-bottom:1px dashed var(--line)}
|
||||
ol.steps>li:last-child{border-bottom:0}
|
||||
ol.steps>li::before{content:counter(s);position:absolute;left:0;top:9px;width:28px;height:28px;border-radius:50%;
|
||||
background:var(--accent);color:#04121f;font-weight:800;display:flex;align-items:center;justify-content:center;font-size:13px}
|
||||
|
||||
.claim{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px 20px;margin:14px 0}
|
||||
.claim .ctag{font-size:12px;font-weight:700;color:var(--accent);text-transform:uppercase;letter-spacing:.08em}
|
||||
.claim ol{margin:8px 0 0;padding-left:22px}
|
||||
.claim li{margin:7px 0}
|
||||
.claim .ind{font-weight:600;color:#e6e9ef}
|
||||
|
||||
.callout{border-left:4px solid var(--warn);background:rgba(255,204,102,.06);padding:12px 16px;border-radius:0 10px 10px 0;margin:14px 0;color:#e8e2cf;font-size:14px}
|
||||
.callout.bad{border-color:var(--bad);background:rgba(255,123,114,.06);color:#f0d6d3}
|
||||
.callout.ok{border-color:var(--good);background:rgba(126,231,135,.06);color:#d6f0d8}
|
||||
|
||||
.pri{display:flex;gap:14px;align-items:flex-start;padding:14px 0;border-bottom:1px solid var(--line)}
|
||||
.pri:last-child{border-bottom:0}
|
||||
.pri .rank{flex:0 0 auto;width:54px;height:54px;border-radius:12px;background:linear-gradient(135deg,#23304a,#18202e);
|
||||
border:1px solid var(--line);display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:800;color:var(--accent)}
|
||||
.pri .rank small{display:block;font-size:9px;color:var(--muted);font-weight:600}
|
||||
|
||||
.kw{display:flex;flex-wrap:wrap;gap:7px;margin:8px 0}
|
||||
.kw span{background:var(--chip);border:1px solid var(--line);border-radius:7px;padding:4px 10px;font-size:12.5px;color:#cdd6e2}
|
||||
.ipc{font-family:"SFMono-Regular",Consolas,monospace;color:var(--accent2)}
|
||||
|
||||
footer{color:var(--muted);font-size:12.5px;border-top:1px solid var(--line);padding-top:18px;margin-top:50px}
|
||||
.disc{font-size:12.5px;color:var(--muted);background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px}
|
||||
a.ref{color:var(--accent);text-decoration:none}
|
||||
a.ref:hover{text-decoration:underline}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hero">
|
||||
<div class="inner">
|
||||
<div class="kicker">Patent Mining · Technical Disclosure</div>
|
||||
<h1>스테이션(측점) 기반 주행영상 플레이어<br>특허 발굴 기술문서</h1>
|
||||
<p class="sub">시간축이 아니라 <b>노선 위치(측점·체이니지)</b>를 1차 좌표로 삼아 철도·도로 주행영상을 색인·탐색하고,
|
||||
프레임별 카메라 자세를 이용해 측량 측점·시설물(POI)·선로중심선을 영상 위에 실시간 정합(geo-registration)하는
|
||||
플레이어를 대상으로, 특허 출원 가치가 있는 기술 요소를 발굴·평가한다.</p>
|
||||
<div class="meta">
|
||||
<span class="m"><b>대상 시스템</b> GhiVideo (드론 주행영상 + 측량 데이터 정합 플레이어)</span>
|
||||
<span class="m"><b>작성일</b> 2026-06-22</span>
|
||||
<span class="m"><b>근거</b> 구현 코드·개발 히스토리 기반</span>
|
||||
<span class="m"><b>분석 절차</b> 5단계 (인벤토리→필터→후보→청구항→선행조사)</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="toc">
|
||||
<div class="inner">
|
||||
<a href="#bg">배경</a>
|
||||
<a href="#s1">① 기술요소 인벤토리</a>
|
||||
<a href="#s2">② 특허적격 필터</a>
|
||||
<a href="#s3">③ 발명 후보</a>
|
||||
<a href="#s4">④ 청구항 초안</a>
|
||||
<a href="#s5">⑤ 선행기술 키워드</a>
|
||||
<a href="#prior">⑥ 선행기술 조사결과</a>
|
||||
<a href="#concl">결론·우선순위</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<!-- 배경 -->
|
||||
<section id="bg">
|
||||
<h2>발명의 배경 — 시간기반 vs 스테이션기반</h2>
|
||||
<p class="lead">종래의 영상 플레이어는 모두 <b>시간축(timeline)</b>을 유일한 탐색 좌표로 사용한다. 그러나 철도·도로
|
||||
시설을 점검·분석하기 위해 차량/드론으로 노선을 따라 촬영한 <b>주행영상</b>에서 의미 있는 단위는 "몇 분 몇 초"가
|
||||
아니라 <b>"어느 측점(milepost/chainage), 어느 역, 어느 교량"</b> 이라는 <b>공간 위치</b>이다.</p>
|
||||
|
||||
<div class="grid2">
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:4px">종래 시간기반 플레이어의 한계</h3>
|
||||
<ul>
|
||||
<li>탐색 단위가 시간 → "157K900 측점 교량"으로 바로 이동 불가</li>
|
||||
<li>주행 속도가 가변(가감속·정차)이라 <b>시간 ↔ 위치 관계가 비선형</b> → 같은 지점도 영상마다 다른 시각</li>
|
||||
<li>영상 위 주석이 시각(time)에 고정 → 다른 회차 영상엔 재사용 불가</li>
|
||||
<li>화면 속 시설물이 "무엇/어디"인지 식별 정보 없음</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:4px">본 시스템의 접근</h3>
|
||||
<ul>
|
||||
<li>프레임마다 <code>lat/lon/alt/yaw/pitch/roll</code> 텔레메트리를 보유</li>
|
||||
<li>측량 <b>측점(체이니지)</b>·시설물(POI)·선로중심선을 <b>지리좌표</b>로 카탈로그화</li>
|
||||
<li>핀홀 카메라 모델로 지리객체를 <b>영상 픽셀에 투영</b>(증강현실식 라벨)</li>
|
||||
<li>탐색·미니맵·타임라인을 <b>측점/㎞</b> 기준으로 제공</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>시스템 데이터 흐름</h3>
|
||||
<div class="flow">
|
||||
<span class="node">프레임별 텔레메트리<br>(드론 SRT/CSV)</span><span class="arr">→</span>
|
||||
<span class="node">자세 평활<br>smoothFrame(±N)</span><span class="arr">→</span>
|
||||
<span class="node">좌표 투영<br>WGS84→TM→ENU→카메라→픽셀</span><span class="arr">→</span>
|
||||
<span class="node">labelMap 사전계산</span><span class="arr">→</span>
|
||||
<span class="node">RAF 60fps 보간·정합 렌더</span>
|
||||
</div>
|
||||
<p class="disc">투영 파이프라인(검증): ① WGS84(lat/lon) → EPSG:5186 TM(m), ② 대상−카메라 상대 ENU 벡터,
|
||||
③ 회전 <code>R_w2c = R_align·R_b2wᵀ</code>(yaw/pitch/roll), ④ 카메라좌표, ⑤ 핀홀 투영
|
||||
<code>px = 0.5 + (Xc/Zc)·(f/sensorW)</code>. 측량 측점 정표고 + 지오이드 보정으로 datum 정합(드론 타원체고와 일치).</p>
|
||||
</section>
|
||||
|
||||
<!-- 1 -->
|
||||
<section id="s1">
|
||||
<h2><span class="num">①</span>기술요소 인벤토리</h2>
|
||||
<p class="lead">구현 코드와 개발 히스토리에서 식별한, 자체 고안된 기술 메커니즘 목록. (단순 라이브러리 사용·UI는 제외)</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th style="width:30px">#</th><th style="width:22%">기술 요소</th><th>핵심 메커니즘</th><th style="width:18%">입력→변환→출력</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>E1</td><td><b>측점(체이니지) 기반 영상 색인·탐색</b></td>
|
||||
<td>프레임을 시각이 아니라 노선 <b>측점/㎞</b>로 색인. 미니맵·타임라인·seek가 측점 단위로 동작(높은 ㎞=위, 낮은 ㎞=아래 등 노선좌표 1차원화).</td>
|
||||
<td>측점 좌표+프레임 텔레메트리 → 측점↔프레임 매핑 → 측점 클릭 seek</td></tr>
|
||||
|
||||
<tr><td>E2</td><td><b>지리객체 실시간 영상 정합(geo-registration overlay)</b></td>
|
||||
<td>프레임별 카메라 자세로 측점/POI/구조물/중심선을 핀홀 모델로 영상 픽셀에 투영, 라벨로 표출. 전 프레임×전 대상 화면좌표를 <code>labelMap</code>으로 사전계산 후 RAF에서 조회.</td>
|
||||
<td>지리좌표+자세 → TM/ENU/카메라/픽셀 → 화면 라벨</td></tr>
|
||||
|
||||
<tr><td>E3</td><td><b>측량 측점 표고 기반 DEM-free 지면고도 추정</b></td>
|
||||
<td>POI에 표고 데이터가 없을 때, <b>최근접 측량 측점의 실측 정표고 + 지오이드고</b>로 대상 고도를 추정(드론 타원체고와 datum 일치). 공개 DEM 없이도 구글어스 지면고도와 <b>0.2m 일치</b> 검증.</td>
|
||||
<td>측점 정표고열 → 최근접 보간+지오이드 → 대상 타원체고</td></tr>
|
||||
|
||||
<tr><td>E4</td><td><b>역투영+슬랜트거리 보존 단일드래그 위치보정</b></td>
|
||||
<td>지오코딩 오차 POI를 마우스로 끌면, 놓은 픽셀을 <b>역투영</b>해 월드좌표 복원. 2D→3D 깊이 모호성을 <b>드래그 시작 시 슬랜트 거리 고정</b>으로 해소 → 한 번의 드래그로 <b>수평·수직 동시 보정</b>. 보정값은 외부 JSON으로 영속·재적용.</td>
|
||||
<td>드롭 픽셀+거리유지 → 카메라방향 역회전 → lat/lon/z 복원 → override 파일</td></tr>
|
||||
|
||||
<tr><td>E5</td><td><b>비등방(진행방향 앞/옆 분리) 거리필터 + 소실점 클러터 억제</b></td>
|
||||
<td>먼 대상은 부각 0°로 소실점에 몰려 선로 위에 겹침. 상대벡터를 yaw 기준 <b>전방(fwd)·측방(side)으로 분해</b>해 각각 임계 적용(앞 멀리/옆 좁게), 슬랜트거리 컷으로 원거리 클러터 제거.</td>
|
||||
<td>상대ENU+yaw → fwd/side 분해 → 임계 컷 → 표시 대상 선별</td></tr>
|
||||
|
||||
<tr><td>E6</td><td><b>노이즈 강건 실시간 오버레이(이중평활·title짝짓기·이상치거부)</b></td>
|
||||
<td>입력 자세 ±N프레임 이동평균 + 화면좌표 EMA(이중평활)로 GPS/자세 노이즈 억제. 프레임 간 보간을 <b>인덱스가 아닌 라벨 title로 짝지어</b> 필터 출입에 따른 오정합 방지. 점프 임계 초과 시 <b>이상치 거부</b>(연속 N회 후 수용).</td>
|
||||
<td>거친 좌표열 → 평활/보간/거부 → 60fps 안정 라벨</td></tr>
|
||||
|
||||
<tr><td>E7</td><td>측량 측점 폴리라인을 선로중심선으로 생성</td>
|
||||
<td>별도 중심선 파일 부재 시 측점 mileage 정렬 폴리라인을 중심선으로 사용, 동일 투영으로 영상에 중첩.</td>
|
||||
<td>측점열 → mileage 정렬 폴리라인 → 중심선 투영</td></tr>
|
||||
|
||||
<tr><td>E8</td><td>이종 측량 CSV 융합 파이프라인(인코딩 자동감지)</td>
|
||||
<td>BOM 자동감지(UTF-8/EUC-KR), 헤더명 실패 시 위치 폴백, 다수 번호 CSV(측점/지장물/교량/터널/구교) 병합 + route.json 보정.</td>
|
||||
<td>혼재 CSV → 정규화 파싱/병합 → 통합 지리 모델</td></tr>
|
||||
|
||||
<tr><td>E9</td><td>측점 비고 텍스트에서 상·하행 방향전환 추출</td>
|
||||
<td>측점 비고의 자연어/시각(mm:ss)을 정규식으로 파싱해 <code>DirectionChange</code> 이벤트 생성.</td>
|
||||
<td>비고 텍스트 → 정규식 추출 → 방향전환 타임라인</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- 2 -->
|
||||
<section id="s2">
|
||||
<h2><span class="num">②</span>특허 적격성 1차 필터</h2>
|
||||
<p class="lead">각 요소를 기술적 과제 / 해결수단의 구체성 / 비자명성 / 측정가능 효과로 평가. (단순 구현·비즈니스 로직 자체는 제외)</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th style="width:8%">요소</th><th style="width:24%">기술적 과제</th><th style="width:30%">해결수단(구체성)</th><th style="width:20%">비자명성 근거</th><th>측정가능 효과</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><b>E1</b></td><td>가변속 주행으로 시간↔위치가 비선형 → 위치단위 탐색 불가</td><td>프레임-측점 매핑 테이블 + 측점/㎞ 1차원 노선좌표 색인·seek</td><td>"위치를 1차 탐색축으로" 자체는 아이디어; 비선형 매핑 구축·동기화 방식이 관건</td><td>위치 직접 도달(검색 시간↓), 회차 간 동일위치 정렬</td></tr>
|
||||
<tr><td><b>E2</b></td><td>화면 속 시설이 무엇/어디인지 알 수 없음, 자세 노이즈 하 정합</td><td>프레임별 자세→TM/ENU/카메라/핀홀 투영 + 사전계산 labelMap</td><td>AR 정합 일반론은 공지; <b>철도 측량 측점/체이니지 도메인 결합</b>과 사전계산 구조가 차별점</td><td>시설 자동 식별·라벨, 60fps 정합</td></tr>
|
||||
<tr><td><b>E3</b></td><td>POI 표고/DEM 부재 → 수직 정합 오차(수십°)</td><td>최근접 측량 측점 정표고 + 지오이드고로 지면고도 추정</td><td>DEM 대신 <b>측량 측점 표고열을 고도원으로 전용</b>하는 발상은 비자명</td><td>부각오차 42.7°→11°, 구글 DEM 대비 <b>0.2m</b></td></tr>
|
||||
<tr><td><b>E4</b></td><td>지오코딩 오차 POI 수동보정 시 2D 드래그의 깊이 모호성</td><td>역투영 + <b>슬랜트거리 보존</b>으로 단일 드래그 수평·수직 동시 복원, 외부영속</td><td>슬랜트거리 고정으로 깊이 구속 → 1제스처 3D 보정은 비자명·구체적</td><td>수동보정 단계 1/2로 축소, 보정 재현성</td></tr>
|
||||
<tr><td><b>E5</b></td><td>원거리 대상이 소실점에 몰려 위치 오인</td><td>진행방향 기준 fwd/side 분해 비등방 임계 + 슬랜트 컷</td><td>등방 반경필터가 통상; 주행 도메인 특화 비등방 분해는 진보성 있음</td><td>오정합 클러터 제거, 앞/옆 독립 튜닝</td></tr>
|
||||
<tr><td><b>E6</b></td><td>GPS/자세 노이즈·필터 출입으로 라벨 튐·끊김</td><td>이중평활 + title 짝짓기 보간 + 이상치 거부 상태머신</td><td>각 기법은 공지이나 <b>정합 라벨 안정화 조합·title 키 보간</b>은 신규성 여지</td><td>라벨 점프 제거, 지연 1.65s→25ms</td></tr>
|
||||
<tr><td>E7</td><td>중심선 파일 부재</td><td>측점 mileage 폴리라인 대체</td><td>대체 산출은 자명에 가까움</td><td>중심선 표출 가능</td></tr>
|
||||
<tr><td>E8</td><td>이종 인코딩·포맷 측량파일 융합</td><td>BOM 감지+헤더 폴백+병합</td><td>데이터 엔지니어링, 통상 기술</td><td>로딩 견고성</td></tr>
|
||||
<tr><td>E9</td><td>방향전환 구간 표시</td><td>비고 정규식 파싱</td><td>단순 텍스트 파싱</td><td>방향 표시</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="callout bad"><b>특허 부적합 판정:</b>
|
||||
<b>E7</b>(폴리라인 대체)·<b>E8</b>(파일 융합·인코딩 감지)·<b>E9</b>(정규식 파싱)은 통상의 기술자가
|
||||
용이하게 구현하는 데이터 처리/엔지니어링으로, 독립된 기술적 진보성을 인정받기 어렵다. → <b>출원 제외</b>(다만
|
||||
독립항을 보강하는 <b>종속항 한정요소</b>로는 활용 가치 있음).</div>
|
||||
</section>
|
||||
|
||||
<!-- 3 -->
|
||||
<section id="s3">
|
||||
<h2><span class="num">③</span>발명 후보 도출</h2>
|
||||
<p class="lead">1차 필터를 통과한 요소를 발명 후보로 정리한다. 도메인 결합·구체적 단계·측정 효과를 갖춘 순으로 제시.</p>
|
||||
|
||||
<!-- 후보 A -->
|
||||
<div class="card">
|
||||
<h3 style="margin-top:2px">후보 A <span class="badge b-high">특허성 상</span></h3>
|
||||
<table>
|
||||
<tr><th style="width:22%">발명 명칭(가칭)</th><td>노선 측점(체이니지) 좌표 기반 주행영상 색인·탐색 및 위치고정 주석 방법</td></tr>
|
||||
<tr><th>해결 기술과제</th><td>가변속 주행으로 시간과 노선위치가 비선형 대응하여, 시간축 플레이어로는 특정 측점/시설로 직접 이동하거나, 한 회차의 주석을 다른 회차 영상에 재사용할 수 없는 문제.</td></tr>
|
||||
<tr><th>핵심 해결수단</th><td>
|
||||
<ol class="steps">
|
||||
<li>프레임별 위치 텔레메트리와 측량 측점열로부터 <b>프레임↔노선좌표(s, ㎞) 비선형 매핑</b>을 구축</li>
|
||||
<li>매핑을 1차원 노선좌표로 정규화하여 측점/㎞ 단위 <b>색인 및 seek 인터페이스</b>(미니맵·타임라인)를 생성</li>
|
||||
<li>주석을 시각이 아닌 <b>노선좌표에 앵커링</b>하여 저장</li>
|
||||
<li>재생 시 현재 프레임의 노선좌표를 역산해 해당 위치 주석을 호출 → <b>속도·회차 무관</b>하게 동일 지점에서 표시</li>
|
||||
</ol></td></tr>
|
||||
<tr><th>종래기술 차별점</th><td>종래 위치기반 영상(예: 스트리트뷰)은 정지영상 노드 전환식이며 연속 주행영상의 시간축을 위치축으로 <b>재좌표화</b>하지 않음. 본 발명은 연속 영상에 비선형 시간-거리 매핑을 부여해 위치단위 탐색·위치고정 주석을 제공.</td></tr>
|
||||
<tr><th>기술적 효과</th><td>특정 측점/시설 직접 도달(탐색시간 단축), 주석의 회차 간 재사용·자동 정렬. <span class="chk">[확인 필요]</span> 다중 회차 영상의 노선좌표 기준 <b>동기 비교 재생</b>은 확장 적용 시 효과.</td></tr>
|
||||
<tr><th>특허성 강도</th><td><b>상</b> — "위치단위 탐색" 추상아이디어를 넘어 비선형 매핑 구축·노선좌표 정규화·위치앵커 주석이라는 구체적 단계로 한정 가능. <span class="chk">[확인 필요]</span> 위치앵커 주석·다중회차 동기화의 실제 구현 범위 점검 후 청구범위 확정.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 후보 B -->
|
||||
<div class="card">
|
||||
<h3 style="margin-top:2px">후보 B <span class="badge b-high">특허성 상</span></h3>
|
||||
<table>
|
||||
<tr><th style="width:22%">발명 명칭(가칭)</th><td>측량 측점 표고를 고도원으로 이용한 DEM-free 지리객체 영상정합 방법</td></tr>
|
||||
<tr><th>해결 기술과제</th><td>주행영상에 시설물(POI)을 정합할 때 대상의 표고/DEM이 없으면 카메라와의 수직각(부각)이 크게 틀어져(예 42.7°) 라벨이 화면 밖/지면 아래로 이탈.</td></tr>
|
||||
<tr><th>핵심 해결수단</th><td>
|
||||
<ol class="steps">
|
||||
<li>노선을 따라 실측된 <b>측점 정표고(EL) 수열</b>을 고도 레퍼런스로 보유</li>
|
||||
<li>표고 미상 대상에 대해 <b>노선상 최근접 측점의 정표고</b>를 지면고도로 채택(노선 진행에 따른 지형상승 반영)</li>
|
||||
<li><b>지오이드고(N)</b>를 가산해 정표고(EL)를 타원체고로 변환, 카메라(드론) 타원체고와 <b>datum 정합</b></li>
|
||||
<li>정합된 고도로 ENU 상대벡터를 구성해 핀홀 투영 → 수직 정합</li>
|
||||
</ol></td></tr>
|
||||
<tr><th>종래기술 차별점</th><td>일반 AR/사진계측은 외부 DEM·LiDAR 또는 대상별 실측고도에 의존. 본 발명은 <b>철도 측량 산출물(측점 표고열)을 지면고도 모델로 전용</b>하고 지오이드 보정으로 항공 텔레메트리와 datum을 맞춤. 공개 DEM 없이도 등가 정밀도 달성.</td></tr>
|
||||
<tr><th>기술적 효과</th><td>부각오차 <b>42.7°→11°</b>, 구글어스 DEM 지면고도 대비 <b>0.2m(≈0.13°, 2160p에서 ~6px)</b> 일치 — 측정·검증됨. 별도 DEM 취득·정합 비용 제거.</td></tr>
|
||||
<tr><th>특허성 강도</th><td><b>상</b> — 도메인 특화 해결수단 + 수치적으로 입증된 효과. 신규성/진보성 인용 위험 낮음(측량측점→고도원 전용 발상 비자명).</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 후보 C -->
|
||||
<div class="card">
|
||||
<h3 style="margin-top:2px">후보 C <span class="badge b-high">특허성 상</span></h3>
|
||||
<table>
|
||||
<tr><th style="width:22%">발명 명칭(가칭)</th><td>슬랜트거리 보존 역투영을 이용한 단일 제스처 수평·수직 동시 위치보정 방법</td></tr>
|
||||
<tr><th>해결 기술과제</th><td>주소 지오코딩 오차(수십m)로 어긋난 객체를 사용자가 화면에서 보정할 때, 2D 화면 드래그는 <b>깊이(거리) 정보가 없어</b> 3D 월드좌표를 유일하게 결정할 수 없음(수평·수직을 따로 보정해야 함).</td></tr>
|
||||
<tr><th>핵심 해결수단</th><td>
|
||||
<ol class="steps">
|
||||
<li>편집 모드에서 객체 라벨의 드래그 시작 시 카메라-객체 <b>슬랜트 거리(range)를 고정(보존)</b></li>
|
||||
<li>드롭한 화면 픽셀을 카메라 방향벡터로 환산 후 <b>보존된 거리만큼 투사</b>해 카메라좌표 결정</li>
|
||||
<li>회전·평행이동의 역변환으로 lat/lon/z를 복원(투영의 지오이드 가산을 역으로 환원)</li>
|
||||
<li>복원 좌표를 식별자 키로 외부 override 파일에 저장 → 차기 로드시 자동 재적용</li>
|
||||
</ol></td></tr>
|
||||
<tr><th>종래기술 차별점</th><td>지도 편집기의 드래그는 평면(2D) 이동에 국한. 본 발명은 <b>거리 보존 구속</b>으로 단일 제스처에서 수평+수직을 동시 복원하고, 정합 영상 맥락에서 역투영을 수행한다는 점이 차별적.</td></tr>
|
||||
<tr><th>기술적 효과</th><td>한 번의 드래그로 3D 보정(작업 단계 절반), 왕복오차 ≈0(검증), 보정의 파일 영속·재현.</td></tr>
|
||||
<tr><th>특허성 강도</th><td><b>상</b> — "거리 보존으로 2D→3D 깊이 모호성 해소"는 구체적·비자명한 기술수단. 단독 또는 후보 B와 결합 출원 유리.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 후보 D / E 요약 -->
|
||||
<div class="grid2">
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:2px">후보 D <span class="badge b-mid">특허성 중</span></h3>
|
||||
<p style="margin:6px 0"><b>명칭(가칭):</b> 주행 진행방향 기준 비등방 거리필터에 의한 정합객체 표출 제어</p>
|
||||
<p style="margin:6px 0"><b>과제:</b> 원거리 객체가 소실점에 수렴해 선로 위에 겹쳐 위치 오인.</p>
|
||||
<p style="margin:6px 0"><b>수단:</b> 상대벡터를 yaw로 fwd/side 분해 → 전방·측방 임계 독립 적용 + 슬랜트거리 컷.</p>
|
||||
<p style="margin:6px 0"><b>차별점:</b> 통상 등방 반경필터 대비 도메인 특화 비등방 제어.</p>
|
||||
<p style="margin:6px 0"><b>강도 근거:</b> 효과는 명확하나 비등방 게이팅 자체의 진보성 다툼 여지 → <b>단독 약, 종속항·조합 권장</b>.</p>
|
||||
</div>
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:2px">후보 E <span class="badge b-mid">특허성 중</span></h3>
|
||||
<p style="margin:6px 0"><b>명칭(가칭):</b> title-키 보간과 이상치 거부를 이용한 노이즈 강건 실시간 정합 렌더링</p>
|
||||
<p style="margin:6px 0"><b>과제:</b> 필터 출입·GPS 스파이크로 라벨이 튀고 끊김.</p>
|
||||
<p style="margin:6px 0"><b>수단:</b> 사전계산 labelMap + <b>title 키 프레임간 보간</b> + 점프 임계 이상치 거부 상태머신 + 이중평활.</p>
|
||||
<p style="margin:6px 0"><b>차별점:</b> 인덱스 짝짓기의 오정합을 식별자 키 보간으로 근본 해결.</p>
|
||||
<p style="margin:6px 0"><b>강도 근거:</b> 구성기법은 공지에 가까우나 정합 라벨 안정화 조합은 신규성 여지 → <b>방법항보다 시스템 종속항 권장</b>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout ok"><b>조합 전략:</b> 후보 <b>B</b>(고도원) + <b>C</b>(거리보존 보정)는 "정합 정확도"라는 한 축에서
|
||||
상호 보완 → 하나의 출원에 <b>독립항 2개</b> 또는 <b>독립항+종속항</b>으로 묶으면 권리 두께가 커진다. 후보 <b>A</b>는
|
||||
탐색·UX 축으로 별도 출원이 명확.</div>
|
||||
</section>
|
||||
|
||||
<!-- 4 -->
|
||||
<section id="s4">
|
||||
<h2><span class="num">④</span>청구항 초안</h2>
|
||||
<p class="lead">가장 유망한 후보 <b>B</b>(방법·매체)와 <b>C</b>(시스템)에 대해 독립항 + 종속항 초안을 제시한다.
|
||||
<span class="chk">[확인 필요]</span> 실제 구현 범위·용어는 명세서 작성 시 변리사 검토로 확정.</p>
|
||||
|
||||
<!-- 청구항 B -->
|
||||
<div class="claim">
|
||||
<div class="ctag">발명 B — 방법(Method) / 매체(Medium)</div>
|
||||
<ol>
|
||||
<li class="ind"><b>[청구항 1]</b> (독립항·방법) 주행영상에 지리객체를 정합하는 방법으로서,
|
||||
<ol style="list-style:lower-alpha">
|
||||
<li>이동체에 탑재된 카메라로 노선을 따라 촬영된 영상의 각 프레임에 대응하는 위치 및 자세(yaw·pitch·roll)와, 상기 카메라의 제1 수직기준에 따른 제1 고도를 획득하는 단계;</li>
|
||||
<li>상기 노선을 따라 실측된 복수의 측점에 대하여, 각 측점의 위치 및 제2 수직기준에 따른 제2 고도를 포함하는 측점 표고 수열을 보유하는 단계;</li>
|
||||
<li>표고가 미상인 대상 지리객체에 대하여, 상기 노선상 최근접 측점의 상기 제2 고도를 상기 대상의 지면고도로 선택하는 단계;</li>
|
||||
<li>지오이드고를 가산하여 상기 제2 수직기준의 지면고도를 상기 제1 수직기준의 고도로 변환함으로써 상기 카메라의 고도와 수직기준을 정합하는 단계; 및</li>
|
||||
<li>정합된 고도로 산출한 카메라-대상 상대벡터를 상기 프레임의 자세에 따라 회전·투영하여 상기 영상의 화면좌표에 상기 대상의 표지를 중첩 표시하는 단계를 포함하는, 방법.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li class="ind"><b>[청구항 2]</b> (종속) 제1항에서, 상기 제1 고도는 타원체고이고 상기 제2 고도는 정표고이며, 상기 지오이드고는 해당 노선 구간에 대해 설정된 값으로서 사용자 입력에 의해 가변되는, 방법.</li>
|
||||
<li class="ind"><b>[청구항 3]</b> (종속) 제1항에서, 상기 최근접 측점 선택은 상기 노선 진행에 따른 측점 표고의 변화를 반영하여 프레임 진행에 따라 지면고도가 갱신되도록 수행되는, 방법.</li>
|
||||
<li class="ind"><b>[청구항 4]</b> (종속) 제1항에서, 상기 표지의 화면좌표를 전체 프레임에 대해 사전계산하여 색인(labelMap)으로 저장하고, 재생 시 상기 색인을 조회하여 인접 프레임 간 보간으로 표지를 표시하는, 방법.</li>
|
||||
<li class="ind"><b>[청구항 5]</b> (매체) 제1항 내지 제4항 중 어느 한 항의 방법을 컴퓨터에 실행시키기 위한 프로그램을 기록한 컴퓨터로 읽을 수 있는 기록매체.</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- 청구항 C -->
|
||||
<div class="claim">
|
||||
<div class="ctag">발명 C — 시스템(System)</div>
|
||||
<ol>
|
||||
<li class="ind"><b>[청구항 1]</b> (독립항·시스템) 정합 영상 내 지리객체의 위치를 보정하는 시스템으로서,
|
||||
<ol style="list-style:lower-alpha">
|
||||
<li>프레임별 카메라 위치·자세로 지리객체를 영상 화면좌표에 투영하여 표지를 표시하는 정합부;</li>
|
||||
<li>화면에 표시된 표지에 대한 포인터의 드래그 개시를 검출하고, 개시 시점의 카메라-객체 간 슬랜트 거리를 보존값으로 고정하는 입력부;</li>
|
||||
<li>드래그 종료 화면좌표를 카메라 방향벡터로 환산하고 상기 보존값만큼 투사하여 카메라좌표를 정하며, 자세 회전과 평행이동의 역변환으로 상기 객체의 수평위치 및 고도를 동시에 복원하는 역투영부; 및</li>
|
||||
<li>복원된 위치를 객체 식별자에 연계하여 보정 데이터로 영속 저장하고, 후속 로드 시 해당 식별자의 지리객체에 자동 적용하는 보정관리부를 포함하는, 시스템.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li class="ind"><b>[청구항 2]</b> (종속) 제1항에서, 상기 역투영부는 화면에 표시된(카메라 전방, Zc>0) 객체에 한해 보정을 허용하는, 시스템.</li>
|
||||
<li class="ind"><b>[청구항 3]</b> (종속) 제1항에서, 상기 정합부는 카메라-객체 상대벡터를 진행방향 기준 전방성분과 측방성분으로 분해하여 각각의 임계와 비교함으로써 표지의 표시 여부를 결정하는, 시스템. <span class="chk">[후보 D 흡수]</span></li>
|
||||
<li class="ind"><b>[청구항 4]</b> (종속) 제1항에서, 상기 보정 데이터는 영상과 동일 위치의 외부 파일로 저장되어 가져오기/내보내기 가능하고, 미보정 객체는 노선상 최근접 측점 표고로 고도가 추정되는, 시스템. <span class="chk">[후보 B 연계]</span></li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="callout"><b>카테고리 선택 근거:</b> B는 데이터 변환·표시의 단계열이 명확해 <b>방법+매체</b>가 적합.
|
||||
C는 입력·역투영·저장 구성요소 간 상호작용이 핵심이라 <b>시스템(장치)</b>가 적합. 미국 출원 병행 시 §101 대응을 위해
|
||||
"구체적 디스플레이 개선·기존기술 향상" 효과(부각오차 수치 등)를 명세서에 명시할 것.</div>
|
||||
</section>
|
||||
|
||||
<!-- 5 -->
|
||||
<section id="s5">
|
||||
<h2><span class="num">⑤</span>선행기술 조사 키워드 · 분류</h2>
|
||||
|
||||
<div class="grid2">
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:2px">후보 A (측점 기반 색인·탐색)</h3>
|
||||
<div class="kw">
|
||||
<span>측점 기반 영상 탐색</span><span>체이니지 비디오 색인</span><span>위치고정 주석</span><span>노선좌표 정규화</span><span>주행영상 위치 동기화</span>
|
||||
<span>chainage based video navigation</span><span>milepost video indexing</span><span>linear referencing video</span><span>location-anchored annotation</span><span>route-coordinate video seek</span>
|
||||
</div>
|
||||
<p style="margin:8px 0 0"><b>분류(추정)</b> <span class="ipc">G11B 27/10·27/34</span>(탐색/색인), <span class="ipc">G06F 16/74·16/78</span>(영상 검색), <span class="ipc">G01C 21/00</span>(노선/내비), <span class="ipc">H04N 21/845</span>(세그먼트화)</p>
|
||||
</div>
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:2px">후보 B (DEM-free 측점표고 정합)</h3>
|
||||
<div class="kw">
|
||||
<span>측량 측점 표고 영상정합</span><span>지오이드 보정 AR</span><span>정표고 타원체고 변환</span><span>DEM 없는 지면고도</span><span>철도 시설 영상 라벨</span>
|
||||
<span>survey benchmark elevation registration</span><span>geoid undulation AR overlay</span><span>orthometric ellipsoidal height</span><span>DEM-free ground elevation</span><span>geo-registration railway video</span>
|
||||
</div>
|
||||
<p style="margin:8px 0 0"><b>분류(추정)</b> <span class="ipc">G06T 19/00</span>(AR), <span class="ipc">G06T 7/73</span>(자세추정), <span class="ipc">G06V 20/56</span>(주행환경 인식), <span class="ipc">G01C 5/00·21/32</span>(고도/지도매칭)</p>
|
||||
</div>
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:2px">후보 C (역투영·거리보존 보정)</h3>
|
||||
<div class="kw">
|
||||
<span>역투영 위치보정</span><span>슬랜트거리 보존 드래그</span><span>2D 3D 깊이 모호성</span><span>단일제스처 3D 편집</span><span>지오코딩 오차 보정</span>
|
||||
<span>inverse projection drag correction</span><span>slant range constrained editing</span><span>monocular depth disambiguation drag</span><span>back-projection geolocation</span><span>annotation override persistence</span>
|
||||
</div>
|
||||
<p style="margin:8px 0 0"><b>분류(추정)</b> <span class="ipc">G06T 19/20</span>(3D 편집), <span class="ipc">G06T 7/70</span>(위치추정), <span class="ipc">G06F 3/04845</span>(직접조작), <span class="ipc">G01C 21/20</span></p>
|
||||
</div>
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:2px">후보 D·E (표출제어·안정화)</h3>
|
||||
<div class="kw">
|
||||
<span>비등방 거리필터</span><span>소실점 클러터 억제</span><span>실시간 라벨 안정화</span><span>이상치 거부 보간</span><span>오버레이 떨림 저감</span>
|
||||
<span>anisotropic distance culling</span><span>vanishing point label clutter</span><span>temporal label stabilization</span><span>outlier rejection interpolation</span><span>AR label jitter reduction</span>
|
||||
</div>
|
||||
<p style="margin:8px 0 0"><b>분류(추정)</b> <span class="ipc">G06T 11/00</span>(라벨렌더), <span class="ipc">G06T 5/00</span>(필터), <span class="ipc">G09G 5/377</span>(오버레이)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout"><b>조사 우선 DB·전략:</b> KIPRIS(국문)·Google Patents·Espacenet·USPTO PatFT.
|
||||
① 후보 B·C는 <b>"railway/road inspection drone video + overlay/registration"</b> 교집합으로 좁혀 검색,
|
||||
② 후보 A는 GIS·LRS(Linear Referencing System) 문헌과 <b>모빌아이/스트리트뷰</b> 계열 특허를 비특허문헌(NPL) 포함 조사,
|
||||
③ 측량·항공사진계측 표준문서(지오이드 모델 KNGeoid)는 신규성보다 <b>구현 배경</b>으로 인용.</div>
|
||||
</section>
|
||||
|
||||
<!-- 6 선행기술 조사결과 -->
|
||||
<section id="prior">
|
||||
<h2><span class="num">⑥</span>선행기술 조사 결과 (실조사)</h2>
|
||||
<p class="lead">위 키워드로 글로벌(Google Patents·USPTO·Esri·논문) + 한국(KIPRIS/Google Patents KR)을 실제 조사하고
|
||||
각 주장을 교차검증한 결과. <span style="color:var(--muted)">조사일 2026-06-23.</span></p>
|
||||
|
||||
<div class="callout bad"><b>큰 틀(E1·E2)은 공지 확정 — 독립항 불가.</b>
|
||||
"프레임별 카메라 자세(GPS/IMU)로 GIS 객체를 영상 픽셀에 정합(AR overlay)하는 위치기반 플레이어"라는 개념 자체는
|
||||
<b>Esri ArcGIS Pro FMV 플레이어</b>, 특허 <b>US9996976B2</b>(Avigilon/Motorola), <b>Sensors 2018</b> 논문으로 이미 공지.
|
||||
지도/GPS 위치 기반 영상 탐색·외부 GIS 공동표시도 <b>Mandli Roadview Explorer</b>·<b>Remote GeoSystems LineVision</b>으로 공지.
|
||||
철도/도로 영상의 milepost·GPS 색인도 <b>ENSCO Virtual Track Walk</b>로 공지. → 큰 개념만으로는 권리화 불가, <b>세부 발명을 좁게</b> 청구해야 함.</div>
|
||||
|
||||
<h3>발명 후보별 신규성 판정 (글로벌 + 한국)</h3>
|
||||
<table>
|
||||
<thead><tr><th style="width:7%">후보</th><th style="width:12%">판정</th><th>가장 가까운 선행기술</th><th style="width:34%">차별점 (= 신규성이 남는 지점)</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>A</b><br><span style="font-size:11px;color:var(--muted)">측점 탐색</span></td>
|
||||
<td><span class="badge b-mid">부분 선행기술</span><br><span style="font-size:11px;color:var(--muted)">신뢰도 중</span></td>
|
||||
<td>
|
||||
<b>KR102268318B1</b>(트라웍스, 2021): 프레임별 거리정보로 <b>두 주행영상을 위치 동기 비교 재생</b>+기준선.<br>
|
||||
<b>KR100411587B1</b>(ETRI, 2001): GPS-영상 동기, <b>지도 위치 지정→해당 영상 표시</b>.<br>
|
||||
<b>ENSCO VTW</b>: milepost/㎞/GPS 영상 색인. <b>US20040263624A1</b>: 철도영상-GPS/엔코더 상관.
|
||||
</td>
|
||||
<td>아래 셋을 가르치는 선행기술 <b>미발견</b>: ① 가변속 <b>시간축의 LRS 비선형 재좌표화</b>(측점/㎞ 직접 seek, 단순 GPS 점좌표 아님), ② <b>회차 간 위치앵커 주석</b>, ③ <b>3개+ 다중회차</b> 위치동기(선행은 1:1 비교에 한정).
|
||||
<br><span class="chk">⚠️ "위치로 영상 탐색"·"두 영상 동기 재생"만 청구하면 KR102268318·KR100411587로 거절 위험.</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>B</b><br><span style="font-size:11px;color:var(--muted)">측점표고 정합</span></td>
|
||||
<td><span class="badge b-high">신규성 있음</span><br><span style="font-size:11px;color:var(--muted)">신뢰도 높음</span></td>
|
||||
<td>
|
||||
<b>Sensors 2018</b>: DEM 없을 때 <b>평면(flat plane)으로 대체</b>, 지오이드/정표고 변환 <b>안 함</b>.<br>
|
||||
<b>US9778360B2</b>: 지오이드 계산 특허지만 <b>영상/AR 무관</b>.<br>
|
||||
<b>KR102417591B1</b>(두시텍)·KAIST·한전: 카메라자세 GIS 영상투영(고도 datum 처리 없음). 최신 한국 AR/GIS는 오히려 <b>DEM 의존</b>(LX DTM).
|
||||
</td>
|
||||
<td>"<b>DEM 부재 시 노선 실측 측점 정표고 수열을 지면고도 모델로 전용 + 지오이드고 가산으로 타원체고 변환 + 카메라 GPS datum 정합</b>"의 결합을 가르치는 특허·논문 <b>전무</b>(글로벌·한국 공통). 개별요소(영상투영, h=H+N 변환)는 공지 → <b>결합의 진보성</b>을 명세서에서 강조 필요.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>C</b><br><span style="font-size:11px;color:var(--muted)">역투영 보정</span></td>
|
||||
<td><span class="badge b-high">신규성 있음</span><br><span style="font-size:11px;color:var(--muted)">신뢰도 높음</span></td>
|
||||
<td>
|
||||
<b>US9188444B2</b>(Google, 2015): 스트리트뷰 드래그→lat/lon, 단 <b>저장된 픽셀별 depth map(라이다) 조회</b>로 깊이 해결. <b>한국 패밀리 없음</b>(JP/WO/EP만).<br>
|
||||
<b>KR102422292B1</b>(김동욱): 역투영+사용자보정이나 <b>두 영상 + 높이값 반복조정</b>으로 깊이 해결.<br>
|
||||
<b>Sensors 2018</b>: 수동보정은 "부담 커서 포기"라 명시.
|
||||
</td>
|
||||
<td>"<b>depth map 없이</b>, 드래그 시작 <b>슬랜트거리(range)를 보존</b>하여 <b>단안·단일 제스처</b>로 수평·수직 동시 복원"하는 메커니즘은 선행기술과 <b>구별됨</b>. Google은 depth map 의존, KR102422292는 2영상 의존 → 본 발명의 거리보존 구속은 <b>미공개</b>.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="grid2">
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:2px">주요 선행기술 출처 (실확인)</h3>
|
||||
<ul style="margin:6px 0;padding-left:18px;font-size:13.5px">
|
||||
<li>Esri ArcGIS Pro <b>Full Motion Video</b> 플레이어 (카메라자세 영상↔지도 정합)</li>
|
||||
<li><b>US9996976B2</b> Avigilon/Motorola — UAV 영상 지도데이터 정합</li>
|
||||
<li><b>US9188444B2</b> Google — 스트리트뷰 3D 객체 위치(depth map)</li>
|
||||
<li>Sensors 2018 (PMC6263998) — UAV 영상 GIS 정합, flat-plane DEM 대체</li>
|
||||
<li><b>Mandli Roadview Explorer</b> / <b>Remote GeoSystems LineVision</b> — GPS 동기 영상</li>
|
||||
<li><b>ENSCO Virtual Track Walk</b> — 철도영상 milepost 색인</li>
|
||||
<li>KR: <b>KR102268318B1</b>, <b>KR100411587B1</b>, <b>KR102417591B1</b>, <b>KR102422292B1</b></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card tight">
|
||||
<h3 style="margin-top:2px">조사 한계 (미해소 — 출원 전 필수)</h3>
|
||||
<ul style="margin:6px 0;padding-left:18px;font-size:13.5px">
|
||||
<li><span class="chk">KIPRIS 전문 DB 직접검색 미수행</span> — 본 조사는 Google Patents 색인 기반. 18개월 미공개·미색인 KR 출원(KRRI·코레일·국가철도공단·도로공사·LX 명의) 누락 가능 → <b>변리사 KIPRIS 전문검색 필수</b>.</li>
|
||||
<li>후보 A의 <b>"LRS + 프레임별 카메라자세 AR투영을 한 시스템에서 결합"</b>한 선행기술(Esri Roadway/LRS+FMV 통합 등) 존재 여부 미확인 — A의 운명을 좌우.</li>
|
||||
<li>제품 능력은 <b>벤더 마케팅 페이지</b> 근거 — "기능 존재"는 확인되나 픽셀정확도·성능 주장은 미검증.</li>
|
||||
<li>유료 학술 전문(DBpia/RISS)·NPL(arXiv GeoDrag 등 드래그 편집 논문)은 진보성 공격용으로 별도 검토 권장.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 결론 -->
|
||||
<section id="concl">
|
||||
<h2>결론 — 출원 우선순위</h2>
|
||||
<div class="card">
|
||||
<div class="pri">
|
||||
<div class="rank"><small>1순위</small>B+C</div>
|
||||
<div>
|
||||
<p style="margin:0 0 4px"><b>정합 정확도 발명군 (후보 B + C 결합 출원)</b> <span class="badge b-high">강</span></p>
|
||||
<p style="margin:0;color:var(--muted)">측점표고 기반 DEM-free 고도원(B)과 거리보존 역투영 보정(C)은 <b>측정·검증된 효과</b>(부각 42.7°→11°,
|
||||
DEM 대비 0.2m, 왕복오차 ≈0)와 도메인 특화 비자명성을 모두 갖춰 권리화 성공 가능성이 가장 높다. 독립항 2개로 두께 확보.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pri">
|
||||
<div class="rank"><small>2순위</small>A</div>
|
||||
<div>
|
||||
<p style="margin:0 0 4px"><b>측점 기반 영상 색인·탐색·위치고정 주석 (후보 A)</b> <span class="badge b-mid">중 (선행기술 조사 후 하향)</span></p>
|
||||
<p style="margin:0;color:var(--muted)">선행기술 조사 결과 <b>강한 한국 선행기술 2건</b>(KR102268318B1 위치동기 비교재생, KR100411587B1 위치→영상 색인) 확인 →
|
||||
"위치 탐색·동기 재생" 광의 청구는 거절 위험. 신규성은 <b>①LRS 비선형 재좌표화 ②회차간 위치앵커 주석 ③3개+ 다중회차 동기</b>에만 잔존 →
|
||||
반드시 이 셋을 묶어 <b>좁게</b> 청구. <span class="chk">실제 구현 범위 확인 + KIPRIS 전문검색 필요.</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pri">
|
||||
<div class="rank"><small>3순위</small>D·E</div>
|
||||
<div>
|
||||
<p style="margin:0 0 4px"><b>표출 제어·실시간 안정화 (후보 D·E)</b> <span class="badge b-mid">중</span></p>
|
||||
<p style="margin:0;color:var(--muted)">단독 출원보다 1·2순위 출원의 <b>종속항으로 흡수</b>해 회피설계 방어력을 높이는 용도로 활용 권장(청구항 C-3, B-4 참조).</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pri">
|
||||
<div class="rank" style="color:var(--bad)"><small>제외</small>—</div>
|
||||
<div>
|
||||
<p style="margin:0 0 4px"><b>E7·E8·E9</b> <span class="badge b-low">특허 부적합</span></p>
|
||||
<p style="margin:0;color:var(--muted)">폴리라인 대체·파일 융합·정규식 파싱은 통상의 데이터 처리로 진보성 인정 곤란 → 출원 제외(명세서 배경기술로만 기술).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout ok" style="margin-top:18px"><b>선행기술 조사 반영(2026-06-23):</b> 본 우선순위는 §6의 실제 글로벌+한국 특허 조사로 검증됨.
|
||||
<b>B·C는 글로벌·한국 모두에서 신규성 확인</b>(B=DEM-free 측점표고 정합 결합 미공개, C=Google 한국패밀리 없음+슬랜트거리보존 메커니즘 미공개) → 1순위 유지·강화.
|
||||
<b>A는 한국 선행기술로 인해 2순위 중급으로 하향</b>. 큰 틀(E1·E2)은 공지 확정.</div>
|
||||
|
||||
<div class="disc" style="margin-top:18px">
|
||||
<b>출력 규칙 준수 표기</b> · <span class="chk">[확인 필요]</span>로 표시한 부분(다중회차 동기화 구현 범위, 위치앵커 주석의
|
||||
실제 데이터 모델, 청구 용어, <b>KIPRIS 전문검색</b>)은 출원 전 코드 확인 및 변리사 검토가 필요하다.
|
||||
선행기술 조사는 Google Patents·웹 색인 기반으로 <b>KIPRIS 직접 전문검색은 미수행</b>(§6 한계 참조). 특허 부적합 요소는 §2·결론에 사유와 함께 명시했다.
|
||||
본 문서는 발굴·평가 단계 산출물이며 법적 의견이 아니다.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
GhiVideo · 스테이션(측점) 기반 주행영상 플레이어 특허 발굴 기술문서 · 작성 2026-06-22 · 선행기술 조사 반영 2026-06-23 ·
|
||||
근거: 구현 코드·개발 히스토리(<a class="ref" href="history/2026-06-22_POI투영오차-개선.md">POI 투영오차</a>,
|
||||
<a class="ref" href="history/2026-04-01_RoutePanel-미니맵-추가.md">RoutePanel</a>,
|
||||
<a class="ref" href="history/2026-06-19_v2.0-import전환.md">v2.0 전환</a>) +
|
||||
글로벌/한국 특허 선행기술 조사(Esri FMV·US9996976B2·US9188444B2·KR102268318B1·KR102417591B1·KR102422292B1 등).
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
# 특허 추출 프롬프트
|
||||
|
||||
당신은 소프트웨어 특허 분석 전문가입니다. 아래 프로젝트를 분석하여 **특허 출원 가치가 있는 기술적 아이디어**를 발굴하세요.
|
||||
|
||||
## 입력 자료
|
||||
[여기에 프로젝트 정보를 넣으세요]
|
||||
- 소스코드 / 아키텍처 문서 / README
|
||||
- 핵심 기능 설명
|
||||
- 기존 방식 대비 차별점 (알고 있다면)
|
||||
|
||||
## 분석 절차
|
||||
|
||||
### 1단계: 기술 요소 인벤토리
|
||||
프로젝트에서 다음을 식별하여 나열하세요.
|
||||
- 핵심 기능과 그것을 구현하는 기술적 메커니즘
|
||||
- 데이터 처리 흐름 (입력 → 변환 → 출력)
|
||||
- 자체적으로 고안한 알고리즘, 자료구조, 처리 순서
|
||||
- 시스템 구성요소 간 상호작용 방식
|
||||
|
||||
### 2단계: 특허 적격성 1차 필터
|
||||
각 요소를 아래 기준으로 평가하세요. (단순 코드 구현이나 비즈니스 로직 자체는 특허 대상이 아님)
|
||||
- **기술적 과제**: 어떤 기술적 문제를 해결하는가?
|
||||
- **기술적 해결수단**: 그 문제를 *어떻게* 해결하는가? (구체적 단계/구조로 설명 가능한가?)
|
||||
- **자명성**: 해당 분야 통상의 기술자가 쉽게 떠올릴 수 있는가? 아니라면 그 이유는?
|
||||
- **효과**: 기존 방식 대비 측정 가능한 개선(속도, 정확도, 자원 절감 등)이 있는가?
|
||||
|
||||
### 3단계: 발명 후보 도출
|
||||
필터를 통과한 요소를 **발명 후보**로 정리하세요. 각 후보마다:
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 발명 명칭(가칭) | |
|
||||
| 해결하는 기술적 과제 | |
|
||||
| 핵심 해결수단 (3~5단계로) | |
|
||||
| 종래기술과의 차별점 | |
|
||||
| 기술적 효과 | |
|
||||
| 특허성 강도 | 상/중/하 + 근거 |
|
||||
|
||||
### 4단계: 청구항 초안
|
||||
가장 유망한 후보 1~2개에 대해 **독립항 1개 + 종속항 2~3개**의 초안을 작성하세요. 방법(method)·시스템(system)·매체(medium) 중 적합한 카테고리로 기술하세요.
|
||||
|
||||
### 5단계: 선행기술 조사 키워드
|
||||
각 발명 후보의 신규성 검증에 사용할 **검색 키워드(국문/영문) 5개 이상**과 조사할 분류(IPC/CPC 추정)를 제시하세요.
|
||||
|
||||
## 출력 규칙
|
||||
- 추측한 부분은 "[확인 필요]"로 명시
|
||||
- 특허성이 낮은 요소는 솔직하게 "특허 부적합" 판정과 이유를 적을 것
|
||||
- 결론에 출원 우선순위(1순위, 2순위...)를 제시
|
||||
Reference in New Issue
Block a user