Files
Aislo/resources/tester/test_347_1_descent_store.py
T
eomsangdonandClaude Opus 5.5 0e0c1be1c0 perf(배수유역): 347-1 흐름 방향 · 도엽 좌표 저장 — 격자 원점 좌표 정수 m · 노선이 범위 안이면 읽기만
- 격자 원점 = 좌표 정수 m(GRID_ORIGIN_XY · 옛 「도로 시작점」 지시를 바꿈 · 사용자 확정)
- 흐름 방향 저장본 drainage/descent_<입력 지문>_<범위>.npz — 입력 지문 = 도엽 등고선 지문 + 남긴 등고 단 + 셀 + 계산 규칙 · 덮으면 읽어 잘라 씀 · 최근 2벌
- 도엽(하천 · 등고선) 사업지 좌표 저장본 drainage/00_sheet_metric.pkl — 원본 자국 · 좌표계가 같으면 재투영 안 함
- 검증_sub3 실측: 저장본 읽기 = 지은 것과 칸마다 같음(적색 · 해석 영역 · 화살표 · 귀속 차이 0) · 108 → 10.8 s(붐빔 · 340 기준 약 6 s) · 원점 바뀜으로 적색 460,035 → 459,792칸(−0.05 %) · 관 자리 같음
- 시험 test_347_1_descent_store · 실측 bench 저장본 갈래

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
2026-10-16 11:21:13 +09:00

129 lines
5.2 KiB
Python

"""347-1 흐름 방향 · 도엽 좌표 저장 — 저장본을 읽어도 결과 같음 · 두 번째는 짓지 않음.
- 격자 원점 = 좌표 정수 m(셀 모서리 0,0) — 노선이 바뀌어도 같은 자리 셀은 같은 자리.
- 방향장 저장본: 입력 지문(도엽 지문 · 남긴 등고 단 · 셀) 같고 범위가 덮으면 읽기만 · 최근 2벌.
- 도엽 좌표 저장본: 원본 자국 · 좌표계가 같으면 다시 안 바꿈.
실측(검증_sub3 · 차이 0칸 · 108 → 10.8 s 붐빔 · 340 기준 약 6 s)은 bench_342_watershed.py.
"""
import math
import numpy as np
from shapely.geometry import LineString
from B04_PreProcess import B04_PreProcess_Engine_Watershed_Expand as expand
from B04_PreProcess import B04_PreProcess_Router_Watershed as router
from B04_PreProcess.B04_PreProcess_Engine_Watershed_Descent import (
descent_input_key,
iter_saved_descents,
)
from B04_PreProcess.B04_PreProcess_Engine_Watershed_Grid import (
GRID_ORIGIN_XY,
GridSpec,
grid_spec_from_bounds,
)
def _contours():
features = []
for radius in range(10, 200, 10):
coords = [
[150.0 + radius * math.cos(t), -150.0 + radius * math.sin(t)]
for t in np.linspace(0, 2 * math.pi, 241)
]
features.append(
{
"geometry": {"type": "LineString", "coordinates": coords},
"properties": {"elevation": 300.0 - radius},
}
)
return features
def test_grid_origin_is_integer_metre():
assert GRID_ORIGIN_XY == (0.0, 0.0)
spec = grid_spec_from_bounds(10.37, -80.6, 90.2, -3.4, 1.0, anchor_xy=GRID_ORIGIN_XY)
assert spec.x_min == 10.0 and spec.y_max == -3.0
def test_input_key_follows_contours_and_floor():
contours = _contours()
base = descent_input_key("도엽-1", contours, None, 1.0)
assert base == descent_input_key("도엽-1", contours, None, 1.0)
assert base != descent_input_key("도엽-2", contours, None, 1.0) # 도엽 · 좌표계 바뀜
assert base != descent_input_key("도엽-1", contours, 200.0, 1.0) # 남긴 등고 단 바뀜
assert base == descent_input_key("도엽-1", contours, 100.5, 1.0) # 단 집합 그대로(전부 남음)
def _run(store, monkeypatch, counter):
real = expand.build_contour_descent
def counting(*args, **kwargs):
counter.append(1)
return real(*args, **kwargs)
monkeypatch.setattr(expand, "build_contour_descent", counting)
spec = GridSpec(x_min=100.0, y_max=-100.0, cell_m=1.0, n_rows=100, n_cols=100)
domain = np.zeros((100, 100), bool)
domain[20:80, 20:80] = True
route = LineString([(110.0, -150.0), (190.0, -150.0)])
result = expand.expand_by_red_boundary(
spec, domain, _contours(), route, [], None, descent_store=store
)
return result.analysis
def test_store_gives_same_result_and_reads_second_time(tmp_path, monkeypatch):
monkeypatch.setattr(expand, "DESCENT_SPARE_M", 40.0)
built: list[int] = []
plain = _run(None, monkeypatch, built)
store = (tmp_path, "도엽-1")
first = _run(store, monkeypatch, built)
count_after_first = len(built)
second = _run(store, monkeypatch, built)
assert len(built) == count_after_first # 두 번째는 짓지 않음
for analysis in (first, second):
assert analysis.spec == plain.spec
assert np.array_equal(analysis.flow.reaches_road, plain.flow.reaches_road)
assert np.array_equal(analysis.descent.receiver, plain.descent.receiver)
saved = list(tmp_path.glob("descent_*.npz"))
assert 1 <= len(saved) <= 2
key = descent_input_key("도엽-1", _contours(), None, 1.0)
assert next(iter_saved_descents(tmp_path, key)).spec.n_rows >= plain.spec.n_rows
def test_store_keeps_two_newest(tmp_path, monkeypatch):
from B04_PreProcess.B04_PreProcess_Engine_Watershed_Descent import save_descent
monkeypatch.setattr(expand, "DESCENT_SPARE_M", 0.0)
analysis = _run(None, monkeypatch, [])
for index in range(3):
moved = GridSpec(100.0 + index, -100.0, 1.0, analysis.spec.n_rows, analysis.spec.n_cols)
descent = analysis.descent
save_descent(tmp_path, "key", type(descent)(**{**descent.__dict__, "spec": moved}))
assert len(list(tmp_path.glob("descent_*.npz"))) == 2
def test_metric_sheets_cached_until_source_changes(tmp_path, monkeypatch):
sheets = tmp_path / "sheets"
sheets.mkdir()
feature = {
"type": "Feature",
"properties": {"elevation": 10},
"geometry": {"type": "LineString", "coordinates": [[0, 0], [1, 1]]},
}
import json
for name in (router.STREAM_FILE, router.CONTOUR_FILE):
(sheets / name).write_text(json.dumps({"features": [feature]}), "utf-8")
calls = []
real = router._reproject_features
monkeypatch.setattr(
router, "_reproject_features", lambda f, t: calls.append(1) or real(f, None)
)
first = router._metric_sheets(sheets, tmp_path / "drainage", "EPSG:5186", None)
again = router._metric_sheets(sheets, tmp_path / "drainage", "EPSG:5186", None)
assert len(calls) == 2 and first == again # 두 번째는 저장본
router._metric_sheets(sheets, tmp_path / "drainage", "EPSG:5179", None) # 좌표계 바뀜
assert len(calls) == 4