From c1b6f9903227e02d59b2dfd24d994b3dc5c71223 Mon Sep 17 00:00:00 2001 From: minsung Date: Thu, 2 Apr 2026 17:17:59 +0900 Subject: [PATCH] fix: geoMatch loadFrames crash when GEO_DATA_DIR missing on new server - Add fs.existsSync check before readdirSync in loadFrames() - Add warning logs when GEO_DATA_DIR, drone CSV, or building/ dir not found - Remove dead csvPath variable (unused) - Add warning log in loadPois() when building/ dir missing Co-Authored-By: Claude Sonnet 4.6 --- ...-04-02_프레임동기화-렌더링최적화-터널링.md | 53 +++++++++++++++++++ server/src/services/geoMatch.ts | 17 ++++-- 2 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 docs/history/2026-04-02_프레임동기화-렌더링최적화-터널링.md diff --git a/docs/history/2026-04-02_프레임동기화-렌더링최적화-터널링.md b/docs/history/2026-04-02_프레임동기화-렌더링최적화-터널링.md new file mode 100644 index 0000000..02d097a --- /dev/null +++ b/docs/history/2026-04-02_프레임동기화-렌더링최적화-터널링.md @@ -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 diff --git a/server/src/services/geoMatch.ts b/server/src/services/geoMatch.ts index e3bc855..10cc071 100644 --- a/server/src/services/geoMatch.ts +++ b/server/src/services/geoMatch.ts @@ -276,15 +276,19 @@ function loadFrames(): DroneFrame[] { if (_frames) return _frames; if (!_dataDir) return []; - const csvPath = fs.readdirSync(_dataDir) - .find(f => f.endsWith('.csv') && !fs.statSync(path.join(_dataDir!, f)).isDirectory() - && !path.join(_dataDir!, f).includes('building')); + if (!fs.existsSync(_dataDir)) { + console.warn(`[geo] GEO_DATA_DIR not found: ${_dataDir}. Set GEO_DATA_DIR env var.`); + return []; + } // 드론 CSV는 data dir 바로 아래 (building 폴더 제외) const files = fs.readdirSync(_dataDir).filter(f => f.endsWith('.csv') && f.includes('회덕') && !f.includes('POI') && !f.includes('측점') ); - if (!files.length) return []; + if (!files.length) { + console.warn(`[geo] No drone CSV (containing '회덕') found in: ${_dataDir}`); + return []; + } const rows = readCsvUtf8(path.join(_dataDir, files[0])); const header = rows[0].map(h => h.trim().replace(/^\uFEFF/, '')); @@ -309,7 +313,10 @@ function loadPois(): GeoPoint[] { if (!_dataDir) return []; const buildingDir = path.join(_dataDir, 'building'); - if (!fs.existsSync(buildingDir)) return []; + if (!fs.existsSync(buildingDir)) { + console.warn(`[geo] building/ dir not found: ${buildingDir}`); + return []; + } const result: GeoPoint[] = [];