260705_2
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""B05 경로 설계 엔진 오케스트레이터.
|
||||
|
||||
경로점(BP/CP/EP/AP/FP)과 옵션을 받아 최적 경로를 계산하고, 폴리라인을
|
||||
GeoJSON으로 저장하며 DB 기록용 데이터(메타·렌더링 샘플·통계)를 준비한다.
|
||||
라우터에서 asyncio.to_thread로 호출한다.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from B05_wf2_Route.B05_wf2_Route_Engine_RidgeValley import solve_ridge_valley_route
|
||||
from B05_wf2_Route.B05_wf2_Route_Engine_Solver import solve_optimal_route
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
|
||||
_ROUTE_SUBDIR = Path("B05_wf2_Route") / "route"
|
||||
# route_points 테이블에 저장할 렌더링 샘플 최대 개수
|
||||
_MAX_RENDER_POINTS = 500
|
||||
|
||||
|
||||
def _route_geojson(polyline: list[list[float]]) -> dict[str, Any]:
|
||||
"""폴리라인을 3D LineString GeoJSON Feature로 변환한다."""
|
||||
return {
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [[round(x, 3), round(y, 3), round(z, 3)] for x, y, z in polyline],
|
||||
},
|
||||
"properties": {},
|
||||
}
|
||||
|
||||
|
||||
def _sample_render_points(
|
||||
polyline: list[list[float]], chainage_m: list[float], maximum: int
|
||||
) -> list[dict[str, Any]]:
|
||||
"""폴리라인을 최대 maximum개로 균등 샘플링해 렌더링용 포인트를 만든다."""
|
||||
n = len(polyline)
|
||||
if n == 0:
|
||||
return []
|
||||
if n <= maximum:
|
||||
indices = range(n)
|
||||
else:
|
||||
step = n / maximum
|
||||
indices = (int(i * step) for i in range(maximum))
|
||||
|
||||
points: list[dict[str, Any]] = []
|
||||
for seq, idx in enumerate(indices):
|
||||
idx = min(idx, n - 1)
|
||||
_, _, z = polyline[idx]
|
||||
# 국소 경사(%) — 직전 정점과의 차이
|
||||
slope_pct = 0.0
|
||||
if idx > 0:
|
||||
x0, y0, z0 = polyline[idx - 1]
|
||||
x1, y1, z1 = polyline[idx]
|
||||
h = ((x1 - x0) ** 2 + (y1 - y0) ** 2) ** 0.5
|
||||
if h > 1e-6:
|
||||
slope_pct = abs(z1 - z0) / h * 100.0
|
||||
points.append(
|
||||
{
|
||||
"chainage_m": round(chainage_m[idx], 3) if idx < len(chainage_m) else None,
|
||||
"elevation_m": round(z, 3),
|
||||
"slope_percent": round(slope_pct, 3),
|
||||
"sequence_num": seq,
|
||||
}
|
||||
)
|
||||
return points
|
||||
|
||||
|
||||
def run_route_design(
|
||||
project_root: Path,
|
||||
filter_key: str,
|
||||
method: str,
|
||||
smooth: bool,
|
||||
points_data: dict[str, Any],
|
||||
options: dict[str, Any],
|
||||
algorithm: str = "dijkstra",
|
||||
) -> dict[str, Any]:
|
||||
"""경로 탐색을 실행하고 GeoJSON 저장 + DB 기록용 데이터를 반환한다.
|
||||
|
||||
algorithm: "dijkstra"(격자 Dijkstra) 또는 "ridge_valley"(능선-계곡 정속경사).
|
||||
|
||||
반환 dict:
|
||||
- route_data_path: 저장한 GeoJSON의 프로젝트 상대 경로
|
||||
- solver_result: solver 원본 결과 (polyline, metrics, segments 등)
|
||||
- render_points: route_points 테이블 저장용 샘플
|
||||
- statistics: route_statistics 저장용 요약
|
||||
"""
|
||||
if algorithm == "ridge_valley":
|
||||
result = solve_ridge_valley_route(
|
||||
project_root, filter_key, smooth, points_data, options, method=method
|
||||
)
|
||||
else:
|
||||
result = solve_optimal_route(
|
||||
project_root, filter_key, smooth, points_data, options, method=method
|
||||
)
|
||||
polyline = result["polyline"]
|
||||
chainage_m = result["chainage_m"]
|
||||
|
||||
route_dir = project_root / _ROUTE_SUBDIR
|
||||
route_dir.mkdir(parents=True, exist_ok=True)
|
||||
geojson_path = route_dir / "route_main.geojson"
|
||||
atomic_write_json(geojson_path, _route_geojson(polyline))
|
||||
|
||||
# 통계 요약 (solver 메트릭에서 파생)
|
||||
metrics = result["metrics"]
|
||||
statistics = {
|
||||
"min_slope": 0.0,
|
||||
"max_slope": metrics.get("max_grade_pct"),
|
||||
"mean_slope": metrics.get("avg_grade_pct"),
|
||||
"cost_score": None,
|
||||
}
|
||||
|
||||
return {
|
||||
"route_data_path": geojson_path.relative_to(project_root).as_posix(),
|
||||
"solver_result": result,
|
||||
"render_points": _sample_render_points(polyline, chainage_m, _MAX_RENDER_POINTS),
|
||||
"statistics": statistics,
|
||||
"grade_percent": [seg.get("max_grade_pct") for seg in result.get("segments", [])],
|
||||
"constraints": result.get("conditions_snapshot", {}),
|
||||
"algorithm_params": options.get("weights") or {},
|
||||
}
|
||||
Reference in New Issue
Block a user