Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
"""D 노선분석 GIS 겹 「지금」 묶음(PLAN 581-1) — 계류 · 도로 띠 합침 · 넓이 · 보호구역 잘라 넓이 ·
|
||||
종류 하나 실패 = 그 종류만 빠짐 · 모두 실패 = 오류 · 덮으면 다시 안 받음 · 음영기복 저장본 따로 ·
|
||||
도엽 건물 · 물 면 코드.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_581_1_plan_gis_layers.py -q
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from shapely.geometry import LineString
|
||||
|
||||
from B04_PreProcess.B04_PreProcess_Engine_SheetParser import SHEET_LAYERS
|
||||
from common_util.common_util_crs import tm_to_lonlat
|
||||
from D03_PlanAnalysis import D03_PlanAnalysis_Layers as layers
|
||||
|
||||
EPSG = 5186
|
||||
BOX = (230000.0, 450000.0, 232000.0, 451000.0) # 2 × 1 km
|
||||
|
||||
|
||||
def test_sheet_codes() -> None:
|
||||
assert SHEET_LAYERS["건물"] == ("N3A", "B0010000")
|
||||
assert SHEET_LAYERS["실폭하천"] == ("N3A", "E0010001")
|
||||
assert {SHEET_LAYERS["수계면E003"][1], SHEET_LAYERS["수계면E005"][1]} == {
|
||||
"E0032111",
|
||||
"E0052114",
|
||||
}
|
||||
|
||||
|
||||
# ── 띠 ──
|
||||
|
||||
|
||||
def test_band_one_line_area() -> None:
|
||||
line = LineString([(230500.0, 450500.0), (231500.0, 450500.0)])
|
||||
band = layers.band_union([line], 100.0, BOX)
|
||||
assert band.area == pytest.approx(2 * 100 * 1000 + math.pi * 100**2, rel=0.01)
|
||||
|
||||
|
||||
def test_band_overlap_merged_and_clipped() -> None:
|
||||
a = LineString([(230500.0, 450500.0), (231500.0, 450500.0)])
|
||||
twin = layers.band_union([a, a], 100.0, BOX)
|
||||
assert twin.area == pytest.approx(layers.band_union([a], 100.0, BOX).area) # 겹친 곳 한 번
|
||||
edge = LineString([(230000.0, 450500.0), (232000.0, 450500.0)])
|
||||
assert layers.band_union([edge], 100.0, BOX).area == pytest.approx(2000 * 200, rel=1e-6)
|
||||
assert layers.band_union([], 100.0, BOX) is None
|
||||
far = LineString([(240000.0, 460000.0), (240100.0, 460000.0)])
|
||||
assert layers.band_union([far], 100.0, BOX) is None
|
||||
|
||||
|
||||
def _line_feature(points: list[tuple[float, float]], **props) -> dict:
|
||||
coords = [list(tm_to_lonlat(x, y, EPSG)) for x, y in points]
|
||||
return {
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "LineString", "coordinates": coords},
|
||||
"properties": props,
|
||||
}
|
||||
|
||||
|
||||
def test_band_layer_filters_and_caches(tmp_path, monkeypatch) -> None:
|
||||
processed = tmp_path / "B04_PreProcess" / "processed"
|
||||
processed.mkdir(parents=True)
|
||||
row = [(230500.0, 450500.0), (231500.0, 450500.0)]
|
||||
streams = [
|
||||
_line_feature(row, 구분="지방하천"),
|
||||
_line_feature([(230200.0, 450100.0), (230200.0, 450900.0)], 구분="세류"),
|
||||
]
|
||||
roads = [_line_feature(row, 도로구분="지방도"), _line_feature(row, 도로구분="소로")]
|
||||
for key, feats in (("하천중심선", streams), ("도로중심선", roads)):
|
||||
body = {"type": "FeatureCollection", "features": feats}
|
||||
(processed / f"도엽_{key}.geojson").write_text(
|
||||
json.dumps(body, ensure_ascii=False), "utf-8"
|
||||
)
|
||||
real = layers.band_union
|
||||
counted = []
|
||||
monkeypatch.setattr(layers, "band_union", lambda *a: counted.append(1) or real(*a))
|
||||
value = layers.band_layer(tmp_path, EPSG, BOX)
|
||||
assert value["stream"]["lines"] == 1 and value["road"]["lines"] == 1 # 세류 · 소로 뺌
|
||||
assert value["stream"]["area_m2"] == pytest.approx(2e5 + math.pi * 1e4, rel=0.02)
|
||||
road = 2 * 300 * 1000 + math.pi * 300**2 # 둥근 끝 · 범위 안
|
||||
assert value["road"]["area_m2"] == pytest.approx(road, rel=0.01)
|
||||
assert value["road"]["ratio"] == pytest.approx(road / 2e6, abs=0.005)
|
||||
assert len(value["stream"]["collection"]["features"]) == 1
|
||||
again = layers.band_layer(tmp_path, EPSG, BOX)
|
||||
assert again == json.loads(json.dumps(value)) and len(counted) == 2 # 저장본
|
||||
|
||||
|
||||
# ── 보호 · 규제 구역 ──
|
||||
|
||||
|
||||
def _square(x0: float, y0: float, side: float) -> dict:
|
||||
ring = [(x0, y0), (x0 + side, y0), (x0 + side, y0 + side), (x0, y0 + side), (x0, y0)]
|
||||
return {"type": "Polygon", "coordinates": [[list(tm_to_lonlat(x, y, EPSG)) for x, y in ring]]}
|
||||
|
||||
|
||||
def test_clip_zones_area_and_names() -> None:
|
||||
found = {
|
||||
"forest": [
|
||||
{
|
||||
"id": "a",
|
||||
"geometry": _square(231800.0, 450800.0, 400.0),
|
||||
"properties": {"alias": "보안림"},
|
||||
},
|
||||
{
|
||||
"id": "a",
|
||||
"geometry": _square(231800.0, 450800.0, 400.0),
|
||||
"properties": {},
|
||||
}, # 조각 겹침
|
||||
{
|
||||
"id": "b",
|
||||
"geometry": _square(240000.0, 460000.0, 100.0),
|
||||
"properties": {},
|
||||
}, # 범위 밖
|
||||
],
|
||||
"park": [
|
||||
{
|
||||
"id": "c",
|
||||
"geometry": _square(230100.0, 450100.0, 100.0),
|
||||
"properties": {"park_name": "속리산"},
|
||||
}
|
||||
],
|
||||
}
|
||||
features, summary = layers.clip_zones(found, BOX, EPSG)
|
||||
assert summary["forest"]["count"] == 1
|
||||
assert summary["forest"]["area_m2"] == pytest.approx(200 * 200, rel=0.01) # 귀퉁이만 남음
|
||||
assert summary["park"]["area_m2"] == pytest.approx(100 * 100, rel=0.01)
|
||||
assert summary["water"] == {"label": "상수원보호구역", "count": 0, "area_m2": 0.0}
|
||||
assert [f["properties"]["name"] for f in features] == ["보안림", "속리산"]
|
||||
assert {f["properties"]["kind"] for f in features} == {"forest", "park"}
|
||||
|
||||
|
||||
class FakeZoneWfs:
|
||||
"""겹 이름마다 정사각형 하나 · `fail` 에 든 겹 이름은 실패(파일 없음)."""
|
||||
|
||||
def __init__(self, fail: frozenset[str] = frozenset()) -> None:
|
||||
self.fail, self.calls = fail, []
|
||||
|
||||
def __call__(self, typename, filename, bounds, output_dir) -> None:
|
||||
self.calls.append(typename)
|
||||
if typename in self.fail:
|
||||
return
|
||||
feats = [{"id": typename, "geometry": _square(230500.0, 450200.0, 300.0), "properties": {}}]
|
||||
body = {"type": "FeatureCollection", "features": feats}
|
||||
(Path(output_dir) / filename).write_text(json.dumps(body), "utf-8")
|
||||
|
||||
|
||||
ALL_NAMES = [name for _, _, names in layers.ZONE_KINDS for name in names]
|
||||
|
||||
|
||||
def test_zones_fetch_once_partial_failure(tmp_path, monkeypatch) -> None:
|
||||
assert len(layers.ZONE_KINDS) == 8 and len(ALL_NAMES) == 10
|
||||
fake = FakeZoneWfs(fail=frozenset({"lt_c_uf151"}))
|
||||
monkeypatch.setattr(layers, "request_vworld_wfs", fake)
|
||||
got = layers.ensure_zones(tmp_path, BOX, EPSG)
|
||||
assert (
|
||||
got["fetched"] and got["failed"] == ["forest"] and sorted(fake.calls) == sorted(ALL_NAMES)
|
||||
)
|
||||
saved = json.loads((tmp_path / layers.ZONE_FILE).read_text("utf-8"))
|
||||
assert saved["summary"]["forest"].get("failed") is True
|
||||
assert saved["summary"]["park"]["count"] == 3 # 자연공원 겹 셋
|
||||
assert len(saved["features"]) == 9
|
||||
ok = FakeZoneWfs()
|
||||
monkeypatch.setattr(layers, "request_vworld_wfs", ok)
|
||||
assert layers.ensure_zones(tmp_path, BOX, EPSG)["fetched"] # 실패 종류 있으면 다시
|
||||
assert not layers.ensure_zones(tmp_path, (230500.0, 450100.0, 231000.0, 450900.0), EPSG)[
|
||||
"fetched"
|
||||
]
|
||||
assert len(ok.calls) == 10 # 덮으면 다시 안 받음
|
||||
|
||||
|
||||
def test_zones_all_fail(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setattr(layers, "request_vworld_wfs", FakeZoneWfs(fail=frozenset(ALL_NAMES)))
|
||||
with pytest.raises(RuntimeError):
|
||||
layers.ensure_zones(tmp_path, BOX, EPSG)
|
||||
assert not (tmp_path / layers.ZONE_FILE).exists()
|
||||
|
||||
|
||||
# ── 음영기복 ──
|
||||
|
||||
|
||||
def test_hillshade_saved_apart_from_slope(tmp_path) -> None:
|
||||
models = tmp_path / "B04_PreProcess" / "models_plan"
|
||||
models.mkdir(parents=True)
|
||||
n = 30
|
||||
z = np.tile(np.arange(n) * 1.0, (n, 1))
|
||||
x = 200000.0 + np.arange(n) * 2.0
|
||||
np.savez(
|
||||
models / "dtm_sheet_laplace.npz",
|
||||
x=x,
|
||||
y=x + 250000.0,
|
||||
z=z,
|
||||
valid_mask=np.ones(z.shape, bool),
|
||||
resolution=[2.0],
|
||||
)
|
||||
shade = layers.slope_layer(tmp_path, models, EPSG, "hillshade")
|
||||
slope = layers.slope_layer(tmp_path, models, EPSG, "slope")
|
||||
assert shade["image"].startswith("data:image/png;base64,") and shade["bands"] == []
|
||||
assert slope["bands"] and shade["box_m"] == slope["box_m"]
|
||||
assert layers.slope_layer(tmp_path, models, EPSG, "hillshade") == shade
|
||||
Reference in New Issue
Block a user