# 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개. "화면표시 옵션"에서 선형/드론궤적 독립 토글·투명도·표고 조절. "카메라 파라미터"는 투영 보정만.
