Files
GhiVideo_v4/docs/쉬운설명_프레임기반에서_측점기반_플레이어.md
b23042andClaude Fable 5 d38b842e8d GhiVideo 소스 복제 — v4 작업 시작 기준
기존 GhiVideo 저장소 HEAD의 트래킹 소스 362개 파일을 복제.
(node_modules·storage·빌드 산출물·대용량 미디어는 .gitignore 규칙대로 제외)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:32:51 +09:00

246 lines
19 KiB
Markdown

# 시간이 아니라 '측점'으로 움직이는 영상 플레이어 (그림 설명서)
> 이 글은 **초등학생도 이해할 수 있게** 그림과 이야기로 쓴 설명서예요.
> "보통 영상 플레이어는 **시간(프레임)** 으로 움직이는데, 우리는 어떻게 **측점(선로 위치)** 으로 움직이게 바꿨을까?"
> 그 비밀을 하나씩 풀어 줄게요.
> (진짜 코드는 [chainage.ts](../client/src/utils/chainage.ts) · [StationBar.tsx](../client/src/stationbar/StationBar.tsx) 참고)
<style>
figure.fig{margin:22px 0;text-align:center;page-break-inside:avoid;break-inside:avoid;}
figure.fig svg{width:100%;max-width:720px;height:auto;border:1px solid #e7e0d2;border-radius:10px;background:#fffdf8;}
figure.fig figcaption{font-size:0.9em;color:#777;margin-top:6px;}
text{font-family:'Malgun Gothic','Hancom Gothic',sans-serif;}
h2{border-top:3px solid #e7e0d2;padding-top:14px;margin-top:34px;}
code{background:#f5f3ee;padding:1px 4px;border-radius:4px;}
</style>
---
## 0. 한 문장으로 말하면
**"영상의 매 장면마다 드론 GPS로 '지금 몇 측점인지'를 계산해 두어서, 시간(몇 분 몇 초)이 아니라 측점(160k130 같은 선로 위치)으로 영상을 찾아보게 만든 기술"** 이에요.
> 🎯 비유: 보통 영화는 "32분 10초로 가줘"라고 하죠. 우리 플레이어는 **"회덕터널 앞으로 가줘"** 처럼 **장소로** 찾아가요.
---
## 1. 보통 플레이어 vs 우리 플레이어
보통 영상 플레이어의 아래 막대는 **시간**(0:00 ~ 끝)이에요. 그런데 철도 점검하는 분들은 "몇 분"이 아니라 **"몇 측점"** 으로 이야기해요. 그래서 막대를 **측점 기준**으로 바꿨어요.
<figure class="fig">
<svg viewBox="0 0 720 250" role="img" aria-label="시간축 재생바 vs 측점축 재생바 비교">
<text x="360" y="24" text-anchor="middle" font-size="13" font-weight="bold" fill="#475569">① 보통 플레이어 (시간 축)</text>
<rect x="40" y="38" width="640" height="26" rx="6" fill="#e5e7eb" stroke="#9ca3af"/>
<rect x="40" y="38" width="250" height="26" rx="6" fill="#9ca3af"/>
<circle cx="290" cy="51" r="10" fill="#374151"/>
<text x="45" y="82" font-size="11" fill="#374151">0:00</text>
<text x="300" y="82" font-size="11" fill="#374151">03:12</text>
<text x="648" y="82" font-size="11" fill="#374151">09:28</text>
<text x="360" y="104" text-anchor="middle" font-size="11" fill="#6b7280">"3분 12초 지점" — 여기가 어느 선로인지 모름 😵</text>
<text x="360" y="150" text-anchor="middle" font-size="13" font-weight="bold" fill="#166534">② 우리 플레이어 (측점 축)</text>
<rect x="40" y="164" width="640" height="26" rx="6" fill="#fde9c8" stroke="#e0a94f"/>
<rect x="40" y="164" width="310" height="26" rx="6" fill="#f59e0b"/>
<circle cx="350" cy="177" r="11" fill="#ea580c"/><text x="350" y="181" text-anchor="middle" font-size="9" fill="#fff" font-weight="bold">160k</text>
<line x1="205" y1="160" x2="205" y2="194" stroke="#0284c7" stroke-width="2"/><text x="205" y="210" text-anchor="middle" font-size="9" fill="#0369a1">회덕터널</text>
<line x1="480" y1="160" x2="480" y2="194" stroke="#0284c7" stroke-width="2"/><text x="480" y="210" text-anchor="middle" font-size="9" fill="#0369a1">회덕천교</text>
<text x="45" y="230" font-size="11" fill="#166534">대전조차장</text>
<text x="612" y="230" font-size="11" fill="#166534">신탄진</text>
</svg>
<figcaption>시간 축은 "몇 분"만 알려준다. 측점 축은 "지금 어느 선로 위치(측점)", "무슨 구조물 근처"인지 한눈에 보인다.</figcaption>
</figure>
---
## 2. '측점'이 뭐예요? 📍
철도에서 **출발점부터 몇 미터 왔는지**를 나타내는 '거리 이정표'예요. 도로의 **km 표지판**과 똑같아요!
- `160k130` = 출발점에서 **160km + 130m** 지점
- 숫자가 커지면 앞으로 간 것, 작아지면 되돌아온 것
---
## 3. 핵심 비밀 — 매 장면마다 '시간'과 '측점'을 짝지어 둔다 🗺️
비밀은 **드론 GPS**예요. 드론 영상의 **매 프레임(사진 한 장)** 에는 그때 드론의 **위도·경도(GPS)** 가 기록돼 있어요. 이걸 이용해 프레임마다 두 가지를 계산해 **짝지어** 둬요.
<figure class="fig">
<svg viewBox="0 0 720 330" role="img" aria-label="사진 1장마다 시간과 측점을 계산해 표로 모으고, 표로 양방향 변환">
<defs><marker id="ad" markerWidth="10" markerHeight="10" refX="7" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#7c3aed"/></marker></defs>
<!-- ① 왼쪽: 사진 1장 → 두 값 계산 -->
<text x="178" y="22" text-anchor="middle" font-size="12" font-weight="bold" fill="#334155">① 사진 1장마다 두 값을 계산</text>
<rect x="88" y="34" width="180" height="58" rx="8" fill="#f1f5f9" stroke="#475569"/>
<text x="178" y="57" text-anchor="middle" font-size="11.5" font-weight="bold" fill="#334155">🎞 프레임 5760번</text>
<text x="178" y="78" text-anchor="middle" font-size="10" fill="#475569">📍 드론 GPS 함께 기록됨</text>
<line x1="133" y1="92" x2="106" y2="118" stroke="#7c3aed" stroke-width="2" marker-end="url(#ad)"/>
<line x1="223" y1="92" x2="250" y2="118" stroke="#7c3aed" stroke-width="2" marker-end="url(#ad)"/>
<rect x="20" y="122" width="160" height="58" rx="8" fill="#eff6ff" stroke="#2563eb"/>
<text x="100" y="144" text-anchor="middle" font-size="11.5" font-weight="bold" fill="#1e3a8a">⏱ 시간 = 192초</text>
<text x="100" y="166" text-anchor="middle" font-size="9.5" fill="#1e40af">번호 5760 ÷ 30fps</text>
<rect x="196" y="122" width="160" height="58" rx="8" fill="#f0fdf4" stroke="#16a34a"/>
<text x="276" y="144" text-anchor="middle" font-size="11.5" font-weight="bold" fill="#166534">📐 측점 = 160k130</text>
<text x="276" y="166" text-anchor="middle" font-size="9.5" fill="#15803d">GPS를 선로에 '툭' 투영</text>
<!-- 가운데 화살표: 반복 -->
<line x1="368" y1="106" x2="418" y2="106" stroke="#7c3aed" stroke-width="3" marker-end="url(#ad)"/>
<text x="393" y="92" text-anchor="middle" font-size="10" font-weight="bold" fill="#7c3aed">모든 사진에</text>
<text x="393" y="128" text-anchor="middle" font-size="10" font-weight="bold" fill="#7c3aed">반복</text>
<!-- ② 오른쪽: 시간↔측점 대응표 -->
<text x="560" y="22" text-anchor="middle" font-size="12" font-weight="bold" fill="#854d0e">② '시간↔측점 대응표' 완성</text>
<rect x="440" y="34" width="240" height="26" fill="#fef3c7" stroke="#ca8a04"/>
<line x1="560" y1="34" x2="560" y2="190" stroke="#ca8a04" stroke-width="1"/>
<text x="500" y="52" text-anchor="middle" font-size="11" font-weight="bold" fill="#1e3a8a">⏱ 시간</text>
<text x="620" y="52" text-anchor="middle" font-size="11" font-weight="bold" fill="#166534">📐 측점</text>
<rect x="440" y="60" width="240" height="26" fill="#ffffff" stroke="#ca8a04"/>
<text x="500" y="78" text-anchor="middle" font-size="10.5" fill="#334155">191초</text>
<text x="620" y="78" text-anchor="middle" font-size="10.5" fill="#334155">160k100</text>
<rect x="440" y="86" width="240" height="26" fill="#fef9e7" stroke="#ca8a04"/>
<text x="500" y="104" text-anchor="middle" font-size="10.5" font-weight="bold" fill="#1e3a8a">192초</text>
<text x="620" y="104" text-anchor="middle" font-size="10.5" font-weight="bold" fill="#166534">160k130</text>
<rect x="440" y="112" width="240" height="26" fill="#ffffff" stroke="#ca8a04"/>
<text x="500" y="130" text-anchor="middle" font-size="10.5" fill="#334155">193초</text>
<text x="620" y="130" text-anchor="middle" font-size="10.5" fill="#334155">160k160</text>
<rect x="440" y="138" width="240" height="26" fill="#ffffff" stroke="#ca8a04"/>
<text x="500" y="156" text-anchor="middle" font-size="10.5" fill="#94a3b8">⋮</text>
<text x="620" y="156" text-anchor="middle" font-size="10.5" fill="#94a3b8">⋮</text>
<rect x="440" y="164" width="240" height="26" fill="#ffffff" stroke="#ca8a04"/>
<text x="500" y="182" text-anchor="middle" font-size="10.5" fill="#334155">348초</text>
<text x="620" y="182" text-anchor="middle" font-size="10.5" fill="#334155">165k000</text>
<!-- ③ 아래: 표를 쓰는 두 방향 -->
<text x="360" y="232" text-anchor="middle" font-size="12" font-weight="bold" fill="#334155">③ 이 표 하나로, 어느 쪽으로든 찾기</text>
<rect x="110" y="246" width="500" height="30" rx="7" fill="#eff6ff" stroke="#2563eb"/>
<text x="360" y="266" text-anchor="middle" font-size="10.5" fill="#1e3a8a">재생 중: <tspan font-weight="bold">"지금 192초… 여긴 어디?"</tspan> → 표에서 찾아 → <tspan font-weight="bold" fill="#166534">측점 160k130 표시</tspan></text>
<rect x="110" y="284" width="500" height="30" rx="7" fill="#f0fdf4" stroke="#16a34a"/>
<text x="360" y="304" text-anchor="middle" font-size="10.5" fill="#166534">검색: <tspan font-weight="bold">"160k130 가줘!"</tspan> → 표에서 찾아 → <tspan font-weight="bold" fill="#1e3a8a">192초로 점프</tspan></text>
</svg>
<figcaption>사진 1장마다 (시간, 측점) 짝을 계산해(①) 표로 모아 두면(②), "지금 어디?"와 "거기로 가줘!"를 모두 이 표 하나로 해결한다(③). (코드: StationBar precompute)</figcaption>
</figure>
### GPS를 측점으로 바꾸는 법 — '선로에 수직으로 툭' 📐
드론은 선로 바로 위가 아니라 옆에서 비스듬히 날 때도 많아요. 그래서 드론 GPS 점을 **선로(측점들을 이은 선)에 수직으로 툭 떨어뜨려**, 그 발자국이 몇 측점인지 읽어요.
<figure class="fig">
<svg viewBox="0 0 720 170" role="img" aria-label="드론 GPS를 선로에 수직 투영해 측점 읽기">
<line x1="60" y1="120" x2="660" y2="70" stroke="#8a7a5c" stroke-width="6"/>
<text x="70" y="145" font-size="11" fill="#6b4f2a">선로 (측점 158k·159k·160k…를 이은 선)</text>
<circle cx="360" cy="32" r="7" fill="#2563eb"/><text x="360" y="24" text-anchor="middle" font-size="10" fill="#1e3a8a">드론 GPS</text>
<line x1="360" y1="32" x2="374" y2="94" stroke="#ef4444" stroke-width="2" stroke-dasharray="4 3"/>
<circle cx="374" cy="94" r="5" fill="#ef4444"/>
<text x="410" y="98" font-size="11" fill="#b91c1c" font-weight="bold">수직으로 내린 발자국 = 160k130</text>
</svg>
<figcaption>드론이 선로 옆에 있어도, 수직으로 내린 발자국 위치로 '측점값'을 정확히 읽는다. (코드: chainage.ts projectToChain)</figcaption>
</figure>
---
## 4. 재생바의 가로축을 '시간'이 아니라 '실제 이동 거리'로 🚂
여기가 특히 똑똑한 부분이에요. 드론이 **멈춰서 맴돌면** 시간은 계속 흐르지만 **위치(측점)는 그대로**예요. 이때 막대 커서가 계속 가면 이상하죠.
그래서 가로축을 **"실제로 이동한 거리"**(측점이 변한 양을 쌓은 값)로 만들었어요. → **멈추면 커서도 멈추고, 움직일 때만 앞으로 가요.**
<figure class="fig">
<svg viewBox="0 0 720 150" role="img" aria-label="이동거리 축 - 멈추면 커서 정지">
<text x="180" y="22" text-anchor="middle" font-size="12" font-weight="bold" fill="#475569">시간 축 (옛 방식)</text>
<rect x="40" y="34" width="280" height="20" rx="5" fill="#e5e7eb" stroke="#9ca3af"/><circle cx="240" cy="44" r="8" fill="#374151"/>
<text x="180" y="78" text-anchor="middle" font-size="10" fill="#b91c1c">드론이 멈춰 맴돌아도 커서가 계속 감 😵</text>
<text x="540" y="22" text-anchor="middle" font-size="12" font-weight="bold" fill="#166534">이동거리 축 (새 방식)</text>
<rect x="400" y="34" width="280" height="20" rx="5" fill="#fde9c8" stroke="#e0a94f"/><circle cx="560" cy="44" r="8" fill="#ea580c"/>
<text x="540" y="78" text-anchor="middle" font-size="10" fill="#166534">멈추면 커서도 멈춤, 움직일 때만 전진 😎</text>
<text x="360" y="120" text-anchor="middle" font-size="11" fill="#475569">→ 그래서 커서 위치만 봐도 "드론이 실제로 얼마나 이동했는지"를 알 수 있어요.</text>
</svg>
<figcaption>가로축 = 측점 변화량을 쌓은 '이동거리'. 전진이든 후진이든 움직이면 커서는 오른쪽으로 간다(방향은 색으로 구분). (코드: cumFracAtTime / timeAtFrac)</figcaption>
</figure>
---
## 5. 방향을 색으로 — 앞으로 가면 주황, 되돌아오면 파랑 🎨
드론이 앞으로 가서 측점이 커지면 **주황**, 되돌아와서 측점이 작아지면 **하늘색** 으로 칠해요. 그래서 막대만 봐도 **어디서 앞으로/뒤로 갔는지** 알 수 있어요.
<figure class="fig">
<svg viewBox="0 0 720 90" role="img" aria-label="진행 방향 색 구분">
<rect x="40" y="30" width="230" height="24" rx="5" fill="#f59e0b"/><text x="155" y="47" text-anchor="middle" font-size="11" fill="#7a3d00" font-weight="bold">전진(측점 ↑) 주황</text>
<rect x="270" y="30" width="150" height="24" rx="5" fill="#3fb6d4"/><text x="345" y="47" text-anchor="middle" font-size="11" fill="#06455a" font-weight="bold">후진(측점 ↓) 파랑</text>
<rect x="420" y="30" width="260" height="24" rx="5" fill="#f59e0b"/><text x="550" y="47" text-anchor="middle" font-size="11" fill="#7a3d00" font-weight="bold">다시 전진 주황</text>
<text x="360" y="78" text-anchor="middle" font-size="10" fill="#475569">색이 바뀌는 지점 = 드론이 방향을 바꾼 곳</text>
</svg>
</figure>
---
## 6. 구조물을 '측점 위치'에 딱 배치 🚉
교량·터널·역을 각자의 **측점값 위치**에 표시해요. 각 시설물의 실제 측점(예: 회덕터널 160k)과, 드론이 **그 측점을 실제로 지나는 시점**을 맞춰서 막대에 점으로 찍어요. 그래서 "이 구조물이 어디쯤인지"를 막대만 봐도 알 수 있어요.
> 드론이 같은 곳을 **여러 번 지나가면**(조차장에서 왔다갔다) 그 시설물 마커도 **지날 때마다** 찍혀요.
---
## 7. 측점으로 '찾아가기' (검색) 🔎
측점값을 입력하면 → **그 측점이 나오는 시간을 표에서 찾아 → 그 시간으로 이동**해요. (3번에서 만든 '시간↔측점 대응표' 덕분!)
<figure class="fig">
<svg viewBox="0 0 720 90" role="img" aria-label="측점 입력으로 이동">
<defs><marker id="as" markerWidth="10" markerHeight="10" refX="7" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#7c3aed"/></marker></defs>
<rect x="30" y="30" width="180" height="34" rx="6" fill="#f0fdf4" stroke="#16a34a"/><text x="120" y="52" text-anchor="middle" font-size="12" fill="#166534">"160k130" 입력</text>
<line x1="210" y1="47" x2="258" y2="47" stroke="#7c3aed" stroke-width="2" marker-end="url(#as)"/>
<rect x="263" y="30" width="210" height="34" rx="6" fill="#fef3c7" stroke="#ca8a04"/><text x="368" y="52" text-anchor="middle" font-size="11" fill="#854d0e">표에서 시간 찾기 (192초)</text>
<line x1="473" y1="47" x2="521" y2="47" stroke="#7c3aed" stroke-width="2" marker-end="url(#as)"/>
<rect x="526" y="30" width="164" height="34" rx="6" fill="#eff6ff" stroke="#2563eb"/><text x="608" y="52" text-anchor="middle" font-size="12" fill="#1e3a8a">그 시간으로 재생</text>
</svg>
<figcaption>여러 번 지난 측점이면 Enter 를 반복해 다음 통과 지점으로 순환 이동. 없는 측점이면 '측점 없음' 안내.</figcaption>
</figure>
- **실제 측점(드론이 진짜 지난 곳)** 으로만 이동해요. 이 영상에 없는 측점이면 엉뚱한 데로 안 가고 **"측점 없음"** 을 알려줘요.
- 드론이 **끝(종점)까지 못 간** 경우, 종점 측점은 막대 맨 끝에 **'미도착'** 으로 표시해요.
---
## 8. 전체 흐름 한눈에 (컨베이어 벨트처럼) 🏭
<figure class="fig">
<svg viewBox="0 0 720 300" role="img" aria-label="전체 흐름">
<defs><marker id="a8" markerWidth="11" markerHeight="11" refX="7" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#7c3aed"/></marker></defs>
<rect x="210" y="12" width="300" height="40" rx="8" fill="#f1f5f9" stroke="#475569"/>
<text x="360" y="30" text-anchor="middle" font-size="12" font-weight="bold" fill="#334155">드론 영상 + GPS 기록 + 측점 데이터</text>
<text x="360" y="45" text-anchor="middle" font-size="10" fill="#475569">(폴더 선택으로 불러옴)</text>
<line x1="360" y1="52" x2="360" y2="70" stroke="#7c3aed" stroke-width="2" marker-end="url(#a8)"/>
<g font-size="11">
<rect x="150" y="72" width="420" height="30" rx="6" fill="#f0fdf4" stroke="#16a34a"/>
<text x="360" y="92" text-anchor="middle" fill="#166534">① 프레임마다: GPS → 측점(선로에 수직투영), 프레임번호 → 시간</text>
<line x1="360" y1="102" x2="360" y2="118" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#a8)"/>
<rect x="150" y="120" width="420" height="30" rx="6" fill="#fef3c7" stroke="#ca8a04"/>
<text x="360" y="140" text-anchor="middle" fill="#854d0e">② 시간↔측점 대응표 + '이동거리 축' 만들기(멈추면 커서 정지)</text>
<line x1="360" y1="150" x2="360" y2="166" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#a8)"/>
<rect x="150" y="168" width="420" height="30" rx="6" fill="#eff6ff" stroke="#2563eb"/>
<text x="360" y="188" text-anchor="middle" fill="#1e3a8a">③ 막대 그리기: 방향색(주황/파랑) + 구조물(교량·터널·역) 배치</text>
<line x1="360" y1="198" x2="360" y2="214" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#a8)"/>
<rect x="150" y="216" width="420" height="30" rx="6" fill="#fdf2f8" stroke="#db2777"/>
<text x="360" y="236" text-anchor="middle" fill="#9d174d">④ 측점 입력 검색 → 표에서 시간 찾아 그 위치로 이동</text>
<line x1="360" y1="246" x2="360" y2="262" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#a8)"/>
<rect x="210" y="264" width="300" height="28" rx="6" fill="#f5f3ff" stroke="#7c3aed"/>
<text x="360" y="283" text-anchor="middle" fill="#5b21b6" font-weight="bold">🎯 측점으로 보는 주행영상 플레이어 완성</text>
</g>
</svg>
</figure>
| 단계 | 하는 일 | 파일·함수 |
|---|---|---|
| ① GPS→측점 | 드론 위치를 선로에 수직투영 | [chainage.ts](../client/src/utils/chainage.ts) `projectToChain` |
| ① 프레임→시간 | 프레임번호 ÷ fps | StationBar `videoFps` |
| ② 시간↔측점 대응표 | 프레임별 사전계산 | StationBar precompute |
| ② 이동거리 축 | 측점 변화량 누적 | `cumFracAtTime` / `timeAtFrac` |
| ③ 방향색·구조물 | 측점 증감/측점값 기준 | `barSegments` / `structureMarks` |
| ④ 측점 검색 | 측점→시간 역변환 | `handleJumpToMileage` |
---
## 9. 한 문장 결론 🎁
> **"드론 GPS로 매 장면의 측점을 계산해 '시간↔측점 대응표'를 만들어 두었기 때문에, 시간이 아니라 측점(선로 위치)으로 영상을 찾아보고 이동할 수 있게 된 것"** 이에요.
이 덕분에 GhiVideo는 단순 영상이 아니라 **"지금 선로 어디를 보고 있는지 아는" 주행영상 플레이어**가 됩니다. 🚁📏