- 격자 원점 = 좌표 정수 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
129 lines
5.2 KiB
Python
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
|