목적: 본 문서는 (1) 특허 출원/발명 신고의 기초 자료와 (2) 후임 개발자 인계·업그레이드 자료를 겸한다. 대상 시스템: GhiVideo — 시간축이 아니라 노선 위치(측점·체이니지) 를 1차 좌표로 삼아 드론 주행영상을 색인·탐색하고, 프레임별 카메라 자세로 측점·POI·선로중심선을 영상 위에 실시간 정합(geo-registration)하는 철도 주행영상 분석 플레이어. 작성일: 2026-06-29 · 모든 소스 참조는
[경로](경로#Lxx)형식(클릭 가능).
| 장 | 내용 | 주 독자 |
|---|---|---|
| 1 | 시스템 개요·아키텍처·데이터 흐름 | 전체 |
| 2 | 좌표·투영 정본 파이프라인 (모든 정합의 기반) | 전체 |
| 3 | 카메라 투영 & 화각(FOV) 정합 | 개발/특허 |
| 4 | 측점 표고 정합 (DEM-free) — 발명 B | 특허 |
| 5 | 드론 자세 평활/보간 + 오버레이 렌더링 | 개발 |
| 6 | 측점(체이니지) 시스템 & 스테이션바 | 개발/특허 |
| 7 | 위치보정 역투영 — 발명 C | 특허 |
| 8 | 백엔드 스트리밍/FFmpeg·데이터·상태관리 | 개발 |
| 9 | 개발 타임라인(주요 의사결정) | 전체 |
| 10 | 특허 관점 종합(인벤토리·후보·청구포인트·선행기술) | 특허 |
| 11 | 검증 결과표 | 전체 |
| 12 | 후임자 인계(데이터흐름·튜닝상수·빌드·업그레이드) | 개발 |
| 13 | 한계 / 출원 전 확인사항 | 전체 |
드론으로 촬영한 철도 선로 주행영상의 각 프레임에는 SRT 메타데이터로 드론 위치(lat/lon/고도)·자세(yaw/pitch/roll)·초점거리가 기록된다. 한편 철도 GIS에는 측점·POI·선로 중심선의 WGS84 좌표(+표고) 가 있다. 이 둘을 핀홀 카메라 투영으로 결합하면:
[폴더 선택: base.csv(드론프레임) + building/측점·교량·터널·역사 CSV + route.json + KMZ]
│ geoData.loadFolderGeoData (인코딩 자동감지·병렬 파싱·KMZ 우선)
▼
[geoStore] frames / stations / pois / structures / centerline / origin / routeMeta
│
├─▶ StationOverlay (영상 위 AR 오버레이)
│ precompute(가시성/겹침, requestIdleCallback) → RAF(연속포즈 재투영) → Canvas
│ geoProjection: WGS84→TM→ENU→카메라→핀홀
│
├─▶ StationBar (하단 측점 스크러버)
│ chainage: 드론GPS→측점폴리라인 수직투영→연속 체이니지
│ 이동거리/측점축, 방향색, 구조물 마커, 측점검색 순환
│
└─▶ VideoPlayer (Video.js + hls.js, 이중 재생경로, 프레임 정확도)
child_process.spawn FFmpeg 래퍼, Range Request 스트리밍, HLS 변환 + SSE 진행률.ghiVideo, 포트 55000)가 client/dist 정적 서빙(빌드 결과물). 소스 변경 시 npm run build -w client(Node 20) 후 반영.본 절을 정본으로 하고, 3~7장은 이를 참조한다. Python 레퍼런스
advanced_tuner_v2.py와 회전행렬·축정렬·정규화 공식이 1:1 동치로 이식되어 있다(geoProjection.ts:1-12).
[1] WGS84 (lat, lon, EL정표고)
│ proj4 EPSG:4326 → EPSG:5186 (한국 중부원점 TM)
▼
[2] 월드 ENU [East(m), North(m), Up(m)] (East=X, North=Y)
│ rel = target_ENU − (drone_ENU + offset)
▼
[3] 카메라좌표 [Xc, Yc, Zc] (R_w2c = R_align · R_b2w(yaw,pitch,roll)ᵀ)
│ 핀홀 + focal/sensor
▼
[4] 정규 이미지 좌표 [px, py] ∈ (0~1) (0.5 = 영상 중심)
│ object-fit:cover 변환
▼
[5] 화면 px
단계 1 — WGS84 → TM (geoProjection.ts:75-85)
proj4.defs('EPSG:5186', '+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs');
function latLonToTM(lat, lon) { const [e, n] = _toTM.forward([lon, lat]); return [e, n]; }단계 2 — TM → ENU + 표고 datum 정합 (geoProjection.ts:91-97, 263-271). X/Y는 절대 TM, Z만 기준고도 대비 상대(상대벡터를 빼므로 무관). 핵심: GIS 정표고(EL) + 지오이드고(N) → 타원체고로 변환해 드론 GPS 타원체고와 datum을 맞춘다.
const stEnu = geoToEnu(targetLat, targetLon, targetAlt + (params.geoidOffset ?? 0), ...); // EL + N
const drEnuAdj = [drEnu[0]+offX, drEnu[1]+offY, drEnu[2]+offZ];
const relEnu = [stEnu[0]-drEnuAdj[0], stEnu[1]-drEnuAdj[1], stEnu[2]-drEnuAdj[2]];지오이드고 N: 대전지역 ≈ 25.8m(= 84.244 − 16.853 − 41.59, KNGeoid 검증값). 초기 문서엔 25.449m로 기록된 바 있으나 본 명세는 25.8m로 통일(§11). 지역 상수이므로 타 지역 데이터셋은 재튜닝 필요.
단계 3 — ENU → 카메라좌표 (geoProjection.ts:275-295). R_b2w = Rz(−yaw)·Rx(pitch)·Ry(roll), R_w2c = R_align·R_b2wᵀ, R_align=[[1,0,0],[0,0,−1],[0,1,0]]. 행렬을 명시 생성하지 않고 전치(행=열)·부호치환으로 직접 계산:
function applyRw2c(b2w, rel) {
return {
Xc: b2w[0][0]*rel[0] + b2w[1][0]*rel[1] + b2w[2][0]*rel[2], // col0
Yc: -(b2w[0][2]*rel[0] + b2w[1][2]*rel[1] + b2w[2][2]*rel[2]), // -col2
Zc: b2w[0][1]*rel[0] + b2w[1][1]*rel[1] + b2w[2][1]*rel[2], // col1
};
}규약: Zc>0 = 카메라 정면(영상 깊이축), Xc=우, Yc=하. Zc≤0은 카메라 뒤 → null.
단계 4 — 카메라좌표 → 정규 픽셀(핀홀) (geoProjection.ts:126-137). 영상 중심 = 0.5, 해상도 독립:
pxRaw = (0.5 + cx0) + (Xc/Zc) * (f / sensorW); // 가로
pyRaw = (0.5 + cy0) + (Yc/Zc) * (f / sensorH); // 세로단계 5 — 정규 → 화면 px (object-fit:cover) (StationOverlay.tsx:923-933). 영상 크롭 사각형에 맞춰 정합:
const s = Math.max(W / vW, H / vH); // cover: 더 큰 배율(넘침=크롭)
dispW = vW * s; dispH = vH * s; offX = (W - dispW)/2; offY = (H - dispH)/2;
const vx = (nx) => offX + nx * dispW; const vy = (ny) => offY + ny * dispH;ENU 월드원점은 첫 측점(없으면 첫 드론프레임, 없으면 중심선 첫 점)(geoSearch.ts:117-128).
좌표 변환 파이프라인은 §2 참조. 본 절은 화각·내부표정 보정과 역투영을 다룬다.
화각은 focal과 센서 치수로 결정된다(핀홀): FOV = 2·atan(sensor / (2·focal)) (StationOverlay.tsx:864). 기본값 focal=24mm, sensorW=36mm(풀프레임 환산), sensorH=20.25mm(=36×9/16, 16:9) (geoProjection.ts:48-66). 이 hFOV는 나침반/미니맵 시야부채꼴에도 사용.
라벨이 멀리/가까이에서 상하로 어긋날 때, 사용자가 POI 하나를 영상 속 실제 위치로 드래그하면 그 한 점의 대응으로 세로 화각을 역산한다. 설계 핵심: 가로 focal(f/sensorW)은 건드리지 않고 sensorH만 역산 → 빨간 중심선이 좌우로 틀어지지 않으면서 상하 배율만 교정.
투영식 py = 0.5 + cy0 + (Yc/Zc)·(f/sH) 를 sH에 대해 풀면 (StationOverlay.tsx:1326-1344):
const cc = toCameraCoords(drone, drag.lat, drag.lon, poiZ, p, wo);
const b = cc.Yc / cc.Zc;
const v = pos.y - 0.5 - (p.cy0 ?? 0);
const sHNew = (b * p.focalLen) / v; // sH = (Yc/Zc)·f / (py − 0.5 − cy0)
setParam('sensorH', Math.max(6, Math.min(36, sHNew)));영상 세로 중심(v≈0)은 발산 → |v|<0.03이면 거부하고 안내.
toCameraCoords (geoProjection.ts:301-317) — GIS점→카메라좌표. 부산물 distH(수평 직선거리), fwd/side(진행방향 앞/옆 성분, 비등방 거리필터용). pixelFromCamera와 분리한 이유: 중심선처럼 선분 단위 근거리 Zc 클리핑·보간 후 투영해야 하기 때문.pixelFromCamera (geoProjection.ts:126-137) — 카메라좌표→정규픽셀(클램프 없음).groundPointFromPixel (geoProjection.ts:218-251) — 화면픽셀→지면 lat/lon. 광선-수평면 교차:
const t = relUpTarget / dir[2]; // dir = R_w2cᵀ·((px−0.5−cx0)·sW/f, (py−0.5−cy0)·sH/f, 1)
if (t <= 0) return null; // 카메라 뒤/위
const E = drEnu[0]+offX + t*dir[0]; const N = drEnu[1]+offY + t*dir[1];
const [lon, lat] = _toTM.inverse([E, N]);worldFromPixel(슬랜트거리 유지 lat/lon/z 동시복원, 149-183), solveZForPixelY(lat/lon 고정, py에 맞는 z만 1차식 역산, 194-211). → 발명 C(§7).ghivideo:calib:{baseName} (로드 1회, 저장 800ms debounce, StationOverlay.tsx:473-500).ghivideo:poiov:{baseName} (폴더 파일 override 위에 localStorage 병합, 502-524).표시할 시설물(POI)은 주소 기반이라 표고값이 없는 경우가 대부분이다. 표고가 없으면 부각(수직각) 산출 불가 → 종래는 지면을 평면으로 가정. 그러나 노선은 실제 기복(정표고 41.6m→57.5m)이 있어 평면 가정 시 라벨이 화면 밖/지면 아래로 이탈한다. 실측에서 잘못된 고도(≈0) 사용 시 부각오차 42.7°.
→ 별도 DEM 취득 없이, 노선을 따라 실측된 측점의 정표고를 지면고도원으로 삼아 정합한다.
rel_up = (targetZ + N) − droneAlt.타원체고 = 정표고(EL) + 지오이드고(N≈25.8m, 대전)
# 측점 157K900, frame0, 수평거리 91.3m
정표고≈0 → 부각 42.7° ❌
정표고 41.5 → 부각 25.1°
정표고+N≈67 → 부각 11.0° ✅ (드론 rel_alt 16.85m와 일치)
building/01)측점.csv에 표고 컬럼이 둘 — Z좌표(로컬 ≈0) / Z좌표_한국(EPSG:5186 정표고 ≈41.5m). 기존 코드가 Z좌표(≈0)를 읽어 42.7° 오차 발생 → Z좌표_한국 우선으로 교정(geoData.ts 측점 파서).두 본질 과제: (a) 자세/GPS 노이즈(단순평균은 회전 뭉갬→지연), (b) 렌더 부드러움 vs 계산비용(가시성 판정은 비싸고 60fps는 매 프레임 재투영 요구).
smoothFrameStationOverlay.tsx:577-604. 중심 i에서 좌우 최대 halfWin(기본 60fr≈2초)까지 평균하되, 중심 yaw와 차이가 YAW_EDGE=8° 이내인 인접까지만 창 확장.
const YAW_EDGE = 8;
const near = (idx) => Math.abs(((frames[idx].yaw - yc + 540) % 360) - 180) <= YAW_EDGE;
while (lo > loMin && near(lo - 1)) lo--; // 회전경계에서 멈춤
while (hi < hiMax && near(hi + 1)) hi++;
// yaw는 원형평균: atan2(Σsin, Σcos)
yaw: Math.atan2(sinYaw / n, cosYaw / n) * 180 / Math.PI,poseAtStationOverlay.tsx:670-690. 실수 프레임번호 estFrame에 대해 양 이웃의 평활포즈를 선형보간(이산 30fps 격자→60fps 연속). yaw는 최단각 보간:
let dy = b.yaw - a.yaw; dy = ((dy + 540) % 360) - 180; // 최단각
return { ...a, frame: estFrame, lat:L(a.lat,b.lat), ..., yaw: a.yaw + dy*frac };O(log n) 이진탐색으로 매 RAF 호출 가능.
smoothStep (One-Euro 사상)StationOverlay.tsx:48-61. 느릴 때(떨림)는 강하게 평활, 빠를 때(실제 팬)는 즉시 추종.
if (d > REJECT_DIST && prev.rej < MAX_REJECT_FRAMES) // 0.12 초과 점프 = 이상치 → 위치 유지
return { x: prev.x, y: prev.y, rej: prev.rej + 1, ... };
const vx = prev.vx + (dx - prev.vx) * SMOOTH_VEL_BETA; // β=0.25 속도평활(떨림 왕복은 상쇄)
const a = Math.min(maxAlpha, minAlpha + (maxAlpha - minAlpha) * Math.min(1, speed/speedRef));
return { x: prev.x + dx*a, y: prev.y + dy*a, ... };startLabelPrecompute (StationOverlay.tsx:692-746): 전 프레임의 가시집합·겹침해소 결과를 Map<frameNum, LabelCache>에 사전계산. 화면좌표가 아니라 월드좌표(lat/lon/z)를 저장 → RAF가 매 프레임 연속포즈로 재투영. requestIdleCallback 200프레임 청크, 취소토큰(precomputeIdRef), 현재 프레임부터 순환 처리(재생 즉시 라벨), 첫 청크 즉시 노출, 재계산 시 옛 맵 복사.POI_MERGE_X=0.10/세로 0.035(비등방, 텍스트가 가로로 긴 특성). 우선순위 ①철도역/역사 최우선 ②근접거리. 진행방향 변형 우선: 상/하행 형제 겹침 시 노선 방향((상)/(하)) 변형을 남김.매 프레임 계산 없이 조회+투영+드로잉만. 라이브 시간은 VideoPlayer smoothTimeRef(단조 벽시계 보간, 일시정지·시크·배속 보정)에서 받음.
| 작업 | 비용 | 배치 | 빈도 |
|---|---|---|---|
| 가시성/겹침 판정 | 높음 | precompute | 1회(+설정변경 debounce), idle 청크 |
| 좌표 투영 | 중간 | RAF | 매 프레임 |
| 평활/EMA/히스테리시스 | 낮음 | RAF | 매 프레임 |
| 팝업 가시집합 setState | 리렌더 | setInterval 150ms | 변화 시만 |
핵심 = "무엇을 그릴지"(precompute) ↔︎ "어디에 그릴지"(RAF) 분리로 단일스레드 60fps 달성. 중심선/드론궤적은 buildLines로 매 프레임 직접 투영(근거리 Zc 클리핑·outcode 컬링). 나침반은 별도 ±3fr 평활 yaw + 360° 누적 언랩.
드론 한 프레임의 위경도를 측점 폴리라인에 수직 투영해 100m 양자화가 아닌 10m 해상도 연속 측점값을 얻는다(부산물: 선로 수직 이격 offsetM).
buildChainLine (chainage.ts:22-32): 측점값 오름차순 정렬, 평면투영 x=lon·k, y=lat·111000 (k=cos(lat0)·111000).projectToChain (chainage.ts:35-50):
const t = L2===0?0:Math.max(0,Math.min(1, ((px-a.x)*dx + (py-a.y)*dy)/L2));
const d = (px-cx)**2 + (py-cy)**2;
if (d < bestD) { bestD = d; bestKm = a.km + (b.km-a.km)*t; } // 최근접 선분 위 km 보간km vs 방향용 chain프레임별 precompute(StationBar.tsx:233-246): km=최근접 측점점(100m, 배지/폴백), chain=연속투영(10m, 방향/축/탐지). videoFps는 실데이터(마지막 프레임/재생시간)에서 유도, 미비 시 30000/1001 폴백.
선로 점검 영상은 호버(정지비행) 와 전·후진 반복 특성이 있어 시간축이 부적합하다(호버 시 커서가 위치와 어긋남).
cum += |sm[i]−sm[i−1]|)을 정규화(frac, 단조 비감소). 호버=frac 정체(커서 정지), 이동=frac 증가. 방향은 색 리본으로 직교 분리.cumFracAtTime/timeAtFrac(또는 chainAtTime/pxAtChain)를 이진탐색+보간으로. 커서·구간색·구조물 마커가 동일 매핑 공유. 라이브 커서는 React 리렌더 없이 RAF에서 CSS 변수(--pos-px)만 갱신(771-786).평활 측점값 추세를 극값추적 + 히스테리시스 HYST=10m로 방향 전환점 확정(395-458). forwardDir = arrChain≥depChain?1:−1. 정방향=주황 음영, 역방향=청록. 이동거리/측점축은 구간 px가 단조라 linear-gradient 1장으로 경량 처리(686-726).
kmExists (핵심 ③)두 통과 탐지기:
pxPassesTo(lat,lon,off) (489-534) — 좌표 근접. 임계 TH=max(100, gmin·3).pxPassesAtMileage(targetM) (537-559) — 연속 체이니지가 TH=stationTolerance(기본 20m) 이내.탐지 우선순위(단락평가): ①명시 측점값 ②좌표를 측점선 투영→측점기준(통과순서 정확) ③이름매칭 POI 투영 ④좌표근접 폴백 … (612-639).
역사 합집합 보강: 종점/조차장은 측점선 투영이 불안정해 재진입을 놓침(예 대전조차장 162k080). 단락평가 탓에 한 통과를 찾으면 좌표근접이 안 돌아 누락 → 역사에 한해 좌표근접 통과를 합집합으로 합치되 24px 임계 중복제거(629-634).
kmExists (★): 좌표 반경만으로 잡힌 통과는 실제 측점값이 그 위치에 없을 수 있다.
const kmExists = stationKmVal == null || Math.abs(p.km - stationKmVal) <= existTol; // existTol = stationTolerance(StationBar.tsx:648) false면 측점값 라벨·측점검색에서 제외하되 마커(동그라미)는 유지 — "위치 표시"와 "측점 단정"의 분리.
종점 미도착: endGapPx=min(폭·0.15, (gapM/lenM)·폭), placeUnreachedTerminal이 최우측 역사를 트랙 끝에 미도착(속 빈 링)으로 고정(303-308, 574-586).
handleJumpToMileageStationBar.tsx:850-916. 측점값 입력 시 여러 통과를 통과방향 순환:
structureMarks) 중 kmExists!==false 이고 표시 10m값 일치 → px→timeAtFrac 역변환. (좌표근접 통과 포함 → 모든 동그라미 도달)jumpRef로 연속검색 판정. Enter 후 입력값 유지, blur 시 삭제(PlaybackControls.tsx).splitStructLabel: CJK 1.0/기타 0.55폭, 괄호그룹은 통째로 아랫줄(30-49).showMileage: 직전 표시와 (lastW+w)/2 미만이면 숨김, kmExists===false는 애초 생략(117-137).지오코딩 POI는 ±수십 m 오차. 2D 화면 이동만으로는 카메라로부터의 거리(깊이)를 결정 못함(깊이 모호성). 종래는 수평/높이를 따로 조정하거나 깊이맵(LiDAR) 요구. → 단일 드래그로 수평·수직(3D) 동시 보정, 깊이맵 불요.
<base>_poi_overrides.json + ghivideo:poiov:<baseName> 저장.dir_cam ∝ ((px−0.5−cx0)·sW/f, (py−0.5−cy0)·sH/f, 1)
cam = dir_cam/|dir_cam| · range // ★거리 보존(드래그 시작값)
rel = R_w2cᵀ · cam // 회전 역변환
z = droneAlt + offZ + rel.up − N_geoid // 정표고 복원
(lat,lon) = TM⁻¹(droneE+offX+rel.x, droneN+offY+rel.y)변형 — z 전용 역산 solveZForPixelY: lat/lon 고정, 드롭 세로위치로 z만 1차식 역산. 왕복검증 z=42→py→42.000.
2D 드래그 1회로 3D 보정(단계 절반), 역투영 왕복오차 ≈0, 깊이맵 불요, 파일+localStorage 재현성. "depth map 없이 거리보존" 을 명시해 US9188444B2와 구별(§10).
URL.createObjectURL Blob URL 직접 재생(업로드/변환 없음), 교체 시 revokeObjectURL(누수 방지). 현 운영 주 경로.SHOW_TOOLBAR=false로 숨김.)핵심: 한 응답에 전체를 싣지 않음. 끝값 없으면 start+10MB−1. pipeline()(백프레셔/에러 자동), highWaterMark:1MB, Accept-Ranges:bytes, path traversal 방어(resolveVideoPath). JS Number(2^53−1)로 4GB+ 안전(BigInt 불요). 멀티레인지 미지원.
멱등(디스크에 index.m3u8 있으면 즉시 done), 코덱 분기(H.264 -c copy 수 초 / 그 외 libx264), /progress SSE로 stderr time= 파싱 푸시. hls.js 필수: backBufferLength:30(기본 Infinity면 장시간 재생 메모리 누수→크래시), maxBufferLength 30, enableWorker(useVideoPlayer.ts:7-13).
-y auto, stderr time=HH:MM:SS.cc 정규식 진행률, exit≠0 시 stderr 끝 500자 reject. 프레임 추출은 -accurate_seek -ss {t} -i {f} -frames:v 1(플래그 순서 = 입력단 정확 시킹, frame.ts:37-44).
<input webkitdirectory>/드래그로 받은 File[]을 병렬 파싱(Promise.all). 인코딩 자동감지(BOM→UTF-8/EUC-KR), 헤더명 우선+위치 폴백(makeFieldIndexer), 좌표 유효성 게이트(lat 33~39/lon 124~132), KMZ 우선(있으면 CSV POI/구조물 미사용, fflate unzipSync).
| 소스 | 파서 | 대상 | 매핑 |
|---|---|---|---|
<base>.csv |
parseDroneFrames | DroneFrame[] | frame_cnt, lat/lon/alt, yaw/pitch/roll, focal |
building/01)측점.csv |
parseStations | station + DirectionChange | 측점→title, Z좌표_한국(정표고) 우선, 비고→방향전환 |
<base>_POI.csv / 02)지장물.csv |
parsePois | poi | category_clean/명칭, lat/lon |
02)지장물_역사.csv |
parseHistoricStations | 역사 구조물 | 명칭→name(스테이션바 마커) |
03)교량/04)터널/06)구교.csv |
parseStructures | bridge/tunnel | 구분/시설물명, 연장→lengthM, 시설종별→grade |
05)출입문번호.csv |
parseAccessDoors | 출입문 poi | 출입문번호→title |
*.kmz/kml |
parseKmz | poi+구조물 | Placemark + description표→props |
route.json |
mergeStructures | 보정 | station/offset/이정 override |
중심선은 v2.0에 별도 파일이 없어 측점을 측점값 순 정렬한 폴리라인으로 합성.
basePois(원본) vs pois(override 적용) 분리로 원본 불변성(geoStore.ts:96-110).ghivideo.settings): 시설종별 필터, 표시 토글들. 커스텀 merge로 스키마 진화 대비. isGradeVisible 공유 규칙.data-vjs-player + Strict Mode 가드, controls:false(측점바로 대체), fill:true.requestVideoFrameCallback, 첫 콜백은 시작점만 기록(31fps 오감지 방지), 연속 2회 일치 확정. 스텝은 1/fps(누적 금지=드리프트 방지).smoothTimeRef(매 프레임, transform) + smoothTime(throttle, 배지) 분리.| 시기 | 핵심 결정/구현 |
|---|---|
| 2026-04-01 | labelMap 사전계산 + requestIdleCallback + RAF, ±10fr 이동평균(yaw sin/cos), 30→60fps 보간 |
| 2026-04-02 | 투영 파이프라인 정립: EPSG:5186 TM, 핀홀(Rz·Rx·Ry), 타원체고=정표고+지오이드, Cohen-Sutherland 클리핑 |
| 2026-06-19 | v1→v2 데이터 형상 전환(실측 폴더), 인코딩 자동감지, 측점 비고→방향전환 추출, 시설종별 필터 |
| 2026-06-22 | 측점 표고 컬럼 교정(Z좌표_한국) → 부각 42.7°→11°(발명 B). 역투영 드래그 보정+override(발명 C 최초). title-키 보간+이상치 거부. 비등방 거리필터 |
| 2026-06-23 | 에지보존 평활(YAW_EDGE=8°). 글로벌+KIPRIS 선행기술 조사 → B·C 신규성 확인 |
| 2026-06-24 | 1클릭 FOV 보정(→sensorH 역산), object-fit:cover 정렬, 라벨 월드좌표 저장+RAF 재투영, One-Euro EMA, KMZ 직접 로딩 |
| 2026-06-25~26 | 종점역 누락 수정, 양끝 역 고정, 라벨 2줄분할, 동일항목 브래킷, 거리축→측점축 전환 |
| 2026-06-29 | 좌표/측점 이중탐지+합집합 보강, 측점값 라벨 겹침숨김, 측점검색 순환+마커기준 전환+kmExists, UI 통합(토글 재생바 이동, 활성색 amber), 보고서 3종 생성, 55000 외부접속 |
상세 근거: docs/history/2026-06-22_POI투영오차-개선.md(발명 B·C 1차 출처), 2026-06-26_1520_측점축-전환, 2026-06-29_1029_역사-통과-좌표근접-보강 등.
출처: docs/특허_스테이션기반_주행영상_플레이어.html(기술발굴), docs/발명신고서_측점표고정합_역투영보정.html(변리사 제출용).
| 코드 | 요소 | 특허 적합성 |
|---|---|---|
| E1 | 측점(체이니지) 기반 영상 색인·탐색 | 후보 A(좁게) |
| E2 | 지리객체 실시간 영상 정합(labelMap 사전계산) | 공지(큰 틀) |
| E3 | 측점 표고 기반 DEM-free 지면고도 추정 | 후보 B(상) |
| E4 | 역투영+슬랜트거리 보존 단일드래그 위치보정 | 후보 C(상) |
| E5 | 비등방 거리필터+소실점 클러터 억제 | 후보 D(종속) |
| E6 | 노이즈 강건 오버레이(이중평활·title짝·이상치거부) | 후보 E(종속) |
| E7 | 측점 폴리라인→중심선 | 제외(통상 처리) |
| E8 | 이종 CSV 융합(인코딩 자동감지) | 제외 |
| E9 | 비고 텍스트 방향전환 추출 | 제외 |
| 항목 | 개선 전 | 개선 후 | 근거 |
|---|---|---|---|
| 부각 오차(측점 157K900, 91.3m) | 42.7° | 11.0° | §4.3 |
| 지면고도 정확도(측점 vs 구글 DEM) | — | 0.2m(~6px@2160p) | §4.3 |
| 역투영 왕복오차(z=42→py→z) | — | ≈0 (42.000) | §7.2 |
| 화면 EMA 지연 | 1.65s(α=0.01) | ~25ms(α=0.4) | §5.3 / history 06-22 |
| 렌더 | 끊김 | 60fps(precompute+RAF 분리) | §5.5 |
| 역 재진입 마커 | 누락 | 좌표근접 합집합 보강으로 표출 | §6.5 |
지오이드고 N: 본 명세 25.8m(KNGeoid 검증) 통일. 초기 문서값 25.449m는 각주 처리.
geoStore(frames/stations/structures/routeMeta) → useEffect precompute(viewedRef=프레임별 km/chain/time, chainRef=time/frac/chain) → pxAtTime/pxAtChain·barSegments·structureMarks → Timeline 렌더. 모든 px는 단일 매핑(pxAtTime/pxAtChain)을 통과, 역변환은 timeAtFrac/chainAtTime만 사용.
stationTolerance(통과/검색 허용오차, 20m), direction/directionChanges(방향 변형 필터), endStationGapMeters/lengthKm(미도착 폭), startStationName/endStationName.focalLen, sensorW, sensorH(FOV), yawOffset/pitch/roll, geoidOffset(지역 N), poiZOffset.W=8, YAW_EDGE=8°, HYST=10m, 좌표통과 TH=max(100,gmin·3), 역사 합집합 24px, 검색 중복 0.3s, EMA β=0.25/REJECT_DIST=0.12/MAX_REJECT_FRAMES=8.55000은 PM2 ghiVideo가 client/dist(빌드 결과)를 정적 서빙한다. 소스만 고치면 화면에 반영 안 됨.
export PATH="$HOME/.nvm/versions/node/v20.20.2/bin:$PATH" # 시스템 node v12 → tsc/vite 실패
npm run build -w client # = tsc && vite build → client/dist 갱신
# 브라우저 Ctrl+Shift+R, 폴더 데이터는 런타임 로드라 폴더 재선택 필요외부(LAN) 접속: netsh portproxy(Windows :55000 → WSL IP:55000) + 방화벽. WSL IP는 재부팅 시 변동.
geoidOffset 자동화.getVideoFps, HLS jobs DB 큐, 멀티 Range 지원.projectToChain ↔︎ projectChainage) 단일화.geoidOffset 지역 상수(대전 25.8m) — 타 지역 재튜닝.[확인 필요] 유지): 다중회차 동기화 구현 범위, 위치앵커 주석 데이터모델, 권리귀속, 공개이력.본 문서는 5개 서브시스템(카메라 투영/자세 평활/측점·스테이션바/백엔드·데이터/기존 특허문서) 정밀 조사 결과를 종합한 것이다. 각 소스 참조는 클릭 가능한 파일:라인으로 추적 가능하며, 특허 출원 시 본 문서의 §3~§7·§10과 history 원문을 함께 변리사에게 제공할 것을 권장한다.