#!/usr/bin/env python3
# fig_POI투영_상세.py — POI 좌표를 영상 속 화면 위치로 변환(투영)하는 과정
#   소스 3종(비행로그·POI·측점)이 투영 패널로 모여 단계(①②③④)를 거쳐
#   화면 위치가 산출되는 흐름 + 핀홀 투영 미니 일러스트.
# 실행: python3 scripts/fig_POI투영_상세.py → docs/그림_POI투영_상세.png / .pdf
import math, os
from PIL import Image, ImageDraw, ImageFont

OUT_DIR = os.path.join(os.path.dirname(__file__), '..', 'docs')
FB = os.path.expanduser('~/.local/share/fonts/malgunbd.ttf')
FR = os.path.expanduser('~/.local/share/fonts/malgun.ttf')

W, H = 2200, 1520
im = Image.new('RGB', (W, H), 'white')
d = ImageDraw.Draw(im)

GRAY, BLUE, GREEN, AMBER, TEAL = (89, 89, 89), (46, 117, 182), (112, 173, 71), (191, 144, 0), (49, 132, 155)
GREEN_BG, PURPLE, NOTE = (240, 247, 234), (124, 58, 237), (102, 102, 102)
ORANGE, RED = (234, 88, 12), (220, 38, 38)

def fit(text, path, size, maxw):
    while size > 18:
        f = ImageFont.truetype(path, size)
        if d.textlength(text, font=f) <= maxw:
            return f
        size -= 2
    return ImageFont.truetype(path, size)

def box(x1, y1, x2, y2, color, main, sub, subcolor):
    d.rectangle((x1, y1, x2, y2), fill=color)
    cx, cy, maxw = (x1 + x2) // 2, (y1 + y2) // 2, (x2 - x1) - 40
    d.text((cx, cy - 24), main, font=fit(main, FB, 40, maxw), fill='white', anchor='mm')
    d.text((cx, cy + 32), sub, font=fit(sub, FR, 28, maxw), fill=subcolor, anchor='mm')

def arrow(x1, y1, x2, y2, w=7):
    d.line((x1, y1, x2, y2), fill=PURPLE, width=w)
    ang = math.atan2(y2 - y1, x2 - x1)
    L, Wd = 32, 19
    d.polygon([(x2, y2),
               (x2 - L*math.cos(ang) + Wd*math.sin(ang), y2 - L*math.sin(ang) - Wd*math.cos(ang)),
               (x2 - L*math.cos(ang) - Wd*math.sin(ang), y2 - L*math.sin(ang) + Wd*math.cos(ang))],
              fill=PURPLE)

def dash(x1, y1, x2, y2, color, w=5, seg=14, gap=10):
    length = math.hypot(x2 - x1, y2 - y1)
    ux, uy = (x2 - x1) / length, (y2 - y1) / length
    t = 0
    while t < length:
        e = min(t + seg, length)
        d.line((x1 + ux*t, y1 + uy*t, x1 + ux*e, y1 + uy*e), fill=color, width=w)
        t = e + gap

note = ImageFont.truetype(FR, 26)
lab = ImageFont.truetype(FB, 28)

# ── 소스 3종 (드론 기록 vs 지도·측량 성과) ──
d.text((470, 48), '드론이 촬영하며 기록', font=note, fill=NOTE, anchor='mm')
box(160, 78, 780, 230, GRAY, '드론 비행로그 (csv)', '프레임별 위치 · 자세각 · 초점거리', (232, 232, 232))
d.text((1450, 48), '드론과 무관한 지도·측량 성과 (별도 입력)', font=note, fill=NOTE, anchor='mm')
box(880, 78, 1460, 230, BLUE, 'POI·구조물 (kmz)', '명칭 · 위경도 (높이 정보 없음)', (219, 233, 247))
box(1540, 78, 2060, 230, TEAL, '측점 (csv)', '실측 표고 — POI 높이 보완용', (214, 235, 240))

# ── 투영 패널 ──
d.rectangle((160, 440, 2060, 1130), fill=GREEN_BG, outline=GREEN, width=3)
d.rectangle((160, 360, 2060, 440), fill=GREEN)
head = '투영 — 3차원 위치를 그 순간 카메라가 보는 화면의 픽셀로 변환'
d.text((1110, 400), head, font=fit(head, FB, 36, 1800), fill='white', anchor='mm')

arrow(470, 230, 610, 352)
d.text((430, 300), '위치·자세각·초점거리', font=note, fill=NOTE, anchor='mm')
arrow(1170, 230, 1130, 352)
d.text((1280, 295), 'POI 위경도', font=note, fill=NOTE, anchor='mm')
arrow(1800, 230, 1680, 352)
d.text((1880, 300), '실측 표고', font=note, fill=NOTE, anchor='mm')

# 단계 4개 (패널 좌측)
steps = [
    ('① POI 높이 보완 — 최근접 측점 실측 표고 사용', 'POI엔 높이가 없음 → 외부 지형데이터(DEM) 불필요'),
    ('② 좌표 변환 — 드론 기준 상대위치(m)로', '예: 드론에서 앞 120m · 옆 15m · 아래 30m'),
    ('③ 시선 회전 — 자세각(yaw·pitch·roll) 적용', "'카메라가 보는 방향' 기준 좌표로 회전"),
    ('④ 핀홀 투영 — 화면 위치 산출', '화면 위치 = 중앙 + (옆÷앞) × (초점거리÷센서)'),
]
sy = 480
for main, sub in steps:
    d.rectangle((200, sy, 1180, sy + 130), fill='white', outline=GREEN, width=3)
    d.text((690, sy + 44), main, font=fit(main, FB, 32, 940), fill=(47, 91, 24), anchor='mm')
    d.text((690, sy + 94), sub, font=fit(sub, FR, 25, 940), fill=NOTE, anchor='mm')
    sy += 160

# 미니 일러스트 (패널 우측) — 카메라 + 화면 + POI + 광선
cam = (1330, 830)
d.rectangle((cam[0]-55, cam[1]-42, cam[0]+55, cam[1]+42), fill=(30, 30, 30))
d.rectangle((cam[0]+55, cam[1]-20, cam[0]+80, cam[1]+20), fill=(30, 30, 30))
d.text((cam[0], cam[1]+78), '드론 카메라', font=lab, fill=(30, 30, 30), anchor='mm')

scr_x = 1560
d.rectangle((scr_x-8, 620, scr_x+8, 1000), fill=(180, 190, 200))
d.text((scr_x, 1035), '화면(영상 프레임)', font=note, fill=(71, 85, 105), anchor='mm')

poi = (1965, 545)
dash(cam[0]+80, cam[1]-6, poi[0], poi[1], ORANGE, w=6)
d.ellipse((poi[0]-13, poi[1]-13, poi[0]+13, poi[1]+13), fill=RED)
d.text((1810, 505), '교량 POI (위경도+표고)', font=lab, fill=RED, anchor='mm')

# 광선과 화면의 교차점(맺힌 점)
t = (scr_x - (cam[0]+80)) / (poi[0] - (cam[0]+80))
hit = (scr_x, (cam[1]-6) + t * (poi[1] - (cam[1]-6)))
d.ellipse((hit[0]-11, hit[1]-11, hit[0]+11, hit[1]+11), fill=ORANGE)
d.text((hit[0]+30, hit[1]+40), '맺힌 점 = 화면 속 위치', font=lab, fill=ORANGE, anchor='lm')

cap = "3차원 위치가 렌즈를 지나 화면에 '맺히는' 자리를 계산 — 원근법 그대로"
d.text((1620, 1095), cap, font=fit(cap, FR, 26, 800), fill=NOTE, anchor='mm')

# ── 하단: 결과 ──
box(660, 1210, 1560, 1370, AMBER, '영상 속 정확한 자리에 POI 표출!', '매 프레임 재계산(60fps) → 드론이 움직여도 이름표가 따라붙음', (247, 236, 208))
arrow(1110, 1130, 1110, 1202)
foot = '※ 위치가 어긋나면 라벨을 마우스로 끌어 한 번에 보정 (역투영 · 자동 저장)'
d.text((1110, 1440), foot, font=fit(foot, FR, 28, 1600), fill=NOTE, anchor='mm')

png = os.path.join(OUT_DIR, '그림_POI투영_상세.png')
im.save(png)
print('PNG 저장:', png, im.size)

import fitz
doc = fitz.open()
rect = fitz.Rect(0, 0, W / 2, H / 2)
page = doc.new_page(width=rect.width, height=rect.height)
page.insert_image(rect, filename=png)
pdf = os.path.join(OUT_DIR, '그림_POI투영_상세.pdf')
doc.save(pdf, deflate=True, garbage=4)
print('PDF 저장:', pdf)
