- [확인] 이 고른 지형으로 확정 모델 · stage 1 params 를 바꾼 뒤 재계산 체인을 돎(switch_design_terrain · B04 확정 표시도 따라감) · 체인 실패면 노선과 함께 되돌림 - 수치지형도로 갈 때 라이다 확정값을 stage 1 params 에 적어 둠 — 라이다로 되돌려 [확인]하면 원래 모델 · params - 지형만 바꿔도 [확인]이 다시 셈 · 고르기 안 보이면(라이다 없는 프로젝트) 지형 안 실음 · 고른 값 없으면 지금 설계 지형 · 상태줄에 설계 지형 표시 - 분석 통로 terrain 기본 = 지금 설계 지형(None) · terrain-sources 가 design 을 함께 냄 - B06 구조물 측점 추가가 노선 확정 때 남긴 sampling.json 이 지금 지형과 다르면 stage 1 params 로 뜸(B04 재확정 때도 옛 지형으로 뜨던 흠) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
188 lines
6.8 KiB
Python
188 lines
6.8 KiB
Python
"""WF1 지표면 확정 선택값의 기본값·DB 스냅샷 처리."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any, Literal
|
|
|
|
import aiomysql
|
|
|
|
from config.config_system import (
|
|
SHEET_SURFACE_DEFAULT_METHOD,
|
|
SURFACE_CONFIRM_DEFAULT_FILTER,
|
|
SURFACE_CONFIRM_DEFAULT_METHOD,
|
|
SURFACE_CONFIRM_DEFAULT_SMOOTH,
|
|
SURFACE_CONTOUR_INTERVAL_M,
|
|
)
|
|
|
|
#: B05 계획노선 편집 모달의 분석 지형 — las = 확정 지표면 그대로 · sheet = 수치지형도 서피스
|
|
AnalysisTerrain = Literal["las", "sheet"]
|
|
#: 수치지형도 분석 지형 — LAS 없는 프로젝트 자동 확정(WF1)과 같은 모델
|
|
SHEET_ANALYSIS_FILTER = f"sheet_{SHEET_SURFACE_DEFAULT_METHOD}"
|
|
#: 설계 지형을 수치지형도로 바꿀 때 적어 두는 라이다 확정값 — 라이다로 되돌릴 자리(PLAN 133-2)
|
|
LAS_STASH_KEY = "las_selection"
|
|
_SURFACE_KEYS = ("source_filter", "method", "smooth")
|
|
|
|
SURFACE_CONFIRM_PARAM_KEYS = (
|
|
"source_filter",
|
|
"method",
|
|
"smooth",
|
|
"contour_interval_m",
|
|
)
|
|
|
|
|
|
def surface_confirmation_defaults() -> dict[str, Any]:
|
|
"""현재 config에 설정된 지표면 자동 확정 기본값을 반환한다."""
|
|
return {
|
|
"source_filter": SURFACE_CONFIRM_DEFAULT_FILTER,
|
|
"method": SURFACE_CONFIRM_DEFAULT_METHOD,
|
|
"smooth": SURFACE_CONFIRM_DEFAULT_SMOOTH,
|
|
"contour_interval_m": SURFACE_CONTOUR_INTERVAL_M,
|
|
}
|
|
|
|
|
|
def _decode_params(value: Any) -> dict[str, Any]:
|
|
if isinstance(value, dict):
|
|
return dict(value)
|
|
if isinstance(value, str) and value:
|
|
decoded = json.loads(value)
|
|
return dict(decoded) if isinstance(decoded, dict) else {}
|
|
return {}
|
|
|
|
|
|
async def get_surface_confirmation_params(
|
|
connection: aiomysql.Connection,
|
|
project_id: str,
|
|
) -> dict[str, Any]:
|
|
"""stage 1 스냅샷을 우선하고, 없으면 config 기본값으로 보완한다."""
|
|
async with connection.cursor(aiomysql.DictCursor) as cursor:
|
|
await cursor.execute(
|
|
"""
|
|
SELECT params
|
|
FROM project_workflow_stages
|
|
WHERE project_id = %s AND stage_no = 1
|
|
""",
|
|
(project_id,),
|
|
)
|
|
row = await cursor.fetchone()
|
|
|
|
resolved = surface_confirmation_defaults()
|
|
params = _decode_params(row.get("params") if row else None)
|
|
for key in SURFACE_CONFIRM_PARAM_KEYS:
|
|
if key in params and params[key] is not None:
|
|
resolved[key] = params[key]
|
|
if isinstance(params.get(LAS_STASH_KEY), dict):
|
|
resolved[LAS_STASH_KEY] = params[LAS_STASH_KEY]
|
|
return resolved
|
|
|
|
|
|
def _is_sheet(params: dict[str, Any]) -> bool:
|
|
return str(params.get("source_filter") or "").startswith("sheet_")
|
|
|
|
|
|
def design_terrain(params: dict[str, Any]) -> AnalysisTerrain:
|
|
"""지금 설계 지형 — 확정 params 가 수치지형도면 sheet."""
|
|
return "sheet" if _is_sheet(params) else "las"
|
|
|
|
|
|
def analysis_terrain_params(
|
|
params: dict[str, Any], terrain: AnalysisTerrain | None
|
|
) -> dict[str, Any]:
|
|
"""고른 지형의 지표면 params — None · 지금 설계 지형과 같으면 확정 params 그대로.
|
|
|
|
설계 지형이 수치지형도여도 적어 둔 라이다 확정값(`LAS_STASH_KEY`)이 있으면 las 로 돌아감.
|
|
라이다 없는 프로젝트는 무엇을 받아도 확정 params 그대로.
|
|
"""
|
|
if terrain is None or terrain == design_terrain(params):
|
|
return params
|
|
if terrain == "sheet":
|
|
return {**params, "source_filter": SHEET_ANALYSIS_FILTER, "method": "dtm", "smooth": True}
|
|
stash = params.get(LAS_STASH_KEY)
|
|
if not isinstance(stash, dict) or not stash.get("source_filter"):
|
|
return params
|
|
return {**params, **{key: stash[key] for key in _SURFACE_KEYS if key in stash}}
|
|
|
|
|
|
def terrain_changes(params: dict[str, Any], target: dict[str, Any]) -> bool:
|
|
"""지표면(필터 · 표현 · 평활)이 바뀌는가."""
|
|
return any(params.get(key) != target.get(key) for key in _SURFACE_KEYS)
|
|
|
|
|
|
def analysis_terrain_sources(params: dict[str, Any], models_dir: Path) -> list[str]:
|
|
"""고를 수 있는 분석 지형 — 라이다 확정값(지금 것 · 적어 둔 것)과 수치지형도가 둘 다 있으면 둘."""
|
|
if _is_sheet(params):
|
|
return ["las", "sheet"] if isinstance(params.get(LAS_STASH_KEY), dict) else ["sheet"]
|
|
if (Path(models_dir) / f"dtm_{SHEET_ANALYSIS_FILTER}.npz").is_file():
|
|
return ["las", "sheet"]
|
|
return ["las"]
|
|
|
|
|
|
async def update_contour_interval_param(
|
|
connection: aiomysql.Connection,
|
|
project_id: str,
|
|
contour_interval_m: float,
|
|
) -> None:
|
|
"""stage 1 params의 등고선 간격만 갱신한다 (B05 재적용 영속화)."""
|
|
async with connection.cursor(aiomysql.DictCursor) as cursor:
|
|
await cursor.execute(
|
|
"""
|
|
SELECT params
|
|
FROM project_workflow_stages
|
|
WHERE project_id = %s AND stage_no = 1
|
|
FOR UPDATE
|
|
""",
|
|
(project_id,),
|
|
)
|
|
row = await cursor.fetchone()
|
|
if row is None:
|
|
raise LookupError("WF1 단계 상태를 찾을 수 없습니다.")
|
|
params = _decode_params(row.get("params") if row else None)
|
|
params["contour_interval_m"] = float(contour_interval_m)
|
|
await cursor.execute(
|
|
"""
|
|
UPDATE project_workflow_stages
|
|
SET params = %s
|
|
WHERE project_id = %s AND stage_no = 1
|
|
""",
|
|
(json.dumps(params, ensure_ascii=False), project_id),
|
|
)
|
|
|
|
|
|
async def merge_surface_confirmation_params(
|
|
connection: aiomysql.Connection,
|
|
project_id: str,
|
|
selection: dict[str, Any],
|
|
extra: dict[str, Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""기존 stage 1 params에 확정 선택값 전체를 병합 저장한다. `extra` 는 함께 적을 칸."""
|
|
normalized = {
|
|
"source_filter": str(selection["source_filter"]),
|
|
"method": str(selection["method"]),
|
|
"smooth": bool(selection["smooth"]),
|
|
"contour_interval_m": float(selection["contour_interval_m"]),
|
|
}
|
|
async with connection.cursor(aiomysql.DictCursor) as cursor:
|
|
await cursor.execute(
|
|
"""
|
|
SELECT params
|
|
FROM project_workflow_stages
|
|
WHERE project_id = %s AND stage_no = 1
|
|
FOR UPDATE
|
|
""",
|
|
(project_id,),
|
|
)
|
|
row = await cursor.fetchone()
|
|
if row is None:
|
|
raise LookupError("WF1 단계 상태를 찾을 수 없습니다.")
|
|
params = _decode_params(row.get("params") if row else None)
|
|
params.update(normalized)
|
|
params.update(extra or {})
|
|
await cursor.execute(
|
|
"""
|
|
UPDATE project_workflow_stages
|
|
SET params = %s
|
|
WHERE project_id = %s AND stage_no = 1
|
|
""",
|
|
(json.dumps(params, ensure_ascii=False), project_id),
|
|
)
|
|
return normalized
|