- 공용 부품 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>
171 lines
6.8 KiB
Python
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]
|