Files
Aislo/resources/tester/test_520_6_watershed_once.py
T
eomsangdonandClaude Opus 5.5 fb4beb99c9 perf(520-6): B04 배수유역 확장 한 번 셈 · 노선 무관 흐름 방향장 저장본 · 등고선 점 구름 메모리 보관
- 흐름 방향장 열쇠 · 짓기에서 노선 최저점 하한 뺌 — 프로젝트마다 지형 저장본 한 종류 · 초안 [업데이트] 때 다시 짓지 않음
- expand_by_red_boundary 안 — 저장 큰 방향장 전체에서 도로 도달 한 번 판정 → 「도로로 오는 셀 + 확장 폭 띠 ∪ 1차 영역」으로 자름 · 큰 격자 가장자리에 닿으면 옛 회차 확장
- 끊기 확인 = 한 번 셈 앞 · [계측] 줄(한 번 셈 · 점 구름)
- build_contour_cloud — 하한 없는 구름을 피처 목록 · 자르는 범위 열쇠로 2 벌 보관 · 하한은 점 표고로 거름(옛 결과와 점 · 차례 같음)
- test_347_1 열쇠 시험 — 하한 인자 없어짐에 맞춰 고침
- 지킴 시험 test_520_6 — 합성 비탈에서 한 번 셈 = 회차 확장(도로로 오는 셀 · 도로 셀 귀속) · 하한 달라도 방향장 한 번만 지음
- 실데이터 대조(검증_sub2 · 검증_랩탑메인 · 검증_sub3) — 도로로 오는 셀 · 귀속 · 관별 면적 12 개 차이 0 · 초안 8.0~9.3 s → 0.8~2.5 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 08:18:05 +09:00

89 lines
3.5 KiB
Python

"""520-6 배수유역 확장 한 번 셈 · 노선 무관 방향장 저장본.
- 한 번 셈(큰 방향장 전체 판정 → 도로로 오는 셀 + 띠 ∪ 1차 영역으로 자름) = 회차 확장과 도로로
오는 셀 · 도로 셀 귀속이 셀마다 같음(합성 등고선 · 능선까지 비탈).
- 방향장 열쇠 · 짓기가 노선 최저점 하한과 무관 — 하한이 달라도 저장본 하나를 읽기만.
"""
import numpy as np
from shapely.geometry import LineString
from B04_PreProcess import B04_PreProcess_Engine_Watershed_Expand as expand
from B04_PreProcess.B04_PreProcess_Engine_Watershed_Flow import trace_flow
from B04_PreProcess.B04_PreProcess_Engine_Watershed_Grid import GridSpec
def _ridge_contours():
"""도로 북쪽 비탈 — y = −60 능선까지 오르고 그 밖 · 도로 남쪽은 내려감(곧은 등고선)."""
features = []
for y in range(-250, 0, 5):
level = 290.0 - abs(y + 60.0)
features.append(
{
"geometry": {"type": "LineString", "coordinates": [[0.0, y], [300.0, y]]},
"properties": {"elevation": level},
}
)
return features
def _case():
spec = GridSpec(x_min=110.0, y_max=-130.0, cell_m=1.0, n_rows=40, n_cols=80)
domain = np.zeros((40, 80), bool)
domain[5:35, 5:75] = True
route = LineString([(120.0, -150.0), (180.0, -150.0)])
return spec, domain, route
def _run(monkeypatch, once, floor=None, store=None):
monkeypatch.setattr(expand, "DESCENT_SPARE_M", 60.0)
if not once:
monkeypatch.setattr(expand, "_expand_once", lambda *args: None)
spec, domain, route = _case()
return expand.expand_by_red_boundary(
spec, domain, _ridge_contours(), route, [], floor, band_m=10.0, descent_store=store
)
def _world(analysis):
"""도로로 오는 셀 → {(x, y): 도로 셀 누가거리}."""
spec = analysis.spec
routing = trace_flow(analysis.terrain, analysis.road)
red = analysis.flow.reaches_road & analysis.flow.analyzed
chainage = analysis.road.chainage
out = {}
for index in np.flatnonzero(red):
slot = routing.road_slot[index]
x = spec.x_min + (index % spec.n_cols) * spec.cell_m
y = spec.y_max - (index // spec.n_cols) * spec.cell_m
out[(round(x, 3), round(y, 3))] = None if slot < 0 else round(float(chainage[slot]), 3)
return out
def test_once_matches_round_expansion(monkeypatch):
rounds = _run(monkeypatch, once=False)
monkeypatch.undo()
once = _run(monkeypatch, once=True)
assert rounds.rounds >= 2 and rounds.closed # 회차 확장이 실제로 여러 번 넓힘
assert once.closed and once.rounds == 1 and once.added_cells > 0
old, new = _world(rounds.analysis), _world(once.analysis)
assert len(old) > 3_000
assert old == new
def test_descent_key_and_build_ignore_floor(tmp_path, monkeypatch):
built = []
real = expand.build_contour_descent
def counting(*args, **kwargs):
built.append(args[2:] + tuple(kwargs.values()))
return real(*args, **kwargs)
monkeypatch.setattr(expand, "build_contour_descent", counting)
store = (tmp_path, "도엽-1")
low = _run(monkeypatch, once=True, floor=None, store=store)
high = _run(monkeypatch, once=True, floor=230.0, store=store)
assert len(built) == 1 and built[0] == () # 하한 없이 한 번만 지음 · 두 번째는 읽기만
assert len(list(tmp_path.glob("descent_*.npz"))) == 1
assert _world(low.analysis) == _world(high.analysis)