기존 GhiVideo 저장소 HEAD의 트래킹 소스 362개 파일을 복제. (node_modules·storage·빌드 산출물·대용량 미디어는 .gitignore 규칙대로 제외) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
555 lines
29 KiB
HTML
555 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html xmlns="http://www.w3.org/1999/xhtml" lang xml:lang>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="generator" content="pandoc" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
|
<title>명세_GhiVideo_데이터폴더_입력명세</title>
|
|
<style>
|
|
code{white-space: pre-wrap;}
|
|
span.smallcaps{font-variant: small-caps;}
|
|
span.underline{text-decoration: underline;}
|
|
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
|
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
|
ul.task-list{list-style: none;}
|
|
pre > code.sourceCode { white-space: pre; position: relative; }
|
|
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
|
pre > code.sourceCode > span:empty { height: 1.2em; }
|
|
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
|
div.sourceCode { margin: 1em 0; }
|
|
pre.sourceCode { margin: 0; }
|
|
@media screen {
|
|
div.sourceCode { overflow: auto; }
|
|
}
|
|
@media print {
|
|
pre > code.sourceCode { white-space: pre-wrap; }
|
|
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
|
}
|
|
pre.numberSource code
|
|
{ counter-reset: source-line 0; }
|
|
pre.numberSource code > span
|
|
{ position: relative; left: -4em; counter-increment: source-line; }
|
|
pre.numberSource code > span > a:first-child::before
|
|
{ content: counter(source-line);
|
|
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
|
border: none; display: inline-block;
|
|
-webkit-touch-callout: none; -webkit-user-select: none;
|
|
-khtml-user-select: none; -moz-user-select: none;
|
|
-ms-user-select: none; user-select: none;
|
|
padding: 0 4px; width: 4em;
|
|
color: #aaaaaa;
|
|
}
|
|
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
|
div.sourceCode
|
|
{ }
|
|
@media screen {
|
|
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
|
}
|
|
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
|
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
|
code span.at { color: #7d9029; } /* Attribute */
|
|
code span.bn { color: #40a070; } /* BaseN */
|
|
code span.bu { } /* BuiltIn */
|
|
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
|
code span.ch { color: #4070a0; } /* Char */
|
|
code span.cn { color: #880000; } /* Constant */
|
|
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
|
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
|
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
|
code span.dt { color: #902000; } /* DataType */
|
|
code span.dv { color: #40a070; } /* DecVal */
|
|
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
|
code span.ex { } /* Extension */
|
|
code span.fl { color: #40a070; } /* Float */
|
|
code span.fu { color: #06287e; } /* Function */
|
|
code span.im { } /* Import */
|
|
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
|
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
|
code span.op { color: #666666; } /* Operator */
|
|
code span.ot { color: #007020; } /* Other */
|
|
code span.pp { color: #bc7a00; } /* Preprocessor */
|
|
code span.sc { color: #4070a0; } /* SpecialChar */
|
|
code span.ss { color: #bb6688; } /* SpecialString */
|
|
code span.st { color: #4070a0; } /* String */
|
|
code span.va { color: #19177c; } /* Variable */
|
|
code span.vs { color: #4070a0; } /* VerbatimString */
|
|
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
|
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
|
|
</style>
|
|
<style type="text/css">@page {
|
|
size: A4;
|
|
margin: 18mm 16mm 16mm 16mm;
|
|
@bottom-center {
|
|
content: counter(page) " / " counter(pages);
|
|
font-family: "Malgun Gothic", sans-serif;
|
|
font-size: 9pt;
|
|
color: #999;
|
|
}
|
|
}
|
|
html { font-size: 11pt; }
|
|
body {
|
|
font-family: "Malgun Gothic", "Hancom Gothic", sans-serif;
|
|
color: #23272e;
|
|
line-height: 1.65;
|
|
max-width: 920px;
|
|
margin: 0 auto;
|
|
padding: 24px;
|
|
}
|
|
h1 {
|
|
font-size: 1.7rem;
|
|
color: #b45309;
|
|
border-bottom: 3px solid #f59e0b;
|
|
padding-bottom: 8px;
|
|
margin: 0 0 4px;
|
|
}
|
|
h2 {
|
|
font-size: 1.25rem;
|
|
color: #b45309;
|
|
border-bottom: 1px solid #e5d3b3;
|
|
padding-bottom: 5px;
|
|
margin-top: 1.6em;
|
|
}
|
|
h3 { font-size: 1.05rem; color: #92400e; margin-top: 1.1em; }
|
|
a { color: #b45309; }
|
|
hr { border: none; border-top: 1px solid #e2e2e2; margin: 1.6em 0; }
|
|
ul { padding-left: 1.25em; }
|
|
li { margin: 0.18em 0; }
|
|
strong { color: #1f2937; }
|
|
code {
|
|
font-family: "D2Coding", Consolas, monospace;
|
|
background: #f4f1ea;
|
|
border: 1px solid #e7e0d2;
|
|
border-radius: 3px;
|
|
padding: 0.5px 5px;
|
|
font-size: 0.92em;
|
|
}
|
|
table {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin: 0.8em 0;
|
|
font-size: 0.95em;
|
|
}
|
|
th, td { border: 1px solid #d8d2c4; padding: 6px 10px; text-align: left; vertical-align: top; }
|
|
th { background: #fdf3df; color: #7c2d12; }
|
|
blockquote {
|
|
border-left: 4px solid #f59e0b;
|
|
margin: 0.8em 0;
|
|
padding: 0.2em 0 0.2em 14px;
|
|
color: #555;
|
|
background: #fffbf2;
|
|
}
|
|
|
|
h1, h2, h3 { break-after: avoid; }
|
|
</style>
|
|
<!--[if lt IE 9]>
|
|
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
|
<![endif]-->
|
|
</head>
|
|
<body>
|
|
<header id="title-block-header">
|
|
<h1 class="title">명세_GhiVideo_데이터폴더_입력명세</h1>
|
|
</header>
|
|
<h1 id="데이터-폴더-입력-명세--스테이션기반-플레이어">데이터 폴더 입력 명세 — 스테이션기반 플레이어</h1>
|
|
<blockquote>
|
|
<p>웹플레이어가 <strong>동영상이 있는 폴더</strong>를 전달받아 재생할 때, 폴더 안에 어떤 파일이 어떤 형식으로 있어야 하는지를 정의한다. 근거: <code>client/src/utils/geoData.ts</code>(폴더 파서) 기준. 실데이터(v3.0, 대전조차장–회덕)로 검증.</p>
|
|
</blockquote>
|
|
<hr />
|
|
<h2 id="1-동작-개요">1. 동작 개요</h2>
|
|
<ol type="1">
|
|
<li>사용자가 브라우저에서 <strong>폴더를 선택</strong>하면(<code><input type="file" webkitdirectory></code>), 폴더 내 전체 파일 목록이 플레이어에 전달된다.</li>
|
|
<li>플레이어는 파일명 규칙으로 각 파일을 <strong>자동 식별·파싱</strong>한다(별도 설정 불필요).</li>
|
|
<li>기준 이름 <strong>base</strong> = 영상 파일명에서 확장자를 뺀 것 (예: <code>상행)대전조차장-회덕.MP4</code> → base는 <code>상행)대전조차장-회덕</code>). 나머지 파일은 base 일치를 우선 매칭하고, 없으면 폴백 규칙으로 찾는다.</li>
|
|
</ol>
|
|
<hr />
|
|
<h2 id="2-폴더-구성-요약">2. 폴더 구성 요약</h2>
|
|
<table>
|
|
<thead>
|
|
<tr class="header">
|
|
<th>#</th>
|
|
<th>파일</th>
|
|
<th>필수</th>
|
|
<th>역할</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="odd">
|
|
<td>1</td>
|
|
<td><code><base>.MP4</code></td>
|
|
<td><strong>필수</strong></td>
|
|
<td>드론 촬영 항공영상 (mp4/webm)</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>2</td>
|
|
<td><code><base>.csv</code></td>
|
|
<td><strong>필수</strong></td>
|
|
<td>드론 비행로그 — 프레임별 위치·자세각·초점거리</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>3</td>
|
|
<td><code>01)측점.csv</code> 또는 <code>측점.csv</code></td>
|
|
<td><strong>필수</strong></td>
|
|
<td>측점 목록(이정·좌표·실측 표고) → 중심선·스테이션바·POI 높이 보완의 근간</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>4</td>
|
|
<td><code><base>.kmz</code> (또는 <code>.kml</code>)</td>
|
|
<td><strong>필수</strong>(정책)</td>
|
|
<td>POI·구조물의 <strong>유일한 소스</strong> (지장물·교량·터널·출입문·구교·철도역)</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>5</td>
|
|
<td><code><base>.route.json</code> 또는 <code>route.json</code></td>
|
|
<td>선택</td>
|
|
<td>노선 기본 정보 + 구조물 위치 보정</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>6</td>
|
|
<td><code><base>_poi_overrides.json</code></td>
|
|
<td>선택</td>
|
|
<td>POI 드래그 보정 저장값 (역투영 결과)</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>—</td>
|
|
<td><code>.srt</code>, <code>.qgz</code>, <code>.qgs</code> 등 그 외 파일</td>
|
|
<td>무시</td>
|
|
<td>플레이어가 읽지 않음 (작업용 부산물)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<p><strong>예시 폴더</strong> (실데이터 v3.0):</p>
|
|
<pre><code>상행)대전조차장-회덕 데이터(v3.0)/
|
|
├── 상행)대전조차장-회덕.MP4 ← 영상 (base 기준)
|
|
├── 상행)대전조차장-회덕.csv ← 드론 비행로그
|
|
├── 01)측점.csv ← 측점 (루트 또는 building/ 하위 모두 허용)
|
|
├── 상행)대전조차장-회덕.kmz ← POI·구조물 원본
|
|
├── 상행)대전조차장-회덕.route.json ← 노선 정보 (선택)
|
|
├── 상행)대전조차장-회덕.srt ← (무시됨)
|
|
└── 상행)대전조차장-회덕.qgz ← (무시됨, QGIS 작업파일)</code></pre>
|
|
<hr />
|
|
<h2 id="3-파일별-상세-명세">3. 파일별 상세 명세</h2>
|
|
<h3 id="31-영상--basemp4">3.1 영상 — <code><base>.MP4</code></h3>
|
|
<ul>
|
|
<li><strong>확장자</strong>: <code>.mp4</code> 또는 <code>.webm</code> (대소문자 무관). <code>building/</code> 하위가 아닌 <strong>루트의 첫 번째 영상 파일</strong>을 사용한다.</li>
|
|
<li><strong>코덱</strong>: 브라우저 직접 재생이므로 <strong>H.264 권장</strong>.</li>
|
|
<li><strong>용량</strong>: 2GB 이상 가능 (<code>URL.createObjectURL()</code> 참조 방식 — 메모리에 통째로 올리지 않음).</li>
|
|
<li>이 파일명이 폴더 전체의 <strong>base</strong> 를 결정한다.</li>
|
|
</ul>
|
|
<h3 id="32-드론-비행로그--basecsv">3.2 드론 비행로그 — <code><base>.csv</code></h3>
|
|
<p>드론이 촬영과 동시에 기록한 프레임별 데이터. <strong>헤더 이름으로 컬럼을 찾으며</strong>(순서 무관), 헤더가 깨졌을 때만 위치 인덱스로 폴백한다.</p>
|
|
<table>
|
|
<thead>
|
|
<tr class="header">
|
|
<th>컬럼(헤더명)</th>
|
|
<th>의미</th>
|
|
<th>단위/규약</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="odd">
|
|
<td><code>frame_cnt</code></td>
|
|
<td>프레임 번호 (0부터)</td>
|
|
<td>정수</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td><code>latitude</code> / <code>longitude</code></td>
|
|
<td>드론 위치 위도/경도</td>
|
|
<td>도(WGS84)</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td><code>altitude</code></td>
|
|
<td>드론 고도</td>
|
|
<td>m (타원체고)</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td><code>yaw</code></td>
|
|
<td>기수 방위각</td>
|
|
<td>도 — 북=0, 시계방향</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td><code>pitch</code></td>
|
|
<td>카메라 틸트</td>
|
|
<td>도 — 음수=아래</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td><code>roll</code></td>
|
|
<td>롤</td>
|
|
<td>도</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td><code>focal_len</code></td>
|
|
<td>초점거리</td>
|
|
<td>mm (<strong>35mm 환산</strong>)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<ul>
|
|
<li>추가 컬럼(<code>rel_alt</code>, <code>5186x</code>, <code>5186y</code>, <code>X좌표</code>, <code>Y좌표</code> 등)이 있어도 <strong>무시되고 오류 없음</strong>.</li>
|
|
<li><code>latitude</code> 가 숫자가 아닌 행은 건너뜀.</li>
|
|
<li><strong>파일 선택 규칙</strong>: 루트의 <code>.csv</code> 중 ① <code><base>.csv</code> 정확 일치 우선, ② 없으면 파일명에 <code>POI</code> 가 없는 첫 <code>.csv</code> (영상명 비의존 폴백).</li>
|
|
<li><strong>fps 자동 산출</strong>: 최대 <code>frame_cnt</code> ÷ 영상 길이(초) → 표준 fps(24/25/29.97/30/50/60…)에 스냅. 따라서 <strong>비행로그의 프레임 수는 영상의 실제 프레임 수와 일치해야 한다.</strong></li>
|
|
</ul>
|
|
<h3 id="33-측점--01측점csv-또는-측점csv">3.3 측점 — <code>01)측점.csv</code> (또는 <code>측점.csv</code>)</h3>
|
|
<p>철도 노선 측량 성과. 시간↔︎측점 대응표(스테이션바)와 선로 중심선, POI 높이 보완의 근간 데이터.</p>
|
|
<ul>
|
|
<li><strong>파일 찾기 규칙</strong> (3단 폴백): ① <code>building/</code> 하위에서 파일명에 <code>01)측점</code> 포함 → ② 루트 포함 아무 위치에서 <code>01)측점</code> 포함 → ③ 파일명에 <code>측점</code> 포함.</li>
|
|
<li><strong>컬럼</strong> (헤더 이름 기준):</li>
|
|
</ul>
|
|
<table>
|
|
<thead>
|
|
<tr class="header">
|
|
<th>컬럼</th>
|
|
<th>필수</th>
|
|
<th>의미</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="odd">
|
|
<td><code>측점</code></td>
|
|
<td><strong>필수</strong></td>
|
|
<td>이정 표기 (예 <code>157K900</code>) — 스테이션 정렬·표시 기준</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td><code>lat</code> / <code>lon</code></td>
|
|
<td><strong>필수</strong></td>
|
|
<td>측점 좌표 (도, WGS84) — 숫자 아니면 해당 행 제외</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td><code>Z좌표_한국</code></td>
|
|
<td>권장</td>
|
|
<td><strong>실측 정표고(EL, m)</strong> — 있으면 최우선 사용</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td><code>Z좌표</code></td>
|
|
<td>폴백</td>
|
|
<td><code>Z좌표_한국</code> 이 없을 때 사용</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td><code>비고</code></td>
|
|
<td>선택</td>
|
|
<td><code>방향전환점(상행->하행, 02:05)</code> 패턴이 있으면 방향전환점으로 추출</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<ul>
|
|
<li><strong>중심선 자동 생성</strong>: v2.0 이후 <code>center.csv</code> 가 없으므로, 측점을 이정 순으로 이어 만든 폴리라인이 선로 중심선이 된다. → <strong>측점 정확도가 곧 측점 투영·POI 높이 보완의 정확도.</strong></li>
|
|
</ul>
|
|
<blockquote>
|
|
<p>⚠️ <strong>주의 (실데이터에서 발견)</strong>: 상행 v3.0의 <code>01)측점.csv</code> 는 실측 표고가 <code>z</code> 컬럼에 있고 <code>Z좌표</code> 컬럼은 0.0 인데, 파서는 <code>Z좌표_한국</code> → <code>Z좌표</code> 순서로만 읽으므로 <strong>표고가 0으로 로드된다</strong>. 하행 파일처럼 표고 컬럼명을 <strong><code>Z좌표_한국</code> 으로 통일</strong>하거나, 파서에 <code>z</code> 컬럼 폴백 추가가 필요하다.</p>
|
|
</blockquote>
|
|
<h3 id="34-poi구조물--basekmz-또는-kml">3.4 POI·구조물 — <code><base>.kmz</code> (또는 <code>.kml</code>)</h3>
|
|
<p><strong>POI·구조물의 유일한 소스</strong>다 (<code>building/</code> 하위 02)~06) CSV는 더 이상 읽지 않음). 루트의 bare <code>.kml</code> 이 있으면 우선, 없으면 <code>.kmz</code>(zip)를 해제해 <code>doc.kml</code> 을 읽는다.</p>
|
|
<ul>
|
|
<li><strong>KML 폴더 구조 → 분류 매핑</strong>:</li>
|
|
</ul>
|
|
<table>
|
|
<thead>
|
|
<tr class="header">
|
|
<th>KML 폴더명 (포함 키워드)</th>
|
|
<th>분류</th>
|
|
<th>이름 필드 (description 표)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="odd">
|
|
<td><code>03)교량</code></td>
|
|
<td>구조물 bridge</td>
|
|
<td><code>구분</code> (+ <code>연장(m)</code>, <code>시설종별</code>)</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td><code>04)터널</code></td>
|
|
<td>구조물 tunnel</td>
|
|
<td><code>구분</code> (+ <code>연장(m)</code>, <code>시설종별</code>)</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td><code>06)구교</code></td>
|
|
<td>구조물 bridge(구교)</td>
|
|
<td><code>시설물명</code> (+ <code>연장(m)</code>)</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td><code>05)출입문번호</code></td>
|
|
<td>POI (출입문)</td>
|
|
<td><code>출입문번호</code></td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td><code>02)지장물</code> 등 그 외</td>
|
|
<td>POI (지장물 등)</td>
|
|
<td><code>구분</code> → <code>title</code> → placemark <code>name</code> 순</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>— <code>source=KAKAO_RAIL</code> 또는 <code>category_clean=철도역/역사</code></td>
|
|
<td>POI + <strong>역사 구조물</strong> (스테이션바에도 표출)</td>
|
|
<td>위와 동일</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<ul>
|
|
<li><strong>Placemark 좌표</strong>: <code><coordinates></code> 의 <code>lon,lat[,alt]</code> 우선. 없으면 description 속성 <code>lat</code>/<code>lon</code>. <strong>한국 범위 검증</strong>(위도 33~39, 경도 124~132) 밖이면 버림.</li>
|
|
<li><strong>속성(description)</strong>: HTML <code><table></code> 의 (키, 값) 행으로 기재 — <code>구분</code>, <code>연장(m)</code>, <code>시설종별</code>(1종/2종/3종/기타), <code>시설물명</code>, <code>출입문번호</code>, <code>source</code>, <code>category_clean</code>, <code>title</code>, 표고(<code>z</code>/<code>Z좌표</code>/<code>절대고도</code>) 등. 구글어스 재저장본의 표 변형(<code><tbody></code>, 엔티티 등)도 허용.</li>
|
|
<li><strong>표고</strong>: POI 속성에 <code>z</code>/<code>절대고도</code> 가 있으면 사용, 없으면 0 → 재생 시 <strong>최근접 측점 실측 표고로 보완</strong>된다.</li>
|
|
<li><strong>누락 시</strong>: 경고 로그 후 POI·구조물 없이 동작(측점·드론·영상은 정상). CSV 폴백은 없다.</li>
|
|
</ul>
|
|
<h3 id="35-노선-기본-정보--baseroutejson-선택">3.5 노선 기본 정보 — <code><base>.route.json</code> (선택)</h3>
|
|
<p>루트의 <code><base>.route.json</code> 우선, 없으면 <code>route.json</code>. UTF-8 JSON.</p>
|
|
<div class="sourceCode" id="cb2"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
|
|
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">"routeInfo"</span><span class="fu">:</span> <span class="fu">{</span></span>
|
|
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">"name"</span><span class="fu">:</span> <span class="st">"대전조차장-회덕"</span><span class="fu">,</span> <span class="er">//</span> <span class="er">노선명</span> <span class="er">(표시용)</span></span>
|
|
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">"direction"</span><span class="fu">:</span> <span class="st">"상행"</span><span class="fu">,</span> <span class="er">//</span> <span class="er">방향</span> <span class="er">라벨</span></span>
|
|
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">"lengthKm"</span><span class="fu">:</span> <span class="fl">4.25</span><span class="fu">,</span> <span class="er">//</span> <span class="er">구간</span> <span class="er">연장</span> <span class="er">(km)</span></span>
|
|
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">"durationSec"</span><span class="fu">:</span> <span class="dv">568</span><span class="fu">,</span> <span class="er">//</span> <span class="er">영상</span> <span class="er">길이</span> <span class="er">(초)</span></span>
|
|
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">"startStationName"</span><span class="fu">:</span> <span class="st">"대전"</span><span class="fu">,</span> <span class="er">//</span> <span class="er">시점역</span></span>
|
|
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">"endStationName"</span><span class="fu">:</span> <span class="st">"신탄진"</span><span class="fu">,</span> <span class="er">//</span> <span class="er">종점역</span></span>
|
|
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">"stationTolerance"</span><span class="fu">:</span> <span class="dv">20</span><span class="fu">,</span> <span class="er">//</span> <span class="er">측점</span> <span class="er">인정</span> <span class="er">반경(m)</span> <span class="er">—</span> <span class="er">기본</span> <span class="er">40</span></span>
|
|
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">"endStationGapMeters"</span><span class="fu">:</span> <span class="dv">0</span> <span class="er">//</span> <span class="er">종점</span> <span class="er">미도착</span> <span class="er">거리(m)</span> <span class="er">—</span> <span class="dv">0</span><span class="er">이면</span> <span class="er">도착</span></span>
|
|
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a> <span class="fu">},</span></span>
|
|
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a> <span class="dt">"structures"</span><span class="fu">:</span> <span class="ot">[</span> <span class="er">//</span> <span class="er">(선택)</span> <span class="er">구조물</span> <span class="er">보정</span> <span class="er">—</span> <span class="er">name</span> <span class="er">매칭으로</span> <span class="er">override</span></span>
|
|
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a> <span class="fu">{</span></span>
|
|
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a> <span class="dt">"name"</span><span class="fu">:</span> <span class="st">"회덕천교"</span><span class="fu">,</span> <span class="er">//</span> <span class="er">괄호</span> <span class="er">이하</span> <span class="er">제거</span> <span class="er">후</span> <span class="er">부분일치</span> <span class="er">매칭</span></span>
|
|
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a> <span class="dt">"station"</span><span class="fu">:</span> <span class="st">"158k400"</span><span class="fu">,</span> <span class="er">//</span> <span class="er">최우선</span><span class="fu">:</span> <span class="er">이</span> <span class="er">측점값</span> <span class="er">위치에</span> <span class="er">배치</span> <span class="er">(숫자</span> <span class="er">m</span> <span class="er">또는</span> <span class="st">"158k400"</span><span class="er">)</span></span>
|
|
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a> <span class="st">"offset"</span><span class="er">:</span> <span class="dv">-10</span><span class="fu">,</span> <span class="er">//</span> <span class="er">진행방향</span> <span class="er">±N</span> <span class="er">m</span> <span class="er">미세조정</span></span>
|
|
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a> <span class="dt">"startMileage"</span><span class="fu">:</span> <span class="dv">158400</span><span class="fu">,</span> <span class="er">//</span> <span class="er">매칭</span> <span class="er">실패</span> <span class="er">시</span> <span class="er">폴백</span> <span class="er">이정</span></span>
|
|
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a> <span class="dt">"endMileage"</span><span class="fu">:</span> <span class="dv">158500</span><span class="fu">,</span></span>
|
|
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">"lat"</span><span class="fu">:</span> <span class="fl">36.4031</span><span class="fu">,</span> <span class="dt">"lon"</span><span class="fu">:</span> <span class="fl">127.4216</span> <span class="er">//</span> <span class="er">좌표</span> <span class="er">직접</span> <span class="er">지정도</span> <span class="er">가능</span></span>
|
|
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
|
|
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a> <span class="ot">]</span></span>
|
|
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
|
|
<ul>
|
|
<li><code>structures</code> 항목은 KMZ 유래 구조물에 <strong>명시된 필드만 덮어쓰고</strong>, KMZ에 없는 항목은 새 구조물로 추가된다.</li>
|
|
</ul>
|
|
<h3 id="36-poi-위치-보정--base_poi_overridesjson-선택">3.6 POI 위치 보정 — <code><base>_poi_overrides.json</code> (선택)</h3>
|
|
<p>영상에서 POI 라벨을 <strong>드래그(역투영)</strong> 해 보정한 좌표의 저장/배포 파일. 폴더에 있으면 로드 시 자동 적용된다.</p>
|
|
<div class="sourceCode" id="cb3"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
|
|
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">"baseName"</span><span class="fu">:</span> <span class="st">"상행)대전조차장-회덕"</span><span class="fu">,</span></span>
|
|
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">"overrides"</span><span class="fu">:</span> <span class="fu">{</span></span>
|
|
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">"회덕터널"</span><span class="fu">:</span> <span class="fu">{</span> <span class="dt">"lat"</span><span class="fu">:</span> <span class="fl">36.40312</span><span class="fu">,</span> <span class="dt">"lon"</span><span class="fu">:</span> <span class="fl">127.42169</span><span class="fu">,</span> <span class="dt">"z"</span><span class="fu">:</span> <span class="fl">66.1</span> <span class="fu">}</span></span>
|
|
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
|
|
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
|
|
<ul>
|
|
<li><code>overrides</code> 없이 평면 맵(<code>{ "회덕터널": {…} }</code>)도 허용. <code>z</code> 는 정표고(m). POI <code>title</code> 정확 일치로 매칭.</li>
|
|
</ul>
|
|
<hr />
|
|
<h2 id="4-공통-규칙-인코딩파싱">4. 공통 규칙 (인코딩·파싱)</h2>
|
|
<table>
|
|
<thead>
|
|
<tr class="header">
|
|
<th>항목</th>
|
|
<th>규칙</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="odd">
|
|
<td>CSV 인코딩</td>
|
|
<td><strong>자동 감지</strong> — 앞 3바이트가 UTF-8 BOM(EF BB BF)이면 UTF-8, 아니면 EUC-KR. 파일별 하드코딩 없음</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>CSV 컬럼</td>
|
|
<td><strong>헤더 이름으로 탐색</strong> (순서 무관, 여분 컬럼 무시). 헤더 훼손 시 위치 인덱스 폴백</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>CSV 형식</td>
|
|
<td>큰따옴표 필드 지원, 빈 줄 무시, <code>\r\n</code>/<code>\n</code> 모두 허용</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>KML</td>
|
|
<td>UTF-8. <code>.kml</code> > <code>.kmz</code>(내부 <code>doc.kml</code>) 우선순위</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>JSON</td>
|
|
<td>UTF-8. 파싱 실패 시 조용히 무시(선택 파일이므로)</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>폴더 구조</td>
|
|
<td><code>building/</code> 하위는 측점 CSV 탐색 외에는 사용하지 않음. 하위 폴더 깊이는 1단계(<code>building/</code>)만 인식</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<hr />
|
|
<h2 id="5-파일-누락-시-동작">5. 파일 누락 시 동작</h2>
|
|
<table>
|
|
<thead>
|
|
<tr class="header">
|
|
<th>누락 파일</th>
|
|
<th>동작</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="odd">
|
|
<td>영상</td>
|
|
<td>재생 불가 (폴더 인식 실패)</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>드론 비행로그</td>
|
|
<td>측점 투영·POI 오버레이·fps 산출 불가 → 사실상 일반 플레이어로 전락</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>측점 CSV</td>
|
|
<td>중심선·스테이션바·POI 높이 보완 불가 (시간축 재생만 가능)</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>KMZ/KML</td>
|
|
<td><strong>경고 로그</strong> 후 POI·구조물 미표시 (측점·드론·영상은 정상 동작)</td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>route.json</td>
|
|
<td>기본값으로 동작 (노선명 등 미표시, stationTolerance=40)</td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>poi_overrides.json</td>
|
|
<td>보정 없이 원본 좌표 사용</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<hr />
|
|
<h2 id="부록--구현-근거-소스-위치">부록 — 구현 근거 (소스 위치)</h2>
|
|
<table>
|
|
<thead>
|
|
<tr class="header">
|
|
<th>항목</th>
|
|
<th>위치</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="odd">
|
|
<td>폴더 통합 로더</td>
|
|
<td><code>client/src/utils/geoData.ts</code> — <code>loadFolderGeoData()</code></td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>영상/base 식별</td>
|
|
<td>같은 파일 — <code>findVideoFile()</code>, <code>deriveBaseName()</code></td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>드론 CSV 파서</td>
|
|
<td>같은 파일 — <code>parseDroneFrames()</code></td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>측점 CSV 파서 (+방향전환점)</td>
|
|
<td>같은 파일 — <code>parseStations()</code></td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>KMZ/KML 파서</td>
|
|
<td>같은 파일 — <code>parseKmz()</code></td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>route.json / 구조물 병합</td>
|
|
<td>같은 파일 — <code>parseRouteMeta()</code>, <code>mergeStructures()</code></td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>POI 보정 로드/적용</td>
|
|
<td>같은 파일 — <code>parsePoiOverrides()</code>, <code>applyPoiOverrides()</code></td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>인코딩 자동 감지</td>
|
|
<td>같은 파일 — <code>decodeBytes()</code></td>
|
|
</tr>
|
|
<tr class="odd">
|
|
<td>중심선 생성</td>
|
|
<td>같은 파일 — <code>buildCenterlineFromStations()</code></td>
|
|
</tr>
|
|
<tr class="even">
|
|
<td>(참고) 서버 검색 API 용 데이터</td>
|
|
<td><code>server/src/routes/geo.ts</code> — 환경변수 <code>GEO_DATA_DIR</code> 폴더에 동일 형상 필요</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</body>
|
|
</html>
|