Files
Aislo/resources/tester/test_586_tm_frame.py
T
eomsangdonandClaude Opus 5.5 d2412297c7 feat(D03): 노선분석 GL 바탕 시험 — MapLibre 바탕 겹 여섯 · 위 캔버스 그대로 · 처음 = 끔 (PLAN 586-1 · 요구 445 B)
- 겹 단추줄 끝 [GL 바탕 시험] 켬끔(aislo:pref:d03.gl · 처음 = 지금 캔버스)
- 켬 = MapLibre(설치본 5.24 · BSD-3)가 위성 조각 · 급경사 그림 · 지적 · 등고선(주곡선 · 계곡선) · 도로 · 세류를 그림
  · 받는 GeoJSON 그대로 · 등고 글 · 그 밖 겹 · 그은 선 · 재기 · 누름 = 캔버스 그대로
- 카메라 = D03 화면(가운데 · 확대 · 돌림)을 따라감 · 같은 화면에 바로 그림 · 캔버스 겹은 MapLibre 투영으로
- 위성 조각 대리 GET …/plan/tile/{z}/{x}/{y}(브이월드 Satellite z6~19 · 키 안 샘)
- 잼(headless 실 GPU · 검증_sub2_D · 겹 여섯): 줌아웃 화면 간격 중앙 20.9 → 4.2 ms · p90 29.2 → 12.5 · 끊김 5 → 0/30
  · 끌기 20.8 → 4.2 ms · 끊김 2 → 0/40 · 그은 선 대 GL 바탕 0.02 px(돌림 30° 같음) · 재기 차 0 · 긋기 점 두 판 차 0.00001 m
- 줌인 = 위성 z19 조각으로 선명 · 급경사 그림 매끈 · 선 자리 두 판 같음
- 시험: test_586_tm_frame 조각 대리 범위 밖 400 · test_582 겹 수 낡은 셈(12 → 584 뒤 17) 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:10:37 +09:00

137 lines
5.0 KiB
Python

# -*- coding: utf-8 -*-
"""586-0 TM ↔ 경위도 거울 · 화면 TM 틀 — 그은 선 · 급경사 그림을 위성 · 도엽 겹과 같은 자리에.
- `D03_PlanAnalysis_Tm.ts` 의 TM ↔ 경위도 = pyproj(`common_util_crs`) 와 1 mm 안(5185~5188 · 5179)
- 모르는 좌표계(5174 Bessel) = null(지금 꼴로 그림)
- 화면 TM 틀(가운데 1차 맞춤) = 정식 변환 자리와 화면 안 0.5 px 안 · 되돌림 = 1 mm 안
- 옛 「같은 직사각형」 꼴은 D 프로젝트 귀퉁이에서 수십 m 어긋남(고친 까닭)
- 586-1 위성 조각 대리 = 범위 밖 400
Node 헬퍼(`helper_586_tm_frame.cjs`)가 진짜 TS 를 돌린다.
"""
import json
import math
import os
import subprocess
import pytest
from common_util.common_util_crs import lonlat_to_tm, tm_to_lonlat
HERE = os.path.dirname(os.path.abspath(__file__))
# 검증_sub2_D 위성 상자(EPSG:5186 · vworld-meta)
META = {
"x_min": 282572.566652281,
"x_max": 288034.8885857916,
"y_min": 408543.914277056,
"y_max": 411930.1592677671,
"lon_min": 127.9193115234375,
"lon_max": 127.979736328125,
"lat_min": 36.270850207239036,
"lat_max": 36.30184530368433,
}
POINTS = [
(5186, 285303.7, 410237.0),
(5186, 282572.6, 411930.2),
(5186, 288034.9, 408543.9),
(5185, 180000.0, 450000.0),
(5187, 210000.0, 350000.0),
(5188, 190000.0, 520000.0),
(5179, 960000.0, 1800000.0),
]
LONLAT = [(5186, 127.95, 36.28), (5187, 129.1, 35.5), (5179, 127.0, 37.5), (5185, 126.3, 34.8)]
def _view(w, h, scale, ox, oy):
"""지금 캔버스 꼴 — 상자 비율 그대로 화면에 맞추고 배율 · 옮김."""
ratio = (META["x_max"] - META["x_min"]) / (META["y_max"] - META["y_min"])
mw, mh = (w, w / ratio) if ratio > w / h else (h * ratio, h)
x0 = (w - mw) / 2 * scale + w / 2 * (1 - scale) + ox
y0 = (h - mh) / 2 * scale + h / 2 * (1 - scale) + oy
return [w, h, x0, y0, mw * scale, mh * scale]
@pytest.fixture(scope="module")
def r():
box = [META["lon_min"], META["lat_min"], META["lon_max"], META["lat_max"]]
corners = [
(META["x_min"], META["y_max"]),
(META["x_max"], META["y_max"]),
(META["x_min"], META["y_min"]),
(META["x_max"], META["y_min"]),
(285303.7, 410237.0),
]
frames = [
{"epsg": 5186, "box": box, "view": _view(1400, 900, 1, 0, 0), "probes": corners},
{
"epsg": 5186,
"box": box,
"view": _view(1400, 900, 40, -3000, 2000),
"probes": [(285303.7 + dx, 410237.0 + dy) for dx in (-40, 0, 40) for dy in (-25, 25)],
},
]
payload = {"points": POINTS, "lonlat": LONLAT, "frames": frames}
proc = subprocess.run(
["node", os.path.join(HERE, "helper_586_tm_frame.cjs")],
input=json.dumps(payload),
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_TM_경위도_pyproj_와_같음(r):
for (epsg, x, y), (lon, lat) in zip(POINTS, r["toLonLat"]):
want_lon, want_lat = tm_to_lonlat(x, y, epsg)
assert abs(lon - want_lon) < 1e-8 and abs(lat - want_lat) < 1e-8, (epsg, x, y)
for (epsg, lon, lat), (x, y) in zip(LONLAT, r["toTm"]):
want_x, want_y = lonlat_to_tm(lon, lat, epsg)
assert math.hypot(x - want_x, y - want_y) < 1e-3, (epsg, lon, lat)
def test_모르는_좌표계는_null(r):
assert r["unknown"] is None
def test_화면_TM_틀이_정식_자리와_같음(r):
for frame in r["frames"]:
for probe in frame["probes"]:
(ex, ey), (gx, gy) = probe["exact"], probe["got"]
assert math.hypot(ex - gx, ey - gy) < 0.5, probe
# 되돌림(화면 → TM)
probes = [(285303.7 + dx, 410237.0 + dy) for dx in (-40, 0, 40) for dy in (-25, 25)]
for (x, y), probe in zip(probes, r["frames"][1]["probes"]):
bx, by = probe["back"]
assert math.hypot(bx - x, by - y) < 1e-3
def test_옛_같은_직사각형_꼴은_귀퉁이에서_어긋남():
"""고친 까닭 — TM 상자 · 경위도 상자를 같은 직사각형으로 보면 위 오른쪽 귀퉁이 수십 m."""
x, y = META["x_max"], META["y_max"]
lon, lat = tm_to_lonlat(x, y, 5186)
w = META["x_max"] - META["x_min"]
h = META["y_max"] - META["y_min"]
dx = ((lon - META["lon_min"]) / (META["lon_max"] - META["lon_min"]) - 1) * w
dy = ((META["lat_max"] - lat) / (META["lat_max"] - META["lat_min"])) * h
assert math.hypot(dx, dy) > 50
def test_위성_조각_대리는_범위_밖을_막음():
"""586-1 GL 바탕 위성 조각 — z6~19 · 칸 번호 범위 밖 = 400(브이월드에 안 물음)."""
import asyncio
import uuid
from fastapi import HTTPException
from D03_PlanAnalysis.D03_PlanAnalysis_Router import plan_satellite_tile
for z, x, y in ((5, 0, 0), (20, 0, 0), (14, 2**14, 0), (14, 0, -1)):
with pytest.raises(HTTPException) as error:
asyncio.run(plan_satellite_tile(uuid.uuid4(), z, x, y))
assert error.value.status_code == 400