- 텍스트(측점/POI) 전 프레임 사전 계산 Map (requestIdleCallback 백그라운드) - 드론 데이터 이동 평균 스무딩 (smoothFrame ±N프레임) - 30fps→60fps 프레임 간 선형 보간 (performance.now() 기반) - EMA(지수이동평균) 표시 위치 스무딩 (α=0.01 기본값) - 글씨 2배 크기, bold, strokeText 테두리, 배경 박스 제거 - 카메라 파라미터 패널에 smooth/EMA α 슬라이더 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
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;
|