Files
GhiVideo/docs/쉬운설명_지도POI를_영상에_붙이는기술.md
b23042andClaude Opus 4.8 e0ff5dd6d0 feat: 지도 나침반(OSM/위성) 추가 + 측점 검색·마커 정확도 개선 + POI/영상 수정
- 지도 기반 나침반(노스업, 현재위치, 시야 역삼각형): 호버 확대·휠 줌·클릭 위성전환, OSM/Esri 타일 서버 프록시(/api/tile)
- 스테이션 검색: 실제 측점(chain) 기준 이동, 없으면 '측점 없음' 안내
- 역 마커: 직교 투영 측점 일치 시에만 표시, 미도착 종점은 추가 방식
- POI 팝업 겹침/재등장·라벨 정합 수정
- 영상 fps 데이터 기반 자동 산출
- 기술/발표/쉬운설명 문서 추가

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:03:30 +09:00

516 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 지도 위의 POI를 영상에 '딱' 붙이는 기술들 (그림 설명서)
> 이 글은 **초등학생도 이해할 수 있게** 쓴 설명서예요.
> "지도에 적힌 다리·터널·역의 위치(POI)를, 드론 영상의 **정확한 화면 자리**에 어떻게 붙이는가?"
> 그 비밀을 **그림과 이야기**로 하나씩 풀어 줄게요.
> (진짜 수학 공식은 같은 폴더의 기술문서 [geoProjection.ts](../client/src/utils/geoProjection.ts)를 보세요.)
<style>
figure.fig{margin:20px 0;text-align:center;page-break-inside:avoid;break-inside:avoid;}
figure.fig svg{width:100%;max-width:680px;height:auto;border:1px solid #e7e0d2;border-radius:8px;background:#fffdf8;}
figure.fig figcaption{font-size:0.9em;color:#777;margin-top:6px;}
text{font-family:'Malgun Gothic','Hancom Gothic',sans-serif;}
</style>
---
## 0. 한 문장으로 말하면
**"지도에서는 ○○다리가 여기 있어요"** 라는 위치 정보(POI)를 받아서,
**"그럼 드론 영상 화면에서는 바로 이 자리에 보이겠네!"** 하고 글자를 딱 붙여 주는 기술이에요.
> 🎯 **비유**: 보물지도에 "보물은 큰 나무 옆"이라고 적혀 있을 때,
> 내가 지금 서 있는 자리와 보는 방향을 알면 "저 나무가 내 눈앞 어디쯤 보일지" 알 수 있죠. 그걸 컴퓨터가 하는 거예요.
---
## 1. 큰 그림 먼저 — POI가 화면에 붙기까지 여행
POI 하나가 화면에 붙으려면 **여러 단계의 변신**을 거쳐요. 마치 공장의 컨베이어 벨트처럼요.
<figure class="fig">
<svg viewBox="0 0 680 280" role="img" aria-label="POI 매핑 전체 흐름">
<defs><marker id="p1a" markerWidth="10" markerHeight="10" refX="7" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#b45309"/></marker></defs>
<rect x="30" y="30" width="180" height="50" rx="8" fill="#ecfdf5" stroke="#16a34a"/>
<text x="120" y="52" text-anchor="middle" font-size="13" font-weight="bold" fill="#166534">① 지도 위치</text>
<text x="120" y="70" text-anchor="middle" font-size="11" fill="#166534">위도·경도 (각도)</text>
<rect x="250" y="30" width="180" height="50" rx="8" fill="#eff6ff" stroke="#2563eb"/>
<text x="340" y="52" text-anchor="middle" font-size="13" font-weight="bold" fill="#1e3a8a">② 미터 좌표</text>
<text x="340" y="70" text-anchor="middle" font-size="11" fill="#1e3a8a">동 몇 m, 북 몇 m</text>
<rect x="470" y="30" width="180" height="50" rx="8" fill="#fefce8" stroke="#ca8a04"/>
<text x="560" y="52" text-anchor="middle" font-size="13" font-weight="bold" fill="#854d0e">③ 높이 맞추기</text>
<text x="560" y="70" text-anchor="middle" font-size="11" fill="#854d0e">지오이드 보정</text>
<rect x="470" y="115" width="180" height="50" rx="8" fill="#fff7ed" stroke="#ea580c"/>
<text x="560" y="137" text-anchor="middle" font-size="13" font-weight="bold" fill="#9a3412">④ 드론 기준 위치</text>
<text x="560" y="155" text-anchor="middle" font-size="11" fill="#9a3412">드론에서 본 방향</text>
<rect x="250" y="115" width="180" height="50" rx="8" fill="#fdf2f8" stroke="#db2777"/>
<text x="340" y="137" text-anchor="middle" font-size="13" font-weight="bold" fill="#9d174d">⑤ 카메라 눈 기준</text>
<text x="340" y="155" text-anchor="middle" font-size="11" fill="#9d174d">자세(회전) 적용</text>
<rect x="30" y="115" width="180" height="50" rx="8" fill="#f5f3ff" stroke="#7c3aed"/>
<text x="120" y="137" text-anchor="middle" font-size="13" font-weight="bold" fill="#5b21b6">⑥ 화면 평면에 투영</text>
<text x="120" y="155" text-anchor="middle" font-size="11" fill="#5b21b6">바늘구멍 사진기</text>
<rect x="250" y="205" width="180" height="50" rx="8" fill="#fff" stroke="#b45309" stroke-width="2"/>
<text x="340" y="227" text-anchor="middle" font-size="13" font-weight="bold" fill="#23272e">⑦ 화면에 글자!</text>
<text x="340" y="245" text-anchor="middle" font-size="11" fill="#555">○○다리 ✨</text>
<!-- arrows -->
<line x1="210" y1="55" x2="246" y2="55" stroke="#b45309" stroke-width="1.5" marker-end="url(#p1a)"/>
<line x1="430" y1="55" x2="466" y2="55" stroke="#b45309" stroke-width="1.5" marker-end="url(#p1a)"/>
<line x1="560" y1="80" x2="560" y2="111" stroke="#b45309" stroke-width="1.5" marker-end="url(#p1a)"/>
<line x1="470" y1="140" x2="434" y2="140" stroke="#b45309" stroke-width="1.5" marker-end="url(#p1a)"/>
<line x1="250" y1="140" x2="214" y2="140" stroke="#b45309" stroke-width="1.5" marker-end="url(#p1a)"/>
<line x1="120" y1="165" x2="120" y2="195" stroke="#b45309" stroke-width="1.5"/>
<line x1="120" y1="195" x2="250" y2="225" stroke="#b45309" stroke-width="1.5" marker-end="url(#p1a)"/>
</svg>
<figcaption>그림 1. POI는 ①지도좌표 → ②미터 → ③높이맞춤 → ④드론기준 → ⑤카메라기준 → ⑥화면투영 → ⑦글자 순으로 변신합니다.</figcaption>
</figure>
---
## 2. 사용한 기술 한눈에 보기
| 번호 | 기술 이름 | 쉽게 말하면 | 자세히 |
|------|----------|-------------|--------|
| ① | **좌표계 변환 (proj4)** | 위도·경도(각도)를 '미터'로 바꾸기 | 3장 |
| ② | **ENU 월드 좌표** | "기준점에서 동·북·위로 몇 m" | 4장 |
| ③ | **지오이드 높이 보정** | 두 가지 높이 기준을 맞추기 | 5장 |
| ④ | **자세 회전 행렬 (yaw/pitch/roll)** | 드론이 어느 쪽을 보는지 계산 | 6장 |
| ⑤ | **상대 위치 벡터** | 드론에서 POI까지의 방향·거리 | 7장 |
| ⑥ | **핀홀 카메라 원근투영** | 3D 세상을 납작한 화면에 그리기 | 8장 |
| ⑦ | **화각(초점거리·센서)** | "얼마나 넓게 보나" 맞추기 | 9장 |
| ⑧ | **카메라 뒤 지우기(클리핑)** | 뒤에 있는 건 안 그리기 | 10장 |
| ⑨ | **POI 높이 추측** | 실제 높이 없을 때 똑똑하게 짐작 | 11장 |
| ⑩ | **거리 필터** | 너무 먼 건 숨기기 | 12장 |
| ⑪ | **역투영 보정** | 손으로 끌어 거꾸로 고치기 | 13장 |
| ⑫ | **화면 크롭 맞춤(cover)** | 영상 잘림에 글자도 맞추기 | 14장 |
이제 하나씩 그림으로 볼게요!
---
## 3. 기술 ① — 위도·경도를 '미터'로 바꾸기 (proj4)
지도 위치는 보통 **위도·경도** (예: 북위 36.3°, 동경 127.4°)로 말해요. 그런데 이건 **각도**라서
"두 곳이 몇 m 떨어졌나?" 같은 계산이 어려워요.
그래서 **"기준점에서 동쪽 몇 m, 북쪽 몇 m"** 같은 **미터(m)** 좌표로 바꿔요.
우리나라 전용 지도 격자인 **EPSG:5186 (한국 TM)** 을 써서 바꿔요. 이 변신은 `proj4`라는 도구가 해 줍니다.
> 📏 **비유**: "동경 127.4도"라고 하면 와닿지 않지만, "학교에서 동쪽으로 500m"라고 하면 바로 이해되죠.
<figure class="fig">
<svg viewBox="0 0 680 200" role="img" aria-label="위경도를 미터로">
<defs><marker id="p3a" markerWidth="10" markerHeight="10" refX="7" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#16a34a"/></marker></defs>
<!-- globe -->
<circle cx="120" cy="100" r="60" fill="#dbeafe" stroke="#2563eb"/>
<ellipse cx="120" cy="100" rx="60" ry="22" fill="none" stroke="#93c5fd"/>
<ellipse cx="120" cy="100" rx="22" ry="60" fill="none" stroke="#93c5fd"/>
<circle cx="138" cy="78" r="5" fill="#dc2626"/>
<text x="120" y="180" text-anchor="middle" font-size="12" fill="#1e3a8a">위도·경도 (각도)</text>
<text x="120" y="40" text-anchor="middle" font-size="11" fill="#b91c1c">36.3°, 127.4°</text>
<!-- arrow -->
<line x1="195" y1="100" x2="270" y2="100" stroke="#16a34a" stroke-width="2.5" marker-end="url(#p3a)"/>
<text x="232" y="90" text-anchor="middle" font-size="11" fill="#166534">proj4</text>
<text x="232" y="118" text-anchor="middle" font-size="10" fill="#166534">한국 TM</text>
<!-- grid -->
<g stroke="#e5e7eb"><line x1="320" y1="40" x2="320" y2="170"/><line x1="380" y1="40" x2="380" y2="170"/><line x1="440" y1="40" x2="440" y2="170"/><line x1="500" y1="40" x2="500" y2="170"/><line x1="560" y1="40" x2="560" y2="170"/>
<line x1="300" y1="60" x2="620" y2="60"/><line x1="300" y1="100" x2="620" y2="100"/><line x1="300" y1="140" x2="620" y2="140"/></g>
<line x1="320" y1="170" x2="620" y2="170" stroke="#6b7280" stroke-width="2"/>
<line x1="320" y1="170" x2="320" y2="40" stroke="#6b7280" stroke-width="2"/>
<text x="610" y="188" font-size="10" fill="#6b7280">동(m)→</text>
<text x="300" y="50" font-size="10" fill="#6b7280">북(m)↑</text>
<circle cx="500" cy="80" r="6" fill="#16a34a"/>
<text x="508" y="76" font-size="11" font-weight="bold" fill="#166534">동 540m, 북 320m</text>
</svg>
<figcaption>그림 2. 각도(위경도)를 우리나라 전용 미터 격자(한국 TM)로 바꿔 계산하기 쉽게 만듭니다.</figcaption>
</figure>
---
## 4. 기술 ② — "동·북·위로 몇 m" 3D 좌표 (ENU)
이제 위치를 **세 개의 숫자**로 나타내요. 기준점에서:
- **E**ast (동쪽으로 몇 m)
- **N**orth (북쪽으로 몇 m)
- **U**p (위로 몇 m = 높이)
이 세 글자를 따서 **ENU**라고 불러요. 이렇게 하면 지구 위 모든 위치를 **3D 공간의 한 점**으로 다룰 수 있어요.
> 🧊 **비유**: 교실에서 물건 위치를 "칠판에서 오른쪽 2m, 앞으로 3m, 바닥에서 1m 높이"라고 말하는 것과 같아요.
<figure class="fig">
<svg viewBox="0 0 680 220" role="img" aria-label="ENU 3D 좌표">
<defs><marker id="p4a" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#6b7280"/></marker></defs>
<!-- origin -->
<circle cx="180" cy="160" r="6" fill="#16a34a"/>
<text x="150" y="180" font-size="11" fill="#14532d">기준점 (0,0,0)</text>
<!-- E axis -->
<line x1="180" y1="160" x2="560" y2="160" stroke="#6b7280" stroke-width="2" marker-end="url(#p4a)"/>
<text x="555" y="180" font-size="12" fill="#6b7280">E 동쪽</text>
<!-- N axis (diagonal back) -->
<line x1="180" y1="160" x2="380" y2="60" stroke="#6b7280" stroke-width="2" marker-end="url(#p4a)"/>
<text x="385" y="55" font-size="12" fill="#6b7280">N 북쪽</text>
<!-- U axis -->
<line x1="180" y1="160" x2="180" y2="40" stroke="#6b7280" stroke-width="2" marker-end="url(#p4a)"/>
<text x="150" y="40" font-size="12" fill="#6b7280">U 위(높이)</text>
<!-- point -->
<line x1="440" y1="120" x2="440" y2="80" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="4 3"/>
<circle cx="440" cy="80" r="7" fill="#b45309"/>
<text x="450" y="76" font-size="12" font-weight="bold" fill="#b45309">○○다리</text>
<text x="450" y="94" font-size="11" fill="#7c2d12">(E, N, U)</text>
</svg>
<figcaption>그림 3. 모든 위치를 "동·북·위로 몇 m"인 3D 점으로 나타냅니다(ENU).</figcaption>
</figure>
---
## 5. 기술 ③ — 두 가지 '높이 기준' 맞추기 (지오이드 보정)
높이를 재는 기준이 **두 가지**라서 헷갈려요.
- **GPS 높이**: 지구를 매끈한 타원이라 보고 잰 높이 (드론이 기록하는 높이)
- **지도 해발고도**: 평균 바닷물 높이 기준 (지도에 적힌 높이) = **지오이드**
이 둘은 지역마다 **수십 m** 차이 나요. (대전은 약 **25.8m**!) 안 맞추면 글자가 위아래로 엉뚱하게 떠요.
그래서 지도 높이에 **25.8m를 더해** 드론의 높이 기준과 똑같이 맞춰 줘요.
> 🌊 **비유**: "1층 바닥 기준 높이"와 "지하주차장 바닥 기준 높이"는 같은 창문인데 숫자가 다르죠.
> 비교하려면 기준을 하나로 맞춰야 해요.
<figure class="fig">
<svg viewBox="0 0 680 200" role="img" aria-label="높이 기준 차이">
<defs><marker id="p5a" markerWidth="9" markerHeight="9" refX="3" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#dc2626"/></marker><marker id="p5b" markerWidth="9" markerHeight="9" refX="3" refY="3" orient="auto"><path d="M6,0 L0,3 L6,6 Z" fill="#dc2626"/></marker></defs>
<line x1="40" y1="55" x2="640" y2="55" stroke="#2563eb" stroke-width="2" stroke-dasharray="6 4"/>
<text x="450" y="48" font-size="12" fill="#1e3a8a">GPS 높이 기준 (드론이 기록)</text>
<line x1="40" y1="115" x2="640" y2="115" stroke="#0ea5e9" stroke-width="2" stroke-dasharray="6 4"/>
<text x="430" y="135" font-size="12" fill="#0369a1">지도 해발 기준 (지오이드)</text>
<line x1="160" y1="56" x2="160" y2="114" stroke="#dc2626" stroke-width="1.5" marker-start="url(#p5a)" marker-end="url(#p5b)"/>
<text x="170" y="92" font-size="12" font-weight="bold" fill="#dc2626">약 25.8m 차이</text>
<path d="M40,170 Q200,150 360,158 T640,150" fill="none" stroke="#8b5e34" stroke-width="3"/>
<text x="46" y="188" font-size="11" fill="#7c5a3a">실제 땅</text>
<text x="400" y="175" font-size="11" fill="#b91c1c">→ 지도 높이에 25.8m 더해 기준 통일</text>
</svg>
<figcaption>그림 4. 높이 기준이 둘이라 약 25.8m 차이. 더해서 맞춰야 글자가 제 높이에 붙습니다.</figcaption>
</figure>
---
## 6. 기술 ④ — 드론이 '어느 쪽을 보는지' (자세 회전)
드론은 가만히 있지 않아요. **좌우로 돌고(yaw), 아래로 숙이고(pitch), 옆으로 기울어요(roll).**
이 세 가지를 알아야 "카메라가 정확히 어느 방향을 보는지" 계산할 수 있어요.
이걸 **회전 행렬**이라는 수학 도구로 한 번에 계산해요. (세 방향 회전을 합치는 마법 표라고 생각하면 돼요.)
<figure class="fig">
<svg viewBox="0 0 680 200" role="img" aria-label="yaw pitch roll">
<!-- yaw -->
<g><circle cx="120" cy="100" r="45" fill="none" stroke="#2563eb" stroke-width="2"/>
<path d="M120,55 A45,45 0 0 1 158,80" fill="none" stroke="#2563eb" stroke-width="3" marker-end="url(#p6a)"/>
<defs><marker id="p6a" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 Z" fill="#2563eb"/></marker></defs>
<text x="120" y="105" text-anchor="middle" font-size="13" font-weight="bold" fill="#1e3a8a">yaw</text>
<text x="120" y="165" text-anchor="middle" font-size="11" fill="#1e3a8a">좌우로 돌기</text></g>
<!-- pitch -->
<g><ellipse cx="340" cy="100" rx="45" ry="20" fill="none" stroke="#16a34a" stroke-width="2"/>
<path d="M340,80 A20,20 0 0 1 360,100" fill="none" stroke="#16a34a" stroke-width="3"/>
<text x="340" y="105" text-anchor="middle" font-size="13" font-weight="bold" fill="#166534">pitch</text>
<text x="340" y="165" text-anchor="middle" font-size="11" fill="#166534">위·아래 숙이기</text></g>
<!-- roll -->
<g><line x1="510" y1="100" x2="610" y2="100" stroke="#d1d5db" stroke-width="2"/>
<line x1="520" y1="115" x2="600" y2="85" stroke="#ea580c" stroke-width="3"/>
<text x="560" y="135" text-anchor="middle" font-size="13" font-weight="bold" fill="#9a3412">roll</text>
<text x="560" y="165" text-anchor="middle" font-size="11" fill="#9a3412">옆으로 기울기</text></g>
</svg>
<figcaption>그림 5. 드론의 세 가지 자세(yaw·pitch·roll)를 합쳐, 카메라가 보는 방향을 정확히 계산합니다.</figcaption>
</figure>
---
## 7. 기술 ⑤ — 드론에서 POI까지 '상대 위치'
이제 **POI의 위치 − 드론의 위치**를 빼서, **"드론에서 봤을 때 POI가 어느 쪽에, 얼마나 멀리"** 있는지 구해요.
그리고 ④에서 구한 드론의 보는 방향을 적용하면, **"카메라 눈 기준으로" POI가 어디 있는지** 나와요.
> 👉 **비유**: 친구가 어디 서서 어느 쪽을 보는지 알면, "친구 눈에는 저 건물이 왼쪽 앞에 보이겠네" 하고 알 수 있죠.
<figure class="fig">
<svg viewBox="0 0 680 200" role="img" aria-label="상대 위치">
<defs><marker id="p7a" 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>
<!-- view cone -->
<path d="M130,120 L470,55 L470,165 Z" fill="#ede9fe" opacity="0.6"/>
<circle cx="130" cy="120" r="9" fill="#7c3aed"/>
<text x="100" y="148" font-size="12" font-weight="bold" fill="#5b21b6">드론</text>
<rect x="500" y="75" width="55" height="60" fill="#fde68a" stroke="#b45309"/>
<text x="527" y="152" text-anchor="middle" font-size="11" fill="#7c2d12">○○다리</text>
<line x1="140" y1="118" x2="496" y2="100" stroke="#7c3aed" stroke-width="2" stroke-dasharray="5 4" marker-end="url(#p7a)"/>
<text x="250" y="95" font-size="12" fill="#5b21b6">"드론 눈 기준으로 오른쪽 앞, 340m"</text>
</svg>
<figcaption>그림 6. POI 위치에서 드론 위치를 빼고 드론의 보는 방향을 적용해, 카메라 눈 기준 위치를 구합니다.</figcaption>
</figure>
---
## 8. 기술 ⑥ — 3D를 납작한 화면에 그리기 (핀홀 카메라 원근투영)
이제 핵심! **입체(3D)를 납작한 화면(2D)에** 그려요. 규칙은 누구나 알아요: **가까운 건 크게, 먼 건 작게.**
이게 바로 **원근법**이고, 카메라는 **바늘구멍 사진기**처럼 작동해요.
계산은 의외로 간단해요. "옆으로 간 거리 ÷ 앞으로 간 거리" 를 하면 화면의 좌우 위치가 나와요. (위아래도 똑같이!)
멀수록(앞으로 간 거리가 클수록) 나누는 값이 커져서 → 화면 가운데로 작게 모여요.
> 📷 **비유**: 기찻길이 멀어질수록 한 점으로 모이는 것 — 그게 바로 이 나눗셈의 결과예요.
<figure class="fig">
<svg viewBox="0 0 680 230" role="img" aria-label="핀홀 투영">
<line x1="40" y1="195" x2="650" y2="195" stroke="#9ca3af" stroke-width="1.5"/>
<circle cx="70" cy="150" r="7" fill="#111"/>
<text x="42" y="172" font-size="11" fill="#333">카메라(눈)</text>
<line x1="210" y1="55" x2="210" y2="195" stroke="#2563eb" stroke-width="2"/>
<text x="175" y="48" font-size="11" fill="#1e3a8a">화면</text>
<!-- near pole -->
<line x1="360" y1="65" x2="360" y2="195" stroke="#16a34a" stroke-width="4"/>
<text x="330" y="213" font-size="11" fill="#14532d">가까운 다리</text>
<line x1="560" y1="65" x2="560" y2="195" stroke="#16a34a" stroke-width="4"/>
<text x="530" y="213" font-size="11" fill="#14532d">먼 다리</text>
<text x="350" y="53" font-size="10" fill="#777">(실제 크기는 같음)</text>
<line x1="70" y1="150" x2="360" y2="65" stroke="#f59e0b" stroke-width="1" stroke-dasharray="3 3"/>
<line x1="70" y1="150" x2="360" y2="195" stroke="#f59e0b" stroke-width="1" stroke-dasharray="3 3"/>
<line x1="70" y1="150" x2="560" y2="65" stroke="#b45309" stroke-width="1" stroke-dasharray="3 3"/>
<line x1="70" y1="150" x2="560" y2="195" stroke="#b45309" stroke-width="1" stroke-dasharray="3 3"/>
<line x1="210" y1="108" x2="210" y2="173" stroke="#16a34a" stroke-width="6"/>
<line x1="218" y1="125" x2="218" y2="167" stroke="#15803d" stroke-width="6" opacity="0.8"/>
<text x="228" y="120" font-size="10" fill="#166534">화면엔 가까운 게 크게</text>
</svg>
<figcaption>그림 7. 바늘구멍 사진기처럼, "옆거리 ÷ 앞거리"로 화면 위치를 구하면 자동으로 원근법이 됩니다.</figcaption>
</figure>
---
## 9. 기술 ⑦ — '얼마나 넓게 보나' 화각 맞추기 (초점거리·센서)
같은 자리에서도 **광각 렌즈**는 넓게, **줌 렌즈**는 좁게 보여요. 이 "얼마나 넓게 보나"가 **화각**이에요.
화각은 **초점거리(focal, 기본 24mm)****센서 크기(36mm, 16:9)** 로 정해져요.
화각이 안 맞으면 글자가 위아래·좌우로 어긋나요. 그래서 8장의 나눗셈에 이 값을 곱해서 정확히 맞춰 줘요.
> 🔍 **비유**: 같은 창밖 풍경도 망원경(줌)으로 보면 좁고 크게, 그냥 보면 넓고 작게 보이죠.
<figure class="fig">
<svg viewBox="0 0 680 190" role="img" aria-label="화각">
<circle cx="80" cy="95" r="7" fill="#111"/>
<text x="55" y="117" font-size="11" fill="#333">카메라</text>
<path d="M80,95 L620,20 L620,170 Z" fill="#fef3c7" opacity="0.7" stroke="#f59e0b"/>
<text x="480" y="38" font-size="13" font-weight="bold" fill="#b45309">광각: 넓게 봄</text>
<path d="M80,95 L620,75 L620,115 Z" fill="#bfdbfe" opacity="0.85" stroke="#2563eb"/>
<text x="480" y="145" font-size="13" font-weight="bold" fill="#1e3a8a">줌: 좁게 봄</text>
</svg>
<figcaption>그림 8. 초점거리·센서로 정해지는 화각(FOV)을 맞춰야 글자가 정확한 자리에 붙습니다.</figcaption>
</figure>
---
## 10. 기술 ⑧ — 카메라 '뒤'에 있는 건 안 그리기 (클리핑)
POI가 드론 **뒤쪽**이나 **너무 가까이**에 있으면, 계산이 이상해져서 글자가 화면 반대편으로 **휙 튀어요.**
그래서 "앞으로 간 거리(Zc)가 너무 작거나 마이너스면 = 카메라 뒤/너무 가까움" → **그냥 안 그려요.**
> 🙈 **비유**: 내 뒤통수 쪽 물건은 내 눈에 안 보이죠. 안 보이는 건 화면에도 안 그리는 게 맞아요.
<figure class="fig">
<svg viewBox="0 0 680 180" role="img" aria-label="클리핑">
<circle cx="340" cy="90" r="9" fill="#111"/>
<text x="320" y="115" font-size="11" fill="#333">카메라</text>
<path d="M340,90 L640,30 L640,150 Z" fill="#dcfce7" opacity="0.6" stroke="#16a34a"/>
<text x="560" y="90" font-size="12" fill="#166534">앞 = 그림 ✓</text>
<circle cx="560" cy="80" r="6" fill="#16a34a"/>
<!-- behind -->
<path d="M340,90 L40,30 L40,150 Z" fill="#fee2e2" opacity="0.6" stroke="#dc2626"/>
<text x="120" y="90" font-size="12" fill="#b91c1c">뒤 = 안 그림 ✗</text>
<circle cx="130" cy="80" r="6" fill="#dc2626"/>
<line x1="120" y1="70" x2="140" y2="90" stroke="#b91c1c" stroke-width="2"/><line x1="140" y1="70" x2="120" y2="90" stroke="#b91c1c" stroke-width="2"/>
</svg>
<figcaption>그림 9. 카메라 뒤·너무 가까운 POI는 글자가 튀므로 아예 그리지 않습니다(클리핑).</figcaption>
</figure>
---
## 11. 기술 ⑨ — POI 높이를 똑똑하게 '추측'하기
문제가 하나 있어요. 다리·역의 **위도·경도는 정확히 아는데, 정확한 높이는 모를 때**가 많아요.
비싼 3D 지형 데이터(DEM)를 사면 알 수 있지만, 안 사고도 똑똑하게 짐작해요. 두 가지 방법:
1. **가장 가까운 선로(중심선)의 높이**를 빌려 써요. → 선로 굴곡(오르막/내리막)까지 자연스럽게 반영! 👍
2. 또는 **"드론 높이 일정값(약 24m)"** 으로 가정해요.
그리고 사람이 직접 고친 높이가 있으면(드래그/DEM) 그걸 **가장 먼저** 써요.
> 🏔️ **비유**: 친구 키를 모를 때, 바로 옆에 선 비슷한 친구 키를 참고해 짐작하는 것과 같아요.
<figure class="fig">
<svg viewBox="0 0 680 190" role="img" aria-label="POI 높이 추측">
<!-- rail line with slope -->
<polyline points="40,150 200,140 360,120 520,135 640,125" fill="none" stroke="#06a4c8" stroke-width="4"/>
<text x="46" y="172" font-size="11" fill="#0369a1">가까운 선로(높이 알고 있음)</text>
<!-- POI -->
<circle cx="360" cy="120" r="7" fill="#b45309"/>
<line x1="360" y1="120" x2="360" y2="70" stroke="#f59e0b" stroke-width="2" stroke-dasharray="4 3"/>
<rect x="335" y="45" width="50" height="25" rx="4" fill="#fde68a" stroke="#b45309"/>
<text x="360" y="62" text-anchor="middle" font-size="11" fill="#7c2d12">○○다리</text>
<text x="375" y="115" font-size="11" font-weight="bold" fill="#b45309">↑ 옆 선로 높이를 빌려 씀</text>
</svg>
<figcaption>그림 10. POI 높이를 모를 때, 가장 가까운 선로 높이를 빌려 자연스럽게 맞춥니다.</figcaption>
</figure>
---
## 12. 기술 ⑩ — 너무 먼 건 숨기기 (거리 필터)
화면에 모든 POI를 다 그리면 너무 복잡해요. 그래서 드론과의 **수평 직선거리**가
정해진 범위(기본 **1000m**)보다 멀면 **숨겨요.** 가까운 것만 보여 주는 거죠.
또 "앞에 있나 / 옆에 있나"도 따로 구분해서 더 똑똑하게 걸러낼 수 있어요.
> 🔭 **비유**: 지도 앱에서 너무 멀리 있는 가게 이름은 안 보이다가, 가까이 가면 나타나는 것과 같아요.
<figure class="fig">
<svg viewBox="0 0 680 190" role="img" aria-label="거리 필터">
<circle cx="120" cy="95" r="9" fill="#7c3aed"/>
<text x="95" y="120" font-size="11" font-weight="bold" fill="#5b21b6">드론</text>
<circle cx="120" cy="95" r="130" fill="#7c3aed" opacity="0.07" stroke="#7c3aed" stroke-dasharray="6 4"/>
<text x="120" y="220" text-anchor="middle" font-size="11" fill="#5b21b6"></text>
<text x="200" y="40" font-size="11" fill="#5b21b6">범위(1000m) 안</text>
<!-- inside -->
<circle cx="180" cy="120" r="6" fill="#16a34a"/><text x="190" y="124" font-size="11" fill="#166534">○○다리 ✓</text>
<circle cx="210" cy="60" r="6" fill="#16a34a"/><text x="220" y="64" font-size="11" fill="#166534">△△역 ✓</text>
<!-- outside -->
<circle cx="560" cy="120" r="6" fill="#9ca3af"/><text x="500" y="140" font-size="11" fill="#9ca3af">먼 터널 ✗ (숨김)</text>
</svg>
<figcaption>그림 11. 드론에서 너무 먼 POI는 숨기고, 범위 안의 것만 화면에 보여 깔끔하게 합니다.</figcaption>
</figure>
---
## 13. 기술 ⑪ — 손으로 끌어 거꾸로 고치기 (역투영)
지도 위치가 가끔 살짝 틀려요. 그러면 화면에서 글자를 **제자리로 쓱 끌면**,
컴퓨터가 그 화면 위치를 **거꾸로 따라가** 실제 위치(또는 높이)를 다시 계산해 고쳐요. (8장을 반대로!)
고친 값은 **저장**돼서 다음에도, 다른 장면에서도 계속 맞아요.
이렇게 거꾸로 푸는 방법이 여러 개 있어요:
- **화면점 → 실제 위치** 복원 (위·아래 동시 보정)
- **높이만** 다시 풀기 (앞으로 밀려 보일 때)
- **광선을 땅과 만나게** 해서 좌우 위치 고치기
- 글자를 끌면 **화각(초점거리)을 스스로 다시 맞추기**
> 🎯 **비유**: 다트가 빗나가면, 맞은 자리를 보고 "팔을 이만큼 틀어야겠다" 하고 거꾸로 교정하는 것과 같아요.
<figure class="fig">
<svg viewBox="0 0 680 200" role="img" aria-label="역투영 보정">
<defs><marker id="p13a" 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="40" y="35" width="220" height="130" rx="6" fill="#0b1020" stroke="#444"/>
<text x="150" y="28" text-anchor="middle" font-size="11" fill="#333">영상 화면</text>
<circle cx="170" cy="80" r="6" fill="#f59e0b"/>
<text x="95" y="105" font-size="11" fill="#fde68a">여기로 끌었다</text>
<line x1="300" y1="175" x2="650" y2="175" stroke="#8b5e34" stroke-width="3"/>
<text x="300" y="195" font-size="11" fill="#7c5a3a">실제 땅</text>
<line x1="176" y1="82" x2="560" y2="170" stroke="#7c3aed" stroke-width="2" stroke-dasharray="5 4" marker-end="url(#p13a)"/>
<circle cx="560" cy="170" r="6" fill="#7c3aed"/>
<text x="485" y="160" font-size="12" font-weight="bold" fill="#6d28d9">진짜 위치!</text>
<text x="270" y="60" font-size="11" fill="#6d28d9">화면의 점 → 거꾸로 따라가 → 실제 위치 계산·저장</text>
</svg>
<figcaption>그림 12. 글자를 끌면 그 화면점을 거꾸로 따라가 실제 위치·높이·화각을 자동 보정하고 저장합니다.</figcaption>
</figure>
---
## 14. 기술 ⑫ — 영상 잘림에 글자도 맞추기 (cover 변환)
영상은 화면을 **비율 유지하며 꽉 채우다 보니 가장자리가 살짝 잘려요**(CSS `object-fit: cover`).
그래서 글자도 **똑같이 잘린 영상에 맞춰** 위치를 옮겨야 정확히 붙어요.
8장에서 구한 "0~1 사이의 화면 비율 위치"를, 실제 화면 픽셀로 바꿀 때 이 잘림을 똑같이 반영해 줘요.
> 🖼️ **비유**: 액자(화면)에 사진(영상)을 꽉 채우면 사진 가장자리가 조금 잘리죠.
> 사진 위에 붙일 스티커(글자)도 그 잘린 만큼 같이 옮겨 줘야 제자리예요.
<figure class="fig">
<svg viewBox="0 0 680 190" role="img" aria-label="cover 변환">
<!-- container -->
<rect x="220" y="30" width="240" height="130" fill="#0b1020" stroke="#23272e" stroke-width="2"/>
<text x="340" y="22" text-anchor="middle" font-size="11" fill="#333">화면(액자)</text>
<!-- video larger, cropped -->
<rect x="180" y="40" width="320" height="110" fill="#1e293b" opacity="0.5" stroke="#64748b" stroke-dasharray="5 4"/>
<text x="340" y="100" text-anchor="middle" font-size="11" fill="#94a3b8">영상(좌우 살짝 잘림)</text>
<!-- label -->
<rect x="300" y="70" width="80" height="24" rx="4" fill="#fde68a" stroke="#b45309"/>
<text x="340" y="87" text-anchor="middle" font-size="11" fill="#7c2d12">○○다리</text>
<text x="510" y="95" font-size="11" fill="#555">글자도 잘린 만큼</text>
<text x="510" y="113" font-size="11" fill="#555">같이 맞춰 이동</text>
</svg>
<figcaption>그림 13. 영상이 꽉 차며 잘리는 만큼 글자 위치도 똑같이 보정해 정확히 정합시킵니다.</figcaption>
</figure>
---
## 15. 전체를 한 줄로 다시 정리
<figure class="fig">
<svg viewBox="0 0 680 120" role="img" aria-label="전체 요약">
<defs><marker id="p15a" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#b45309"/></marker></defs>
<g font-size="11" text-anchor="middle">
<rect x="10" y="45" width="80" height="34" rx="6" fill="#ecfdf5" stroke="#16a34a"/><text x="50" y="66" fill="#166534">위경도</text>
<rect x="110" y="45" width="80" height="34" rx="6" fill="#eff6ff" stroke="#2563eb"/><text x="150" y="66" fill="#1e3a8a">미터·ENU</text>
<rect x="210" y="45" width="80" height="34" rx="6" fill="#fefce8" stroke="#ca8a04"/><text x="250" y="66" fill="#854d0e">높이맞춤</text>
<rect x="310" y="45" width="80" height="34" rx="6" fill="#fff7ed" stroke="#ea580c"/><text x="350" y="60" fill="#9a3412">드론자세</text><text x="350" y="73" fill="#9a3412">+상대위치</text>
<rect x="410" y="45" width="80" height="34" rx="6" fill="#f5f3ff" stroke="#7c3aed"/><text x="450" y="60" fill="#5b21b6">원근투영</text><text x="450" y="73" fill="#5b21b6">+화각</text>
<rect x="510" y="45" width="70" height="34" rx="6" fill="#fdf2f8" stroke="#db2777"/><text x="545" y="60" fill="#9d174d">거리·겹침</text><text x="545" y="73" fill="#9d174d">필터</text>
<rect x="600" y="45" width="70" height="34" rx="6" fill="#fff" stroke="#b45309" stroke-width="2"/><text x="635" y="66" fill="#23272e">화면 글자✨</text>
</g>
<line x1="90" y1="62" x2="108" y2="62" stroke="#b45309" stroke-width="1.3" marker-end="url(#p15a)"/>
<line x1="190" y1="62" x2="208" y2="62" stroke="#b45309" stroke-width="1.3" marker-end="url(#p15a)"/>
<line x1="290" y1="62" x2="308" y2="62" stroke="#b45309" stroke-width="1.3" marker-end="url(#p15a)"/>
<line x1="390" y1="62" x2="408" y2="62" stroke="#b45309" stroke-width="1.3" marker-end="url(#p15a)"/>
<line x1="490" y1="62" x2="508" y2="62" stroke="#b45309" stroke-width="1.3" marker-end="url(#p15a)"/>
<line x1="580" y1="62" x2="598" y2="62" stroke="#b45309" stroke-width="1.3" marker-end="url(#p15a)"/>
</svg>
<figcaption>그림 14. 위경도 → 미터/ENU → 높이맞춤 → 드론자세·상대위치 → 원근투영·화각 → 거리/겹침 필터 → 화면 글자!</figcaption>
</figure>
---
## 16. 왜 이게 대단할까? (특허감 포인트)
- **측량 선로 높이로, 비싼 3D 지형 없이도 정합** (11장) + 두 높이 기준 자동 맞춤(5장).
- **한 번 끌어서 위치·높이·화각을 동시에 거꾸로 보정** (13장) — 보통은 깊이 측정 장비가 필요한 일을 장비 없이.
- **드론 자세·화각·지오이드·원근법을 모두 합친 정확한 투영** (6~9장) — 영상 위에 글자를 픽셀 단위로 정합.
> ⚠️ 단, 특허는 **비슷한 게 이미 있는지(선행기술)** 전문가가 꼭 찾아봐야 확정돼요.
---
## 17. 쉬운 용어 사전
| 어려운 말 | 쉬운 뜻 |
|----------|---------|
| **POI** | 다리·터널·역처럼 지도에 표시된 '관심 지점'(Point Of Interest) |
| **좌표계 변환 / proj4** | 위도·경도(각도)를 미터 격자로 바꾸는 것 (한국 TM = EPSG:5186) |
| **ENU** | 기준점에서 동(E)·북(N)·위(U)로 몇 m인지 나타내는 3D 좌표 |
| **지오이드** | '해발고도'의 기준(평균 바닷물 높이). GPS 높이와 달라 보정 필요 |
| **yaw / pitch / roll** | 좌우로 돈 / 위아래 숙인 / 옆으로 기운 정도 (드론 자세) |
| **회전 행렬** | 세 가지 회전을 한 번에 계산하는 수학 도구 |
| **투영(projection)** | 입체(3D)를 납작한 화면(2D)에 그리는 것 (원근법) |
| **핀홀 카메라** | 바늘구멍 사진기 — 가까운 건 크게, 먼 건 작게 |
| **화각(FOV) / 초점거리** | 카메라가 얼마나 넓게 보는지 (광각=넓게, 줌=좁게) |
| **클리핑** | 카메라 뒤/너무 가까운 것을 안 그리고 잘라내는 것 |
| **DEM** | 땅의 높낮이를 담은 3D 지형 데이터 (보통 비쌈) |
| **역투영** | 투영의 반대 — 화면의 한 점이 실제 어디인지 거꾸로 찾는 것 |
| **object-fit: cover** | 영상을 비율 유지하며 화면을 꽉 채우는 방식(가장자리 잘림) |
---
## 18. 마치며
> 지도에 적힌 **"○○다리는 여기"** 라는 위치 하나가 화면에 붙기까지,
> **위경도→미터 변환 → 높이 맞추기 → 드론 자세·상대위치 → 원근법 투영 → 거리·겹침 정리** 라는
> 여러 기술이 차례로 일을 해요.
복잡해 보여도, 사실은 **"학교에서 동쪽 몇 m, 친구가 보는 방향, 기찻길이 멀어지면 작아지는 원근법"** 같은
**일상의 생각들**을 컴퓨터로 정밀하게 이어 붙인 것뿐이랍니다. 🙂