- POST /plan/lidar(LAS · LAZ + .prj · 예정노선만 · 다시 올리면 갈음) → 뒤에서 지형(진행 = /plan/progress lidar) · GET /plan/lidar - 자리 = D 의 B04_PreProcess/lidar/(안은 B 와 같은 꼴) · LAS 좌표계 다르면 x · y 옮겨 적음 · DTM = surface_models 행(높이 격자) - terrain=lidar 급경사 · 음영 · 0차곡 = 올린 DTM · 범위 밖 = 빈칸(수치지형도로 안 메움 · 결정 A⑥) - B04 run_surface_analysis 에 maps 인자 덧(기본 True = B 그대로 · False = 지도 · GIS 받기 · 도엽 서피스 건너뜀) - 시험 test_624_4_plan_lidar 4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
5.8 KiB
Python
129 lines
5.8 KiB
Python
"""PLAN 624-4 — D 예정노선 라이다: LAS 올리기 → B04 엔진 지형 → `terrain=lidar` 겹(요구 530 · 531).
|
|
|
|
D 몫 길 = `D03_PlanAnalysis_Lidar` · B04 엔진은 `maps=False` 인자만 덧(기본 = B 그대로).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from D03_PlanAnalysis import D03_PlanAnalysis_Layers as layers
|
|
from D03_PlanAnalysis import D03_PlanAnalysis_Lidar as lidar
|
|
from D03_PlanAnalysis import D03_PlanAnalysis_TerrainSource as source
|
|
|
|
D_EPSG, LAS_EPSG = 5187, 5176 # D = GRS80 동부 · 업체 LAS = 베셀 동부(검증 용화와 같음)
|
|
|
|
|
|
def _write_las(path: Path, epsg: int | None, x0: float, y0: float) -> None:
|
|
"""두 덩이(가운데 빈 땅) 지면점 LAS — 비탈 0.3."""
|
|
import laspy
|
|
from pyproj import CRS
|
|
|
|
rng = np.random.default_rng(7)
|
|
xs, ys = [], []
|
|
for dx in (0.0, 160.0):
|
|
xs.append(rng.uniform(x0 + dx, x0 + dx + 60, 6000))
|
|
ys.append(rng.uniform(y0, y0 + 60, 6000))
|
|
x, y = np.concatenate(xs), np.concatenate(ys)
|
|
header = laspy.LasHeader(point_format=3, version="1.2")
|
|
header.offsets = [round(x0), round(y0), 0]
|
|
header.scales = [0.01, 0.01, 0.01]
|
|
if epsg is not None:
|
|
header.add_crs(CRS.from_epsg(epsg))
|
|
data = laspy.LasData(header)
|
|
data.x, data.y, data.z = x, y, 500 + 0.3 * (x - x0)
|
|
data.classification = np.full(len(x), 2, np.uint8)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
data.write(path)
|
|
|
|
|
|
def _built(tmp_path: Path, monkeypatch, epsg: int | None = LAS_EPSG) -> dict: # type: ignore[no-untyped-def]
|
|
import B04_PreProcess.B04_PreProcess_Engine as engine
|
|
|
|
called: list[int] = []
|
|
monkeypatch.setattr(engine, "download_geodata", lambda *a, **k: called.append(1))
|
|
_write_las(lidar.lidar_root(tmp_path) / lidar.UPLOAD / "t.las", epsg, 208300.0, 367500.0)
|
|
got = lidar.build_lidar(tmp_path, D_EPSG, lambda *_: None)
|
|
assert called == [] # 지도 · GIS 받기 안 함
|
|
return got
|
|
|
|
|
|
def test_이름_고르기() -> None:
|
|
lidar.check_names(["a.las", "a.prj", "b.LAZ"])
|
|
for names in (["a.prj"], ["a.las", "a.txt"], ["a.las", "A.las"]):
|
|
with pytest.raises(HTTPException) as caught:
|
|
lidar.check_names(names)
|
|
assert caught.value.status_code == 400
|
|
|
|
|
|
def test_올리기_뒤_라이다_지형_B_꼴(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
got = _built(tmp_path, monkeypatch)
|
|
meta = got["meta"]
|
|
dem = lidar.lidar_dem(tmp_path)
|
|
assert dem is not None and dem.name == f"dtm_{meta['source_filter']}.npz"
|
|
assert dem.parent == lidar.lidar_root(tmp_path) / "B04_PreProcess" / "models"
|
|
assert meta["files"] == ["t.las"] and meta["point_count"] == 12000
|
|
assert not (lidar.lidar_root(tmp_path) / lidar.UPLOAD).exists()
|
|
# surface_models 행 경로 = 프로젝트 뿌리 기준
|
|
from B04_PreProcess.B04_PreProcess_Repository import _validate_stage_path
|
|
|
|
assert any(m["model_file_path"] == meta["model_file"] for m in got["models"])
|
|
assert all(_validate_stage_path(m["model_file_path"]) for m in got["models"])
|
|
# 좌표계 옮김 — 베셀 → GRS80 북 +10 만 m 언저리
|
|
from pyproj import Transformer
|
|
|
|
want = Transformer.from_crs(LAS_EPSG, D_EPSG, always_xy=True).transform(208300.0, 367500.0)
|
|
assert abs(meta["bounds"]["x_min"] - want[0]) < 1.0
|
|
assert abs(meta["bounds"]["y_min"] - want[1]) < 1.0
|
|
# 다시 올리면 갈음
|
|
_write_las(lidar.lidar_root(tmp_path) / lidar.UPLOAD / "u.las", None, 208300.0, 467500.0)
|
|
again = lidar.build_lidar(tmp_path, D_EPSG, lambda *_: None)["meta"]
|
|
assert again["files"] == ["u.las"] and abs(again["bounds"]["y_min"] - 467500.0) < 1.0
|
|
|
|
|
|
def test_terrain_lidar_급경사_0차곡_범위_밖_빈칸(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
with pytest.raises(HTTPException) as caught: # 안 올림 = 404
|
|
source.need_dem(source.plan_frame(tmp_path, D_EPSG, (0, 0, 1, 1), "lidar"))
|
|
assert caught.value.status_code == 404
|
|
_built(tmp_path, monkeypatch)
|
|
frame = source.plan_frame(tmp_path, D_EPSG, (0, 0, 1, 1), "lidar")
|
|
npz = source.need_dem(frame)
|
|
assert frame.source == "lidar" and npz == lidar.lidar_dem(tmp_path)
|
|
value = layers.slope_layer(tmp_path, npz.parent, D_EPSG, "slope", npz)
|
|
counts = value["counts"]
|
|
assert sum(counts) > 0 and max(range(len(counts)), key=counts.__getitem__) in (29, 30)
|
|
# 두 덩이 사이 = 빈칸(수치지형도로 안 메움)
|
|
with np.load(npz) as dem:
|
|
valid = np.asarray(dem["valid_mask"], bool)
|
|
x = np.asarray(dem["x"], float)
|
|
x0 = float(x[0])
|
|
gap = (x > x0 + 90) & (x < x0 + 130)
|
|
assert gap.any() and not valid[:, gap].any()
|
|
work, light, full = source.zero_order_calls(frame, 50.0, 5000.0)
|
|
assert work(*light) is None # 저장본 없음
|
|
value = work(*full)
|
|
assert value["cell_m"] > 0 and work(*light) == value # 다음 = 저장본
|
|
|
|
|
|
def test_B04_기본은_지도_받기_그대로(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
"""B 프로젝트 전처리(기본 인자) = 지도 · GIS 받기 · 도엽 서피스 그대로 부름."""
|
|
import B04_PreProcess.B04_PreProcess_Engine as engine
|
|
import B04_PreProcess.B04_PreProcess_Engine_SheetSurface as sheet
|
|
|
|
calls: list[str] = []
|
|
monkeypatch.setattr(engine, "download_geodata", lambda *a, **k: calls.append("maps"))
|
|
monkeypatch.setattr(
|
|
sheet, "build_sheet_surface_from_route", lambda *a, **k: calls.append("sheet") or []
|
|
)
|
|
las = tmp_path / "B03_FileInput" / "input" / "las" / "t.las"
|
|
_write_las(las, None, 200000.0, 500000.0)
|
|
engine.run_surface_analysis(tmp_path, las, source_filters=None, methods=["dtm"])
|
|
assert calls == ["maps", "sheet"]
|
|
calls.clear()
|
|
engine.run_surface_analysis(tmp_path, las, source_filters=None, methods=["dtm"], maps=False)
|
|
assert calls == []
|