feat(762-2): 서피스 품질 지표 넷 — 튐 넓이 % · 1 m 등고선 꺾임 /km · 땅 점 빈 곳 % · 필터 차 % · B04 모델마다 표시

- 새 Engine_Quality — 599 정의 그대로(5 × 5 중간값 0.5 m · 2 m 다시 찍어 30° · 2 m 안 지면점 없음 · 짝 필터 |차| > 0.5 m)
- 파이프라인이 dtm · implicit 을 지을 때 셈해 models/quality_{필터}_{표현}.json 에 저장 · 모델 · 짝 모델 지문이 다를 때만 다시 셈
- GET /surface/models/{id}/quality · B04 지표면 분석 칸에 값 목록(부품 kv) · 문턱 판정 없음(사용자 협의)
- B04 필터 드롭다운 · 이름을 새 UI_Filter 로 옮김(UI_Page 679 → 661 줄) · 업체 분류 + 다듬기 · 점진 TIN + 다듬기
- 지킴 시험 test_762_1_refine — 덤불 혹 지움 · 능선 안 깎음 · 스위치 끔 = 지금 길 · 튐 지표

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-03 13:46:51 +09:00
co-authored by Claude Opus 5.5
parent c0f4c766b6
commit 6d0b50aee6
6 changed files with 447 additions and 23 deletions
+5 -23
View File
@@ -40,6 +40,7 @@ import {
type SurfacePointCloudSampleResponse,
type SurfaceStatusResponse,
} from "./B04_PreProcess_Api_Fetch";
import { buildSelectField, createQualityPanel, SOURCE_FILTERS } from "./B04_PreProcess_UI_Filter";
import { buildSurfaceGroup } from "./B04_PreProcess_UI_Group";
import { createSurfaceMapViewer } from "./B04_PreProcess_UI_MapViewer";
import { createSurfaceTerrainViewer } from "./B04_PreProcess_UI_TerrainViewer";
@@ -49,9 +50,6 @@ import "./B04_PreProcess_UI_Style.css";
import "./B04_PreProcess_UI_Style_Map.css";
import { currentProjectId, guardProjectLeave } from "../A00_Common/b_project_switch";
// 고를 수 있는 필터. 자동 전처리는 이 중 기본 하나만 만들고, 나머지는 관리자가
// 드롭다운에서 고를 때 그 조합만 계산한다 (2026-09-01 사용자 확정).
const SOURCE_FILTERS = ["classification", "ptd", "grid_min_z", "csf", "pmf"] as const;
const MODEL_METHODS = ["tin", "dtm", "nurbs", "implicit", "meshfree"] as const;
const DEFAULT_FILTER = "ptd";
const DEFAULT_METHOD = "implicit"; // 확정 기본 표현(요구 558) — 확정본이 있으면 그것이 먼저
@@ -67,30 +65,11 @@ function infoRow(label: string, value: unknown): KvRow {
return { label, value: value == null || value === "" ? "-" : String(value) };
}
/** 라벨 한 줄 + 드롭다운. 지표면 분석 컨테이너 안의 항목 하나를 만든다. */
function buildSelectField(
label: string,
values: readonly string[],
defaultValue: string,
): { root: HTMLElement; select: HTMLSelectElement } {
const { root, select } = createSelectField({
label,
options: values.map((value) => ({
value,
text: value === "ptd" ? "점진 TIN" : value.replaceAll("_", " ").toUpperCase(),
})),
value: defaultValue,
size: 36,
});
root.classList.add("b04-surface__field");
return { root, select };
}
function getModelFilter(model: SurfaceModelSummary): string {
const configured = model.generation_params?.source_filter;
if (typeof configured === "string") return configured.toLowerCase();
const path = model.model_file_path?.toLowerCase() ?? "";
return SOURCE_FILTERS.find((filter) => path.includes(filter)) ?? "";
return SOURCE_FILTERS.filter((filter) => path.includes(filter)).pop() ?? "";
}
export async function renderB04Surface(root: HTMLElement): Promise<void> {
@@ -138,6 +117,7 @@ export async function renderB04Surface(root: HTMLElement): Promise<void> {
let appliedFilter = DEFAULT_FILTER;
let appliedMethod = DEFAULT_METHOD;
const viewer = createSurfacePointCloudViewer();
const quality = createQualityPanel(); // 762-2 고른 모델의 서피스 품질 지표 넷
const terrainViewer = createSurfaceTerrainViewer();
const mapViewer = createSurfaceMapViewer();
// 도엽등고 3D 서피스 — 전처리에서 함께 생성되는 참고 서피스(LAS 없는 설계의 지형 원천,
@@ -268,6 +248,7 @@ export async function renderB04Surface(root: HTMLElement): Promise<void> {
filterGroup.root,
methodGroup.root,
terrainViewer.smoothingField,
quality.root,
]);
// 표시 옵션은 3D 모델 토글과 포인트 슬라이더를 한 칸에 둔다(슬라이더가 맨 아래).
const displayGroup = buildSurfaceGroup(L("B04_Surface_Group_Display"), [
@@ -460,6 +441,7 @@ export async function renderB04Surface(root: HTMLElement): Promise<void> {
if (!projectId) return;
terrainViewer.setSelection(filterGroup.select.value, methodGroup.select.value);
terrainViewer.render(projectId, models);
quality.show(projectId, findSelectedModel());
confirmButton.disabled = !findSelectedModel();
}