- B05 지형 로더 models_dir · cache_dir=None · 비용면 cache_dir=None · 탐색 복도 corridor_m(기본 100 m · 기본이면 묶음 열쇠 그대로) - D03_PlanAnalysis_Terrain — 지점 + 1 km 표본 3×3 도엽 · prune 없음 · 병합 옛 ∪ 새 · 위성 합집합 · 연도별 models_<연도> DEM · plan.prj - D03_PlanAnalysis_Analyze — 연도 구간마다 갈래 넷 · 법정 기준 · 누가 이어 셈 · 파생 저장본(입력 지문) - D03_PlanAnalysis_Router — POST plan/terrain · plan/analyze(뒤에서 · 진행률) · GET plan/progress · plan/analysis · PUT [저장] - 지킴 시험 test_530_1_plan_terrain · test_530_2_plan_analyze · test_530_2_plan_router Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
5.1 KiB
Python
116 lines
5.1 KiB
Python
"""D 예정노선 지형 받기(PLAN 530-1) — 가짜 내려받기 · 옛 도엽 유지 · 새 도엽만 · 빠짐 없음.
|
||
|
||
./venv/Scripts/python.exe -m pytest resources/tester/test_530_1_plan_terrain.py -q
|
||
"""
|
||
|
||
import json
|
||
import math
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from pyproj import Transformer
|
||
|
||
from B04_PreProcess import B04_PreProcess_Engine_SheetStore as store_mod
|
||
from B04_PreProcess.B04_PreProcess_Engine_MapSheet import sheets_for_points
|
||
from common_util.common_util_crs import resolve_project_crs
|
||
from config.config_system_terrain import SHEET_SURFACE_MARGIN_M
|
||
from D03_PlanAnalysis import D03_PlanAnalysis_Terrain as terrain
|
||
|
||
EPSG = 5186
|
||
A, B, C = (230000.0, 450000.0), (231500.0, 451200.0), (238000.0, 455000.0)
|
||
|
||
|
||
@pytest.fixture
|
||
def fake(tmp_path, monkeypatch):
|
||
seen = {"download": [], "merge": [], "satellite": [], "dem": []}
|
||
|
||
def download_sheet(store_dir, sheet_no, version=2):
|
||
seen["download"].append(sheet_no)
|
||
store_dir = Path(store_dir)
|
||
store_dir.mkdir(parents=True, exist_ok=True)
|
||
(store_dir / f"{sheet_no}.zip").write_bytes(b"PK")
|
||
index = store_mod._load_index(store_dir)
|
||
index["sheets"][sheet_no] = {"file": f"{sheet_no}.zip"}
|
||
store_mod._save_index(store_dir, index)
|
||
|
||
def merge(store_dir, sheets, output_dir, layers=None):
|
||
seen["merge"].append(list(sheets))
|
||
(output_dir / "도엽_등고선.geojson").write_text("{}", encoding="utf-8")
|
||
return {}
|
||
|
||
def satellite(prj, bounds, output_dir, layer_name="Satellite", ext="jpeg"):
|
||
seen["satellite"].append(bounds)
|
||
meta = {"x_min": bounds["x"][0], "x_max": bounds["x"][1]}
|
||
meta.update({"y_min": bounds["y"][0], "y_max": bounds["y"][1]})
|
||
(output_dir / "vworld_satellite_meta.json").write_text(json.dumps(meta), "utf-8")
|
||
return output_dir / "vworld_satellite.png"
|
||
|
||
def dem(root, processed, models_dir, route_xy, crs, methods=None):
|
||
seen["dem"].append((models_dir.name, route_xy.tolist(), crs))
|
||
return [{"model_file_path": f"B04_PreProcess/{models_dir.name}/dtm.npz"}]
|
||
|
||
monkeypatch.setattr(store_mod, "download_sheet", download_sheet)
|
||
monkeypatch.setattr(terrain, "parse_and_merge_sheets", merge)
|
||
monkeypatch.setattr(terrain, "download_vworld_satellite_map", satellite)
|
||
monkeypatch.setattr(terrain, "build_sheet_surface_model", dem)
|
||
return tmp_path, seen
|
||
|
||
|
||
def _zips(root):
|
||
return sorted(p.stem for p in store_mod.get_project_map_sheets_dir(root).glob("*.zip"))
|
||
|
||
|
||
def test_more_points_keep_old_sheets_and_fetch_only_new(fake) -> None:
|
||
root, seen = fake
|
||
first = terrain.ensure_plan_terrain(root, [{"year": 2026, "start": A, "end": B}], EPSG)
|
||
old = _zips(root)
|
||
assert sorted(seen["download"]) == old == sorted(first["sheets"]["downloaded"])
|
||
assert first["satellite"] == "done" and list(first["years"]) == [2026]
|
||
# 작업 좌표계 .prj — B 좌표계 창구가 읽음
|
||
assert resolve_project_crs(root) == f"EPSG:{EPSG}"
|
||
|
||
seen["download"].clear()
|
||
segs = [{"year": 2026, "start": A, "end": B}, {"year": 2027, "start": B, "end": C}]
|
||
second = terrain.ensure_plan_terrain(root, segs, EPSG)
|
||
now = _zips(root)
|
||
assert set(old) <= set(now) # 옛 도엽 그대로(prune 없음)
|
||
assert sorted(seen["download"]) == sorted(set(now) - set(old)) # 새 도엽만 받음
|
||
assert seen["merge"][-1] == now # 병합 = 옛 ∪ 새
|
||
# 위성 = 옛 범위 ∪ 새 범위
|
||
first_box, union = seen["satellite"][0], seen["satellite"][-1]
|
||
assert union["x"][0] <= first_box["x"][0] and union["x"][1] >= first_box["x"][1]
|
||
assert union["y"][0] <= first_box["y"][0] and union["y"][1] >= first_box["y"][1]
|
||
assert [d[0] for d in seen["dem"][-2:]] == ["models_2026", "models_2027"]
|
||
assert second["sheets"]["failed"] == {}
|
||
|
||
# 같은 지점 다시 — 받기 0 · 위성 건너뜀 · 병합 열쇠 같음
|
||
seen["download"].clear()
|
||
merges = len(seen["merge"])
|
||
third = terrain.ensure_plan_terrain(root, segs, EPSG)
|
||
assert seen["download"] == [] and third["satellite"] == "skip"
|
||
assert len(seen["merge"]) == merges
|
||
|
||
|
||
def test_far_points_leave_no_gap(fake) -> None:
|
||
root, _ = fake
|
||
far = (A[0] + 9000.0, A[1] + 7000.0) # 11 km — 3×3 두 벌이 안 닿는 거리
|
||
got = terrain.ensure_plan_terrain(root, [{"year": 2026, "start": A, "end": far}], EPSG)
|
||
to_wgs = Transformer.from_crs(f"EPSG:{EPSG}", "EPSG:4326", always_xy=True)
|
||
on_line = []
|
||
for i in range(201):
|
||
lon, lat = to_wgs.transform(
|
||
A[0] + (far[0] - A[0]) * i / 200, A[1] + (far[1] - A[1]) * i / 200
|
||
)
|
||
on_line.append((lat, lon))
|
||
assert set(sheets_for_points(on_line)) <= set(got["sheets"]["merged"])
|
||
|
||
|
||
def test_dem_outline_covers_corridor() -> None:
|
||
box = terrain.dem_outline(A, B)
|
||
reach = terrain.PLAN_CORRIDOR_M + terrain.DEM_EXTRA_M
|
||
assert box[0][0] - SHEET_SURFACE_MARGIN_M <= min(A[0], B[0]) - reach + 1e-9
|
||
assert box[1][1] + SHEET_SURFACE_MARGIN_M >= max(A[1], B[1]) + reach - 1e-9
|
||
samples = terrain.segment_samples(A, C)
|
||
steps = [math.dist(p, q) for p, q in zip(samples, samples[1:])]
|
||
assert samples[0] == A and samples[-1] == C and max(steps) <= terrain.SAMPLE_STEP_M
|