Files
Aislo/resources/tester/test_133_1_analysis_terrain.py
T
eomsangdonandClaude Opus 5.5 11c75f022a refactor(767-1): 안 쓰는 지면 필터 · 서피스 지움 — 필터 넷(CSF · 격자 최저 · PMF · RANSAC) · 표현 셋(TIN + Taubin 평활 · NURBS · meshfree) · 수치지형도 비교 방식 다섯 · 안 부르는 길 정리
- 필터 = 업체 분류 · 점진 TIN · 둘의 다듬기 판만 · 분석 허용 목록 = Ground 등록부(available_filters) 한 벌
- 표현 = DTM · implicit 만 · 평활 = DTM 만 · config 기본 목록 · PRECOMPUTE · 설정 사전 맞춤(설정 지문 바뀜 — 768 에서 다시 지음)
- 등고선 = 격자 · 굳힌 격자만(삼각망 굽기 · 외곽 맞춤 · implicit RBF 폴백 걷음) · 표본기 · 경로 찾기 tin · nurbs · meshfree 갈래 걷음
- 수치지형도 = 라플라스만(TIN 둘 · TPS · ANUDEM형 · 다중해상도 걷음) · B04 단추 하나
- 안 부르는 길 — B04 JSON 점군 GET · ground-stats GET + TS · B07 옛 계획평면도(라이다) 짓기 · lidar_source · LIDAR_ID · plan_lidar 종류 이름 · SOURCE_FILTER_LABELS · MODEL_METHODS(py) · MESH_* 설정
- 옛 manifest 의 지운 갈래 줄은 DB 로 되살리지 않음 · 옛 csf 행은 미리보기 · 등고선 그대로 열림 · 지운 이름 요청 = 400
- 낡은 글 — Ground · Pipeline 머리 · WF1 「없으면 csf」 · B07 음영 기본 「csf」 → 빈 값 · 등고선 폴백 「csf」 → 404 · B04 3D 보기 처음값 · B05 Schema · B06 Prebuild · 씨닝 설명
- 시험 지움 2 — test_b04_contour_raster · test_b04_contour_equivalence(지운 삼각망 굽기 · 없는 프로젝트를 봐 건너뛰던 것)
- 시험 덜어냄 — test_b04_ground_filters CSF · 격자 최저 네 경우(지운 필터)
- 시험 고침 — 허용 목록 · 형 검사에 걸린 csf · tin · grid_min_z · meshfree · tin_sheet 견본을 ptd · implicit · laplace 로(54_22 · 238_1 · 443 · 342_2 · 346_3 · 351_1 · 442_2 · 271 + helper · 344_1 · 133_1 · 343_2 · 637 · 754_1 · 763_1 깨진 tin → 깨진 implicit · 763_3 · b05 둘 · b06 · sheet_surface TIN(도엽선) 경우 지움) · 이름만 바꿈(273_2 · 343_1 · 343_3 · 624_1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:35:12 +09:00

174 lines
7.0 KiB
Python

"""PLAN 133-1 — B05 계획노선 편집 분석 지형(라이다 · 수치지형도) 고르기.
라이다 있는 프로젝트 → 두 지형 다 고름 · sheet 고르면 dtm_sheet 로 표고 · 라이다 없는 프로젝트 →
고르기 안 보임(지형 하나) · 허용 목록 밖 거절 · 확정 params 는 안 바뀜.
"""
from __future__ import annotations
import asyncio
from contextlib import asynccontextmanager
from pathlib import Path
import numpy as np
import pytest
from pydantic import ValidationError
from B05_Profile import B05_Profile_Router_Replan as replan
from B05_Profile import B05_Profile_Router_Terrain as terrain_router
from common_util.common_util_surface_confirmation import (
SHEET_ANALYSIS_FILTER,
analysis_terrain_params,
analysis_terrain_sources,
)
from common_util.common_util_surface_sampler import build_surface_sampler, check_surface_keys
ROOT = Path(__file__).resolve().parents[2]
LAS_PARAMS = {"source_filter": "ptd", "method": "dtm", "smooth": True, "contour_interval_m": 1.0}
SHEET_PARAMS = {
"source_filter": SHEET_ANALYSIS_FILTER,
"method": "dtm",
"smooth": True,
"contour_interval_m": 1.0,
}
LAS_Z = 100.0
SHEET_Z = 250.0
def _dtm(path: Path, z: float) -> None:
x = np.arange(0.0, 11.0)
y = np.arange(0.0, 11.0)
np.savez(
path,
x=x,
y=y,
z=np.full((len(y), len(x)), z),
valid_mask=np.ones((len(y), len(x)), dtype=bool),
)
def _project(tmp_path: Path, *, las: bool, sheet: bool) -> Path:
models = tmp_path / "B04_PreProcess" / "models"
models.mkdir(parents=True)
if las:
_dtm(models / "dtm_ptd_smooth.npz", LAS_Z)
if sheet: # B04 처럼 원본 · 평활 둘 다(평활 쪽을 읽음)
_dtm(models / f"dtm_{SHEET_ANALYSIS_FILTER}.npz", SHEET_Z - 1.0)
_dtm(models / f"dtm_{SHEET_ANALYSIS_FILTER}_smooth.npz", SHEET_Z)
return tmp_path
def _patch_db(monkeypatch, project_root: Path, params: dict) -> None:
class _Pool:
@asynccontextmanager
async def acquire(self):
yield object()
async def stored(_connection, _project_id):
return "x"
async def confirmed(_connection, _project_id):
return dict(params)
monkeypatch.setattr(terrain_router, "get_db_pool", lambda: _Pool())
monkeypatch.setattr(terrain_router, "get_project_storage_relative_path", stored)
monkeypatch.setattr(terrain_router, "get_surface_confirmation_params", confirmed)
monkeypatch.setattr(terrain_router, "resolve_stored_project_path", lambda _s: project_root)
async def no_model(_connection, _project_id, _params): # 격자 받을 자리(344-1)는 따로 시험
return None
monkeypatch.setattr(terrain_router, "_design_model", no_model)
PID = "1bb0f876-983f-4ca5-9cc7-f59ee1f77e0a"
def _elevations(terrain: str) -> dict:
request = terrain_router.ElevationRequest(points=[(5.0, 5.0)], terrain=terrain)
return asyncio.run(terrain_router.read_route_elevations(PID, request))
def test_라이다_있는_프로젝트_두_지형_다_고름(tmp_path, monkeypatch):
root = _project(tmp_path, las=True, sheet=True)
models = root / "B04_PreProcess" / "models"
assert analysis_terrain_sources(LAS_PARAMS, models) == ["las", "sheet"]
_patch_db(monkeypatch, root, LAS_PARAMS)
import B04_PreProcess.B04_PreProcess_Service as service
async def models_in_db(*args): # sheet 고르기 = DB 완료 모델도 있어야(505-2 5d)
params = {"source_filter": SHEET_ANALYSIS_FILTER}
return [{"id": 2, "status": "COMPLETE", "model_type": "DTM", "generation_params": params}]
monkeypatch.setattr(service, "list_surface_models", models_in_db)
answer = asyncio.run(terrain_router.read_terrain_sources(PID))
assert answer["sources"] == ["las", "sheet"]
def test_sheet_고르면_dtm_sheet_로_표고(tmp_path, monkeypatch):
root = _project(tmp_path, las=True, sheet=True)
_patch_db(monkeypatch, root, LAS_PARAMS)
assert _elevations("las")["z"] == [LAS_Z]
assert _elevations("sheet")["z"] == [SHEET_Z]
# 기본값 = 지금 설계 지형(확정 지표면 · 133-2)
request = terrain_router.ElevationRequest(points=[(5.0, 5.0)])
assert request.terrain is None
def test_확정_params_는_안_바뀜():
params = dict(LAS_PARAMS)
picked = analysis_terrain_params(params, "sheet")
assert picked["source_filter"] == SHEET_ANALYSIS_FILTER == "sheet_laplace"
assert picked["method"] == "dtm" and picked["smooth"] is True
assert params == LAS_PARAMS
assert analysis_terrain_params(params, "las") is params
def test_라이다_없는_프로젝트_고르기_안_보임(tmp_path, monkeypatch):
root = _project(tmp_path, las=False, sheet=True)
models = root / "B04_PreProcess" / "models"
assert analysis_terrain_sources(SHEET_PARAMS, models) == ["sheet"]
# 확정이 이미 수치지형도 — 어느 값을 받아도 같은 서피스
assert analysis_terrain_params(SHEET_PARAMS, "sheet") is SHEET_PARAMS
_patch_db(monkeypatch, root, SHEET_PARAMS)
assert asyncio.run(terrain_router.read_terrain_sources(PID))["sources"] == ["sheet"]
# 허용 목록에 sheet_* 가 들어가 LAS 없는 프로젝트 지반고가 읽힘(54-22 뒤 막혀 있던 길)
assert _elevations("las")["z"] == [SHEET_Z]
def test_수치지형도_서피스_없으면_라이다만(tmp_path):
root = _project(tmp_path, las=True, sheet=False)
assert analysis_terrain_sources(LAS_PARAMS, root / "B04_PreProcess" / "models") == ["las"]
@pytest.mark.parametrize("bad", ["lidar", "SHEET", "../x", ""])
def test_허용_목록_밖_거절(bad):
with pytest.raises(ValidationError):
terrain_router.ElevationRequest(points=[(0.0, 0.0)], terrain=bad)
with pytest.raises(ValidationError):
replan.RouteInitialRequest(terrain=bad)
@pytest.mark.parametrize("bad", ["sheet", "sheet_../x", "sheet_LAPLACE", "dtm_sheet_laplace"])
def test_sheet_필터도_허용_목록_검사(bad, tmp_path):
with pytest.raises(ValueError):
check_surface_keys(bad)
with pytest.raises(ValueError):
build_surface_sampler(tmp_path, bad, "dtm", True)
check_surface_keys("sheet_laplace")
def test_화면_고르기_배선():
page = ROOT / "B05_Profile"
chrome = (page / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text(encoding="utf-8")
main = (page / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
api = (page / "B05_Profile_Api_Replan.ts").read_text(encoding="utf-8")
initial_api = (page / "B05_Profile_Api_RouteInitial.ts").read_text(encoding="utf-8")
# 626-1 — 평면 도구줄 지형 칩(공용 · 고르기 = 확인창 → 설계 지형 바꿈) · 옛 이름 칩 · 추천 창 고르기는 걷음
assert 'data-slot="terrain-name"' not in chrome and "terrain: terrain.root" in chrome
assert "chrome.terrain.load(sources)" in main
assert "analysisTerrain" not in api and "analysisTerrain" not in initial_api
assert "body: JSON.stringify(request)," in initial_api
replan_fn = api.split("export async function replanRoute")[1].split("export ")[0]
assert "terrain" not in replan_fn