ruff 글로브를 `resources/tester/*.py` 로 넓게 잡아 내 작업과 무관한 시험 파일까지 서식이 바뀌었음. 다른 창이 그 파일을 만지면 충돌만 남으므로 되돌림. 내가 실제로 고친 다섯(열쇠 장부·Z01 둘·표 읽기·못 읽은 표)만 남김. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsGw1Dz9HmhuAxGisxmDPF
133 lines
4.8 KiB
Python
133 lines
4.8 KiB
Python
"""절단 여유 30m → 3m 로 낮췄을 때 끝단 지반고가 튀지 않는지 (2026-09-04 사용자 지시).
|
|
|
|
용화_LAS 프로젝트의 확정 지표면으로 실측한다. 판정은 「3m 로 남긴 양 끝 20m 구간의
|
|
지반고 변화율이 노선 안쪽 구간과 같은 수준인가」 — 가장자리 점 밀도가 떨어져 값이
|
|
못 미더우면 여기서 눈에 띄게 튄다.
|
|
"""
|
|
|
|
import asyncio
|
|
import math
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.append(str(ROOT))
|
|
|
|
PROJECT_ID = "5cff3920-a181-4a3d-bec0-e0ac4082b75d" # 용화_LAS
|
|
|
|
|
|
def _project_root_and_surface():
|
|
import aiomysql
|
|
|
|
from config.config_db import DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USER
|
|
from common_util.common_util_storage import resolve_stored_project_path
|
|
|
|
async def run():
|
|
conn = await aiomysql.connect(
|
|
host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD,
|
|
db=DB_NAME, charset="utf8mb4",
|
|
)
|
|
try:
|
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
|
await cursor.execute(
|
|
"SELECT storage_path FROM projects WHERE id = %s", (PROJECT_ID,)
|
|
)
|
|
row = await cursor.fetchone()
|
|
from common_util.common_util_surface_confirmation import get_surface_confirmation_params
|
|
|
|
params = await get_surface_confirmation_params(conn, PROJECT_ID)
|
|
return row, params
|
|
finally:
|
|
conn.close()
|
|
|
|
row, params = asyncio.run(run())
|
|
if not row:
|
|
pytest.skip("용화_LAS 프로젝트가 없습니다.")
|
|
return Path(resolve_stored_project_path(row["storage_path"])), params
|
|
|
|
|
|
def _sample_z(sampler, points):
|
|
import numpy as np
|
|
|
|
z, valid = sampler.sample_xy(np.asarray(points, dtype=np.float64))
|
|
return np.asarray(z, dtype=float), np.asarray(valid, dtype=bool)
|
|
|
|
|
|
def _slopes(sampler, points):
|
|
"""이웃 정점 사이 지반고 변화율(m/m) 목록."""
|
|
z, valid = _sample_z(sampler, points)
|
|
out = []
|
|
for i in range(1, len(points)):
|
|
if not (valid[i] and valid[i - 1]):
|
|
continue
|
|
d = math.dist(points[i - 1], points[i])
|
|
if d > 0.5:
|
|
out.append(abs(z[i] - z[i - 1]) / d)
|
|
return out
|
|
|
|
|
|
def _head_tail_within(points, metres):
|
|
"""양 끝에서 `metres` 안에 드는 정점만."""
|
|
acc = [0.0]
|
|
for i in range(1, len(points)):
|
|
acc.append(acc[-1] + math.dist(points[i - 1], points[i]))
|
|
total = acc[-1]
|
|
head = [p for p, a in zip(points, acc) if a <= metres]
|
|
tail = [p for p, a in zip(points, acc) if total - a <= metres]
|
|
return head, tail, total
|
|
|
|
|
|
def test_edge_trim_3m_ends_are_not_spiky():
|
|
from common_util.common_util_route_geometry import (
|
|
find_planned_route_file,
|
|
read_planned_route,
|
|
trim_route_to_surface,
|
|
)
|
|
from common_util.common_util_surface_sampler import build_surface_sampler
|
|
from B04_PreProcess.B04_PreProcess_Engine_Extent import project_epsg_from_prj
|
|
|
|
project_root, params = _project_root_and_surface()
|
|
route_file = find_planned_route_file(project_root / "B03_FileInput" / "input")
|
|
if route_file is None:
|
|
pytest.skip("계획노선 파일이 없습니다.")
|
|
planned = read_planned_route(route_file)
|
|
points = [(float(v.x), float(v.y)) for v in planned.vertices]
|
|
target_crs = project_epsg_from_prj(project_root)
|
|
source_crs = planned.crs_input or target_crs
|
|
if source_crs.upper() != target_crs.upper():
|
|
from pyproj import Transformer
|
|
|
|
transformer = Transformer.from_crs(source_crs, target_crs, always_xy=True)
|
|
points = [transformer.transform(x, y) for x, y in points]
|
|
|
|
sampler = build_surface_sampler(
|
|
project_root / "B04_PreProcess" / "models",
|
|
str(params["source_filter"]),
|
|
str(params["method"]),
|
|
bool(params["smooth"]),
|
|
)
|
|
|
|
trimmed_30 = trim_route_to_surface(points, sampler, 30.0)
|
|
trimmed_3 = trim_route_to_surface(points, sampler, 3.0)
|
|
assert len(trimmed_3) >= 2
|
|
head, tail, total_3 = _head_tail_within(trimmed_3, 20.0)
|
|
_, _, total_30 = _head_tail_within(trimmed_30, 20.0)
|
|
print(f"\n연장: 30m 트림 {total_30:.1f}m / 3m 트림 {total_3:.1f}m (차이 {total_3 - total_30:.1f}m)")
|
|
|
|
inner = _slopes(sampler, trimmed_3[len(trimmed_3) // 4 : 3 * len(trimmed_3) // 4])
|
|
ends = _slopes(sampler, head) + _slopes(sampler, tail)
|
|
assert inner and ends
|
|
inner_max = max(inner)
|
|
ends_max = max(ends)
|
|
print(f"지반고 변화율 최대: 안쪽 {inner_max:.3f} / 양 끝 20m {ends_max:.3f}")
|
|
# 끝단이 안쪽보다 크게 튀지 않아야 한다(가장자리 밀도 저하 확인).
|
|
assert ends_max <= max(inner_max * 1.5, inner_max + 0.05)
|
|
|
|
|
|
def test_config_default_is_3m():
|
|
from config.config_system_terrain import SURFACE_ROUTE_EDGE_TRIM_M
|
|
|
|
assert SURFACE_ROUTE_EDGE_TRIM_M == 3.0
|