Files
Aislo/resources/tester/test_633_grid_cell.py
T
eomsangdonandClaude Opus 5.5 a1092b6c7c fix(633): 격자 칸 = 좌표 간격 — B05 급경사 · 음영 · 0차곡 칸 크기 틀림 고침(요구 538)
- 공용 grid_cell_m — 적힌 칸과 좌표 간격이 1 % 넘게 다르면 좌표 간격 · 로그 한 줄
- D03 급경사 · 음영 · 0차곡(상자 · DEM 길) · B07 도곽 음영이 공용 함수로
- 판 올림 — 급경사 · 음영 2 → 3 · 0차곡 DEM 길 따로 판 4
- B04 평활 격자는 넓힌 실제 칸을 적음
- 검증_sub2 B 평활: 급경사 중앙 146 → 67 % · 빨강 89.4 → 26.7 % · 0차곡 21 가닥 989 m → 99 가닥 4,110 m · D · B 수치지형도 그림 전후 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHXDsevXBHePbAoqUv
2026-10-31 22:52:53 +09:00

111 lines
4.8 KiB
Python

"""격자 칸 = 좌표 간격(요구 538 · PLAN 633).
- 적힌 칸(0.5) ≠ 좌표 간격(1.0) → 급경사 · 음영 · 0차곡이 좌표 간격으로 셈
- 둘이 맞는 격자(D 수치지형도) → 적힌 값 그대로 · 결과 전과 같음
./venv/Scripts/python.exe -m pytest resources/tester/test_633_grid_cell.py -q
"""
import logging
import numpy as np
from common_util.common_util_grid_cell import grid_cell_m
from D03_PlanAnalysis import D03_PlanAnalysis_Layers as layers
from D03_PlanAnalysis import D03_PlanAnalysis_ZeroOrder as zero_order
EPSG = 5186
X0, Y0 = 200000.0, 450000.0
def _npz(path, cell: float, written: float | None, n: int = 60, tilt: float = 0.5):
x, y = X0 + np.arange(n) * cell, Y0 + np.arange(n) * cell
z = np.add.outer(np.zeros(n), x - X0) * tilt # 동쪽으로 tilt(= 50 %)
extra = {} if written is None else {"resolution": np.array([written], np.float32)}
path.parent.mkdir(parents=True, exist_ok=True)
np.savez(path, x=x, y=y, z=z, valid_mask=np.ones(z.shape, bool), **extra)
return path
def test_grid_cell_prefers_spacing(tmp_path, caplog) -> None:
with np.load(_npz(tmp_path / "a.npz", 2.0, 2.0)) as dem:
assert grid_cell_m(dem, dem["x"]) == 2.0
with np.load(_npz(tmp_path / "b.npz", 1.5, None)) as dem:
assert grid_cell_m(dem, dem["x"]) == 1.5
with caplog.at_level(logging.WARNING), np.load(_npz(tmp_path / "c.npz", 1.078, 0.5)) as dem:
assert abs(grid_cell_m(dem, dem["x"]) - 1.078) < 1e-9
assert "좌표 간격" in caplog.text
def test_slope_uses_real_cell(tmp_path) -> None:
npz = _npz(tmp_path / "models" / "dtm_csf_smooth.npz", 1.0, 0.5)
value = layers.slope_layer(tmp_path, tmp_path / "models", EPSG, npz=npz)
counts = np.asarray(value["counts"])
assert int(counts.argmax()) == 50 # 0.5 를 믿으면 100
assert value["box_m"][0] == X0 - 0.5
def test_sheet_slope_unchanged(tmp_path) -> None:
"""적힌 칸 = 좌표 간격 → 옛 셈(적힌 칸)과 같은 그림."""
npz = _npz(tmp_path / "models" / "dtm_sheet_x.npz", 2.0, 2.0)
value = layers.slope_layer(tmp_path, tmp_path / "models", EPSG, npz=npz)
with np.load(npz) as dem:
old = layers.slope_values_png(dem["z"], dem["valid_mask"], 2.0)
assert value["image"] == old["image"] and value["counts"] == old["counts"]
def test_hillshade_uses_real_cell(tmp_path) -> None:
npz = _npz(tmp_path / "models" / "dtm_csf_smooth.npz", 1.0, 0.5)
value = layers.slope_layer(tmp_path, tmp_path / "models", EPSG, kind="hillshade", npz=npz)
with np.load(npz) as dem:
real = layers.hillshade_layer_png(dem["z"], dem["valid_mask"], 1.0)
assert value["image"] == real["image"]
def test_zero_order_dem_uses_real_cell(tmp_path, monkeypatch) -> None:
npz = _npz(tmp_path / "models" / "dtm_csf_smooth.npz", 1.0, 0.5, n=40)
seen = {}
def spy(z, valid, x, y, cell, *rest):
seen.update(shape=z.shape, dx=float(x[1] - x[0]), cell=cell)
return []
monkeypatch.setattr(zero_order, "zero_order_lines", spy)
value = zero_order.dem_zero_order_layer(tmp_path, npz, EPSG)
# 0.5 를 믿으면 4 칸 건너(10×10 · 실제 4 m 를 2 m 로 셈)
assert seen == {"shape": (20, 20), "dx": 2.0, "cell": 2.0} and value["cell_m"] == 2.0
assert zero_order._project_box(npz)[0] == X0 - 0.5
def test_b07_frame_hillshade_uses_real_cell(tmp_path) -> None:
"""B07 도곽 음영 — 옛 평활 파일(0.5 적힘)도 좌표 간격으로."""
from types import SimpleNamespace
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Lidar import hillshade_png
from B07_DesignDetail.B07_DesignDetail_Router_Support_Basin import _hillshade_for_box
npz = _npz(tmp_path / "B04_PreProcess" / "models" / "dtm_csf_smooth.npz", 1.0, 0.5)
context = SimpleNamespace(project_root=tmp_path, surface_params={})
url, _ = _hillshade_for_box(context, (X0, Y0, X0 + 100, Y0 + 100))
with np.load(npz) as dem:
assert url == hillshade_png(dem["z"], dem["valid_mask"], 1.0)[0]
def test_smooth_writes_real_cell(tmp_path) -> None:
"""B04 평활 격자 — 칸을 넓히면(꼭짓점 상한) 넓힌 칸을 적음."""
from types import SimpleNamespace
from B04_PreProcess.B04_PreProcess_Engine_Smooth import smooth_dtm
src = _npz(tmp_path / "dtm_csf.npz", 1.0, 1.0, n=20)
wide = X0 + np.arange(10) * 2.0 # 요청 0.5 → 상한 때문에 2.0
context = SimpleNamespace(
config={"dtm_grid_resolution_meters": 1.0},
preview_grid=lambda res: (wide, Y0 + np.arange(10) * 2.0, None),
contains_xy=lambda xs, ys: np.ones(np.shape(xs), bool),
bounds=np.array([[X0, X0 + 18.0], [Y0, Y0 + 18.0], [0.0, 10.0]]),
)
smooth_dtm(context, tmp_path, "dtm_csf", src)
with np.load(tmp_path / "dtm_csf_smooth.npz") as dem:
assert float(dem["resolution"][0]) == 2.0