Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 — 586-0 겹 맞춤 · UI_Page 충돌(points + epsg) 양쪽 살림 · test_582 겹 수 낡은 단언(12 → 모두 바탕)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/* 586 TM ↔ 경위도 · 화면 TM 틀 헬퍼 — `D03_PlanAnalysis_Tm.ts` 를 stdin 점 목록으로 돌려 JSON 으로 냄.
|
||||
* 입력 { points: [[epsg, x, y]], lonlat: [[epsg, lon, lat]], frames: [{ epsg, box, view, probes }] }
|
||||
*/
|
||||
const path = require("path");
|
||||
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const { load } = makeLoader({ root: ROOT });
|
||||
const tm = load("D03_PlanAnalysis", "logic", "D03_PlanAnalysis_Tm.ts");
|
||||
|
||||
const input = JSON.parse(require("fs").readFileSync(0, "utf-8"));
|
||||
const out = {
|
||||
unknown: tm.tmParams(5174),
|
||||
toLonLat: input.points.map(([epsg, x, y]) => tm.tmToLonLat(tm.tmParams(epsg), x, y)),
|
||||
toTm: input.lonlat.map(([epsg, lon, lat]) => tm.lonLatToTm(tm.tmParams(epsg), lon, lat)),
|
||||
frames: input.frames.map(({ epsg, box, view, probes }) => {
|
||||
// 경위도 상자를 화면 직사각형에 펴는 바탕(캔버스 지금 꼴) · 가운데 = (cx, cy)
|
||||
const [lonMin, latMin, lonMax, latMax] = box;
|
||||
const [w, h, x0, y0, sx, sy] = view;
|
||||
const project = (lon, lat) => [
|
||||
x0 + ((lon - lonMin) / (lonMax - lonMin)) * sx,
|
||||
y0 + ((latMax - lat) / (latMax - latMin)) * sy,
|
||||
];
|
||||
const unproject = (px, py) => [
|
||||
lonMin + ((px - x0) / sx) * (lonMax - lonMin),
|
||||
latMax - ((py - y0) / sy) * (latMax - latMin),
|
||||
];
|
||||
const p = tm.tmParams(epsg);
|
||||
const frame = tm.localTmFrame(p, project, unproject, w / 2, h / 2);
|
||||
return {
|
||||
pxPerMeter: frame.pxPerMeter,
|
||||
probes: probes.map(([x, y]) => {
|
||||
const exact = project(...tm.tmToLonLat(p, x, y));
|
||||
const got = frame.toScreen(x, y);
|
||||
const back = frame.toMetric(...got);
|
||||
return { exact, got, back };
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -75,7 +75,7 @@ def test_D03_만_켬():
|
||||
layers = _read("D03_PlanAnalysis/D03_PlanAnalysis_UI_Layers.ts")
|
||||
assert "indexLayerLod(prepare(collection" in layers
|
||||
# 581-1 합침 — 겹 모두 바탕 캔버스
|
||||
assert layers.count("base: true") == layers.count("draw: (context, map)") == 12
|
||||
assert layers.count("base: true") == layers.count("draw: (context, map)") >= 12 # 겹 모두 바탕(581 · 584 로 늘어남)
|
||||
plan_map = _read("D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts")
|
||||
assert "requestAnimationFrame(flush)" in plan_map
|
||||
assert "drawTop();" in plan_map
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# -*- 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 어긋남(고친 까닭)
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user