feat(637-3): B05 지형 칩 옆 [LAS 올리기] · 진행 서클 · 확인창 → 설계 지형 바꿈 · 공용 올리기 부품 map_lidar_upload(예정노선 같은 단추 · 흐름) · 갈음 때 안 지은 라이다 필터 파일 · 행 지움(요구 546)

- 공용 A00_Common/map/map_lidar_upload — 파일 고름 · POST multipart · 1 초 묻기 · 서클 · 끝난 뒤 일(주소 · 진행 묻기만 인자)
- 예정노선 UI_LidarUpload = 부품 부르기(화면 · 동작 그대로) · 안 쓰는 uploadPlanLidar 걷음
- B05 UI_RouteEdit_Terrain — 칩 · 단추 · 서클 한 칸 · 끝나면 확인창 「설계 지형을 라이다로 바꿈?」(이미 라이다 = force 다시 셈) · covered 거짓 = 바꿈 막고 알림 · 들어올 때 도는 일 이어 봄
- common_util_lidar_upload.drop_stale_filters — 갈음 뒤 안 지은 라이다 필터(옛 LAS) 모델 · 등고선 · 마스크 · 지면점 · manifest 줄 지움 · save_models drop = 그 행만(확정 행 남김)
- 시험 test_637_lidar(필터 지움 · 행 지움 · B05 화면) · test_624_4b(올리기 = 공용 부품으로 옮김)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 11:53:48 +09:00
co-authored by Claude Opus 5.5
parent d9cc2144f2
commit b3dcb73784
9 changed files with 409 additions and 104 deletions
+59 -3
View File
@@ -4,9 +4,12 @@
(좌표계 다르면 x · y 옮겨 적음)
- 받기 고리 `receive_uploads` · 입력 자리 갈음 `stage_las` · 짓기 `build_surface`
(B04 엔진 `run_surface_analysis(maps=False)` — 지도 · GIS 받기 · 도엽 서피스 안 함)
- 갈음 뒤 안 지은 라이다 필터 지움 `drop_stale_filters`
(옛 LAS 산출 · 뒤에 고르면 새 LAS 로 다시 지음)
- 뒤 일 `start_job` · 진행 기록 `note_job` · `saved_job`(서버 다시 켜짐 = 「끊김」)
- DB `save_models` — `surface_models` 넣기/고치기만
(다른 행 안 지움 · `save_surface_analysis_to_db` 안 씀)
(다른 행 안 지움 · `drop` = 지운 필터 파일 행만 · 확정 행 남김 ·
`save_surface_analysis_to_db` 안 씀)
부르는 쪽 = `D03_PlanAnalysis_Lidar`(자리 `B04_PreProcess/lidar/`) ·
`B05_Profile_Router_Lidar`(프로젝트 뿌리).
@@ -143,6 +146,45 @@ def build_surface(
)
def drop_stale_filters(base: Path, built: list[str]) -> list[str]:
"""갈음 뒤 — 이번에 안 지은 라이다 필터의 옛 LAS 산출(모델 · 미리보기 · 등고선 · 마스크 ·
지면점 · 점 미리보기 · manifest 줄)을 지움 → 지운 모델 파일(`base` 기준 상대경로).
B04 엔진은 LAS 가 바뀌면 고른 필터만 다시 지음 — 남은 다른 필터 파일은 옛 LAS 것.
지우면 뒤에 그 필터를 고를 때 캐시가 없어 새 LAS 로 다시 지음.
수치지형도(`sheet_*`)는 필터 밖."""
from B04_PreProcess.B04_PreProcess_Engine_Ground import available_filters
from B04_PreProcess.B04_PreProcess_Engine_ModelBuild import BUILDERS
models = base / "B04_PreProcess" / "models"
processed = base / "B04_PreProcess" / "processed"
stale = [key for key in available_filters() if key not in built]
gone: list[str] = []
for key in stale:
paths = [
*(path for method in BUILDERS for path in models.glob(f"{method}_{key}.*")),
*(path for method in BUILDERS for path in models.glob(f"{method}_{key}_*")),
*models.glob(f"contour_{key}_*"),
*(processed / name for name in (f"mask_{key}.npy", f"ground_points_{key}.npz")),
processed / f"preview_points_{key}.bin",
]
for path in paths:
if path.is_file():
path.unlink()
if path.parent == models and path.suffix == ".npz":
gone.append(path.relative_to(base).as_posix())
manifest = models / "manifest.json"
try:
data = json.loads(manifest.read_text(encoding="utf-8"))
except (OSError, ValueError):
data = None
filters = data.get("source_filters") if isinstance(data, dict) else None
if isinstance(filters, dict) and any(key in filters for key in stale):
data["source_filters"] = {k: v for k, v in filters.items() if k not in stale}
atomic_write_json(manifest, data)
return sorted(gone)
# ── 뒤 일 · 진행 기록 ─────────────────────────────────────────────────────────
IDLE = {"running": False, "percent": 100, "message": None, "error": None, "interrupted": False}
@@ -214,12 +256,19 @@ def start_job(
async def save_models(
project_id: UUID, models: list[dict[str, Any]], epsg: int, *, keep: bool = False
project_id: UUID,
models: list[dict[str, Any]],
epsg: int,
*,
keep: bool = False,
drop: list[str] | None = None,
) -> None:
"""지형면 → `surface_models` 행(같은 파일 · 같은 지문이면 id 살림 · 다른 행 그대로).
`keep` = 같은 파일을 가리키는 옛 행은 지문이 달라도 id · 상태(확정) · 입력 연결을 살림
(LAS 갈음 — 노선 `surface_model_id` · 설계 확정이 끊기지 않게 · B05)."""
(LAS 갈음 — 노선 `surface_model_id` · 설계 확정이 끊기지 않게 · B05).
`drop` = 지운 모델 파일(`drop_stale_filters`)을 가리키는 행 지움(확정 행은 남김 ·
terrain_layers CASCADE · routes SET NULL)."""
from B04_PreProcess.B04_PreProcess_Repository import (
_existing_models,
_model_key,
@@ -259,6 +308,13 @@ async def save_models(
)
if status == "CONFIRMED": # 고쳐 쓰기가 COMPLETE 로 내린 확정을 되살림
await confirm_surface_model(connection, project_id, model_id)
if drop:
async with connection.cursor() as cursor:
await cursor.execute(
"DELETE FROM surface_models WHERE project_id = %s AND status <> 'CONFIRMED'"
" AND model_file_path IN (" + ", ".join(["%s"] * len(drop)) + ")",
(str(project_id), *drop),
)
await connection.commit()
except Exception:
await connection.rollback()