fix(B04·B05): 지도 경위도 겹 · 위성 그림을 TM 겹 자리로 맞춤 (PLAN 589-1 · 요구 447 · 448)

- 서버 위성 메타 응답에 그림 상자 9×9 TM 격자(tm_grid · pyproj · 응답 때 셈 · 귀퉁이 1 cm 넘게 다르면 안 줌)
- 공용 createTmNormalizer(고를 칸 toNorm · 격자 쌍선형) · normOfLonLat · imageScreenMatrix · placeMapImage
- B04 하단 지도 · B05 배수유역도 · B05 평면 노선 편집이 TM 자리로 · 위성 그림 = 화면 가운데 1차 행렬(contain 뺌)
- Watershed 손 베낌 식 넷 · 줌아웃 한계 = 공용 식 · D03 길(createNormalizer) 바뀜 없음 · TM 길 손 안 댐
- 지킴 시험 test_589_tm_grid(Bessel · GRS80 · 격자 = pyproj 0.02 m · 그림 행렬 0.5 px · 옛 꼴 어긋남)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 20:21:39 +09:00
co-authored by Claude Opus 5.5
parent 7beca06a06
commit ba18b0f96d
14 changed files with 475 additions and 79 deletions
@@ -195,3 +195,42 @@ def download_vworld_satellite_map(
meta_json_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
return map_png_path
# 그림 상자 경위도 격자 한 변 마디 수 — 쌍선형 나머지 용화 0.001 · D 0.006 · 큰 상자 0.014 m(589-1).
TM_GRID_NODES = 9
def tm_grid_for_meta(meta: dict, crs_input: str, nodes: int = TM_GRID_NODES) -> dict | None:
"""그림 상자 경위도 고른 마디의 TM 값(589-1 · 화면이 경위도 겹을 TM 자리로 놓음).
마디 `[j * n + i]` = 경도 `lon_min + i/(n-1)·폭` · 위도 `lat_min + j/(n-1)·높이`(j 는 남 → 북).
왼위 · 오른아래 마디가 메타 `x_min · y_max` · `x_max · y_min` 과 1 cm 넘게 다르면(메타를 다른
좌표계로 만든 것) 격자를 주지 않음 — 화면은 옛 같은 네모 식으로 남음.
"""
keys = ("lon_min", "lon_max", "lat_min", "lat_max", "x_min", "x_max", "y_min", "y_max")
if any(not isinstance(meta.get(key), (int, float)) for key in keys):
return None
transformer = Transformer.from_crs("EPSG:4326", crs_input, always_xy=True)
lon_span = meta["lon_max"] - meta["lon_min"]
lat_span = meta["lat_max"] - meta["lat_min"]
lons = [meta["lon_min"] + i / (nodes - 1) * lon_span for i in range(nodes)]
lats = [meta["lat_min"] + j / (nodes - 1) * lat_span for j in range(nodes)]
xs: list[float] = []
ys: list[float] = []
for lat in lats:
x_row, y_row = transformer.transform(lons, [lat] * nodes)
xs.extend(float(v) for v in x_row)
ys.extend(float(v) for v in y_row)
top_left = nodes * (nodes - 1)
bottom_right = nodes - 1
off = max(
abs(xs[top_left] - meta["x_min"]),
abs(ys[top_left] - meta["y_max"]),
abs(xs[bottom_right] - meta["x_max"]),
abs(ys[bottom_right] - meta["y_min"]),
)
if not math.isfinite(off) or off > 0.01:
logger.warning("위성 메타 귀퉁이와 TM 격자가 %.3f m 어긋남 — 격자 안 줌", off)
return None
return {"n": nodes, "x": [round(v, 4) for v in xs], "y": [round(v, 4) for v in ys]}