"""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)