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