Files
Aislo/resources/tester/test_589_2_gl_frame.py
T
eomsangdonandClaude Opus 5.5 21afc379e4 feat(B04·B05): 하단 지도 · 평면 노선 편집 · 배수유역도 바탕 MapLibre 적용 — 공용 GL 부품 · TM 주인 카메라 · 처음 = 켬 · WebGL 못 쓰면 옛 캔버스 (PLAN 589-2 · 요구 448)
- 공용 부품 ui_template_gl_map(지도 세우기 · 자료 · 켬끔 · 색 다시 · 카메라) — D03 GL 껍데기를 올림 · D03 은 겹 꼴만(바뀜 없음)
- TM 주인 따라가기(B04_PreProcess_UI_MapGl · logic/_MapGl_Frame) — 화면 가운데 1차 맞춤 닮음꼴 카메라 + 남는 늘림(웹 메르카토르 가로세로 0.43 %)은 지도 칸 CSS 행렬 · 칸 둘레 여유 8 px
- B04 하단 지도 — 위성 조각(줌인 선명) · 백지도 · 혼합 · 도엽 · 지적 · 행정 · 등고선 겹 GL · 등고 글 · 배수유역 · 계획선 · 관 = 위 캔버스
- B05 배수유역도 — 위성 · 세부유역 채움(짝홀 다각형) · 분수령 · 도엽 GL · 번호 · 상류 세류 · 관 · 강도 = 위 캔버스
- B05 평면 노선 편집 — 바탕칠 · LAS · 도엽 등고선(간격 거름 · 1 m 선) · 하천(LAS 상자 자름) GL · 노선 둘레 띠 = 지도 칸 clip-path · 고른 등고선 · 글 · 노선 · 꼭짓점 = 위 캔버스
- 켬끔 칸(GPU · 세 화면 한 취향 aislo:pref:map.canvas) · WebGL 못 씀 · 잃음 = 옛 캔버스 + 알림 · 누름 · 끌기 판정 손 안 댐
- B04 층 단추 글 = MapLayers gisLayerLabel(지도 본체 700 줄)
- 지킴 시험 test_589_2_gl_frame(카메라 대 캔버스 0.5 px · 칸 행렬 늘이기만 · 되돌림 1 mm · 상자 자르기 · 짝홀 다각형)

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

171 lines
6.8 KiB
Python

# -*- coding: utf-8 -*-
"""589-2 B04 · B05 GL 바탕 — MapLibre 카메라가 TM 주인 화면(589-1 자리)을 따라감.
- 카메라(화면 가운데 1차 맞춤 · 닮음꼴) + 칸 CSS 행렬(남는 늘림) → MapLibre 식 화면 px = 캔버스 자리
(격자 쌍선형 · 화면 식) 0.5 px 안(처음 화면 · 노선 자리 · 크게 당김 · 돌림 30° · 화면 밖 반 폭까지)
- 칸 행렬 = 늘이기만(고윳값 ≥ 1 · 웹 메르카토르 가로세로 차 0.4 %)
- TM 겹 되돌림(정규화 → 경위도) — 다시 정규화하면 1 mm 안 · pyproj 와 2 cm 안
- 준비한 겹 → GeoJSON — 상자 자르기(하천 = LAS 등고선 있는 데까지) · 닫힌 고리 · 점
- 유역 고리(조각 · 구멍) → 다각형 — 캔버스 짝홀 채움과 같은 묶음
Node 헬퍼(`helper_589_2_gl_frame.cjs`)가 진짜 TS 를 돌린다.
"""
import json
import math
import os
import subprocess
import pytest
from pyproj import Transformer
from B04_PreProcess.B04_PreProcess_Engine_VWorld import tm_grid_for_meta
from resources.tester.test_589_tm_grid import SITES, _meta
HERE = os.path.dirname(os.path.abspath(__file__))
# (w, h, 배율, 옮김 x, y, 돌림 도) — 처음 화면 · 노선 자리 · 크게 당김 · 돌림
VIEWS = [
(1400, 560, 1, 0, 0, 0),
(1200, 700, 2.33, -120, 80, 0),
(1400, 560, 300, -60000, 40000, 0),
(1200, 700, 4, 200, -150, 30),
]
LAYER = {
"features": [
{
"kind": "line",
"labelValue": 125,
"parts": [{"coords": [0.0, 0.5, 0.5, 0.5, 1.0, 0.5], "closed": False}],
},
{
"kind": "line",
"labelValue": None,
"parts": [{"coords": [0.2, 0.2, 0.4, 0.2, 0.4, 0.4], "closed": True}],
},
{"kind": "point", "labelValue": None, "parts": [{"coords": [0.3, 0.3, 0.9, 0.9]}]},
]
}
BOX = [0.25, 0.0, 0.75, 1.0]
RINGS = [
[[0, 0], [10, 0], [10, 10], [0, 10]], # 바깥
[[2, 2], [8, 2], [8, 8], [2, 8]], # 구멍
[[4, 4], [6, 4], [6, 6], [4, 6]], # 구멍 속 섬
[[20, 0], [25, 0], [25, 5]], # 떨어진 조각
]
def _run(site):
crs, box, _ = SITES[site]
meta = _meta(crs, box)
meta["tm_grid"] = tm_grid_for_meta(meta, crs)
lon_min, lon_max, lat_min, lat_max = box
# 상자 안 · 둘레 반 폭(화면 밖까지)
lonlat = [
(lon_min + a / 8 * (lon_max - lon_min), lat_min + b / 8 * (lat_max - lat_min))
for a in range(-4, 13)
for b in range(-4, 13)
]
norm = [(a / 6, b / 6) for a in range(-1, 8) for b in range(-1, 8)]
payload = {
"meta": meta,
"lonlat": lonlat,
"views": VIEWS,
"norm": norm,
"layer": LAYER,
"box": BOX,
"rings": RINGS,
}
proc = subprocess.run(
["node", os.path.join(HERE, "helper_589_2_gl_frame.cjs")],
input=json.dumps(payload),
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return meta, norm, json.loads(proc.stdout)
@pytest.fixture(scope="module", params=["B", "D"])
def site(request):
return request.param, *_run(request.param)
def test_GL_카메라는_캔버스_자리를_1px_안에(site):
name, _, _, out = site
for (w, h, scale, *_rest), view in zip(VIEWS, out["views"]):
worst = 0.0
for canvas, gl in view["points"]:
# 화면 안 · 둘레 반 폭까지만 잰다(그 밖은 안 보임)
if not (-w / 2 <= canvas[0] <= w * 1.5 and -h / 2 <= canvas[1] <= h * 1.5):
continue
worst = max(worst, math.hypot(canvas[0] - gl[0], canvas[1] - gl[1]))
assert worst < 0.5, (name, scale, worst)
# 화면 격자(귀퉁이 · 가장자리 · 가운데) — 되돌림 거쳐 제자리
corner = max(math.hypot(s[0] - g[0], s[1] - g[1]) for s, g in view["screen"])
assert corner < 0.5, (name, scale, corner)
def test_남는_늘림은_칸_행렬로_늘이기만(site):
_, _, _, out = site
for view in out["views"]:
a, b, c, d = view["camera"]["css"]
# 대칭 · 고윳값 ≥ 1 · 웹 메르카토르 가로세로 차(0.4 %) 안팎
assert abs(b - c) < 1e-9
trace, det = a + d, a * d - b * c
least = trace / 2 - math.sqrt(max(trace * trace / 4 - det, 0))
most = trace / 2 + math.sqrt(max(trace * trace / 4 - det, 0))
assert least == pytest.approx(1, abs=1e-9) and 1.003 < most < 1.006
def test_돌림은_카메라_도에_그대로():
_, _, out = _run("D")
plain = out["views"][1]["camera"]["bearing"]
turned = out["views"][3]["camera"]["bearing"]
# 화면 30° 돌림 = 카메라 −30° (+ 수렴각 몫은 같음)
assert abs((turned - plain) + 30) < 0.01
def test_TM_되돌림은_다시_정규화하면_제자리(site):
name, meta, norm, out = site
inv = Transformer.from_crs(SITES[name][0], "EPSG:4326", always_xy=True)
for (nx, ny), (lon, lat, bx, by) in zip(norm, out["back"]):
meters = math.hypot((bx - nx) * meta["width_meters"], (by - ny) * meta["height_meters"])
assert meters < 1e-3, (nx, ny, meters)
x = meta["x_min"] + nx * meta["width_meters"]
y = meta["y_min"] + (1 - ny) * meta["height_meters"]
want = inv.transform(x, y)
fwd = Transformer.from_crs("EPSG:4326", SITES[name][0], always_xy=True)
gx, gy = fwd.transform(lon, lat)
assert math.hypot(gx - x, gy - y) < 0.02, (nx, ny, want)
def test_준비한_겹_GeoJSON_자르기():
_, _, out = _run("B")
lines = [f for f in out["geo"]["features"] if f["geometry"]["type"] == "LineString"]
# 가로줄 → 상자 0.25~0.75 만 · 높이 그대로
first = lines[0]
assert first["properties"] == {"level": 125}
xs = [p[0] for p in first["geometry"]["coordinates"]]
assert min(xs) == pytest.approx(0.25) and max(xs) == pytest.approx(0.75)
# 닫힌 고리 → 상자 안 조각만(0.25 왼쪽 몫 잘림)
ring = lines[1]["geometry"]["coordinates"]
assert all(p[0] >= 0.25 - 1e-12 for p in ring)
# 점 — 상자 밖(0.9) 뺌
points = [f for f in out["geo"]["features"] if f["geometry"]["type"] == "MultiPoint"]
assert points[0]["geometry"]["coordinates"] == [[0.3, 0.3]]
# 상자 없으면 통째 · 닫힌 고리는 첫 점으로 닫음
closed = out["geoAll"]["features"][1]["geometry"]["coordinates"]
assert closed[0] == closed[-1] and len(closed) == 4
def test_유역_고리는_짝홀_다각형():
_, _, out = _run("B")
polygons = out["polygons"]
# 바깥(+ 구멍) · 구멍 속 섬 · 떨어진 조각 = 셋
assert len(polygons) == 3
assert len(polygons[0]) == 2 and polygons[0][1][0] == [2, 2]
assert len(polygons[1]) == 1 and polygons[1][0][0] == [4, 4]
assert len(polygons[2]) == 1 and polygons[2][0][0] == [20, 0]