초기 커밋: 스테이션(측점) 기반 주행영상 플레이어

- 클라이언트(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:
2026-06-25 16:37:57 +09:00
co-authored by Claude Opus 4.8
commit 78fc090360
214 changed files with 27897 additions and 0 deletions
@@ -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개. "화면표시 옵션"에서 선형/드론궤적 독립 토글·투명도·표고 조절. "카메라 파라미터"는 투영 보정만.