GhiVideo 소스 복제 — v4 작업 시작 기준

기존 GhiVideo 저장소 HEAD의 트래킹 소스 362개 파일을 복제.
(node_modules·storage·빌드 산출물·대용량 미디어는 .gitignore 규칙대로 제외)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 14:32:51 +09:00
co-authored by Claude Fable 5
commit d38b842e8d
362 changed files with 46358 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
import { Router } from 'express';
import path from 'path';
import { setGeoDataDir, findFramesForPoi, findPoisForFrame, getAllPois, getDroneFrames, getCenterlinePoints } from '../services/geoMatch';
const router = Router();
// 드론 CSV + building 폴더 위치 설정
const GEO_DATA_DIR = process.env.GEO_DATA_DIR ||
path.resolve(__dirname, '../../../samplevideo');
setGeoDataDir(GEO_DATA_DIR);
/** POI/측점 전체 목록 (자동완성용) */
router.get('/pois', (_req, res) => {
try {
const pois = getAllPois();
res.json(pois);
} catch (e) {
res.status(500).json({ error: String(e) });
}
});
/**
* 건물/측점명 검색 → 해당 POI가 카메라에 보이는 프레임 목록
* GET /api/geo/search?q=회덕역&margin=1.0
*/
router.get('/search', (req, res) => {
const q = String(req.query.q || '').trim();
const margin = parseFloat(String(req.query.margin || '1.0'));
const yawOffset = parseFloat(String(req.query.yawOffset || '0'));
if (!q) return res.status(400).json({ error: 'q 파라미터 필요' });
try {
const result = findFramesForPoi(
q,
isNaN(margin) ? 1.0 : margin,
parseFloat(String(req.query.maxDist || '2000')),
isNaN(yawOffset) ? 0 : yawOffset,
);
if (!result.poi) return res.status(404).json({ error: `"${q}" POI를 찾을 수 없습니다` });
res.json(result);
} catch (e) {
res.status(500).json({ error: String(e) });
}
});
/**
* 특정 프레임에서 보이는 POI/측점 목록
* GET /api/geo/frame/1234?margin=1.0
*/
router.get('/frame/:frameNum', (req, res) => {
const frameNum = parseInt(req.params.frameNum, 10);
const margin = parseFloat(String(req.query.margin || '1.0'));
const yawOffset = parseFloat(String(req.query.yawOffset || '0'));
if (isNaN(frameNum)) return res.status(400).json({ error: '유효한 프레임 번호 필요' });
try {
const result = findPoisForFrame(frameNum, isNaN(margin) ? 1.0 : margin, isNaN(yawOffset) ? 0 : yawOffset);
if (!result.droneFrame) return res.status(404).json({ error: `프레임 ${frameNum}을 찾을 수 없습니다` });
res.json(result);
} catch (e) {
res.status(500).json({ error: String(e) });
}
});
/**
* 드론 비행경로 전체 데이터 (경로 시각화용)
* GET /api/geo/frames?step=30 step: 샘플링 간격 (기본 30 = 1초마다)
*/
router.get('/frames', (req, res) => {
const step = Math.max(1, parseInt(String(req.query.step || '30'), 10));
try {
const all = getDroneFrames();
const sampled = all.filter((_, i) => i % step === 0);
res.json(sampled);
} catch (e) {
res.status(500).json({ error: String(e) });
}
});
/**
* 선로 중심선 전체 좌표 (center.csv, 224점)
* GET /api/geo/centerline
*/
router.get('/centerline', (_req, res) => {
try {
const pts = getCenterlinePoints();
res.json(pts);
} catch (e) {
res.status(500).json({ error: String(e) });
}
});
export default router;