feat(B04): 등고선 보간 6종을 만들어 화면에서 비교하게 한다
문헌(ANUDEM Hutchinson 1988/89, DEM 보간 비교연구)은 지형에 따라 우열이
갈려 단일 최적해가 없다고 본다. 방식을 하나로 고르지 않고 전부 만들어 두고
사용자가 눈으로 비교해 정하도록 했다(2026-08-30 사용자 지시).
신규 B04_PreProcess_Engine_SheetMethods.py — 거리비례 / TPS(박판, 감쇠
최소곡률 LSMR) / 라플라스 / TIN 선형 / TIN 곡면(Clough-Tocher) / IDW.
방식마다 dtm_sheet_{key}.npz + 프리뷰를 만들어 surface_models에
source_filter=sheet_{key}로 등록하므로 기존 뷰어·등고선·프리뷰 경로가 그대로
돈다. 폐합 링 안쪽 처리는 방식과 무관하게 똑같이 적용한다.
화면: 도엽등고 3D 서피스 컨테이너에 방식 전환 버튼과 '라이다 겹쳐 보기'
토글(반투명 파랑)을 달았다. 라이다는 같은 좌표계라 같은 자리에 겹친다.
실측(c1bb453f, 6종 65s): 평탄 셀 TPS 0.003% / 거리비례 0.014% /
TIN 곡면 0.026% / 라플라스 0.235% / TIN 선형 10.4% / IDW 69.1%.
LAS 대비 노선 |dz| 평균은 2.59~3.00m로 방식 간 차이가 작다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,15 @@ const MODEL_METHODS = ["tin", "dtm", "nurbs", "implicit", "meshfree"] as const;
|
||||
const DEFAULT_FILTER = "csf";
|
||||
const DEFAULT_METHOD = "dtm";
|
||||
const ROUTE_STAGE = ROUTES.B05_PROFILE;
|
||||
// 도엽 서피스 보간 방식 버튼 순서 — 백엔드 SHEET_SURFACE_METHODS와 같은 차례로 둔다.
|
||||
const SHEET_METHOD_ORDER = [
|
||||
"distance",
|
||||
"biharmonic",
|
||||
"laplace",
|
||||
"tin",
|
||||
"clough",
|
||||
"idw",
|
||||
];
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
@@ -157,7 +166,49 @@ export async function renderB04Surface(root: HTMLElement): Promise<void> {
|
||||
const sheetTitle = document.createElement("h3");
|
||||
sheetTitle.className = "b04-surface__panel-title";
|
||||
sheetTitle.textContent = L("B04_Surface_SheetSurface");
|
||||
sheetSection.append(sheetTitle, sheetViewer.root);
|
||||
// 보간 방식 전환 줄 — 어느 방식이 이 지형에 맞는지 눈으로 비교해 정한다
|
||||
// (2026-08-30 사용자 지시). 버튼 목록은 실제 생성된 모델에서 만든다.
|
||||
const sheetToolbar = document.createElement("div");
|
||||
sheetToolbar.className = "b04-surface__sheet-toolbar";
|
||||
const sheetMethodButtons = new Map<string, HTMLButtonElement>();
|
||||
let sheetMethod = "";
|
||||
|
||||
function selectSheetMethod(method: string): void {
|
||||
sheetMethod = method;
|
||||
for (const [key, button] of sheetMethodButtons) {
|
||||
button.classList.toggle("is-active", key === method);
|
||||
}
|
||||
const projectId = getProjectId();
|
||||
if (!projectId) return;
|
||||
sheetViewer.setSelection(`sheet_${method}`, "dtm");
|
||||
sheetViewer.render(projectId, models);
|
||||
}
|
||||
|
||||
// 라이다 지표면 겹쳐 보기 — 확정 필터의 DTM을 반투명으로 얹는다.
|
||||
const lidarLabel = document.createElement("label");
|
||||
lidarLabel.className = "toggle-label toggle-button b04-surface__sheet-lidar";
|
||||
const lidarCheck = document.createElement("input");
|
||||
lidarCheck.type = "checkbox";
|
||||
lidarLabel.append(
|
||||
lidarCheck,
|
||||
document.createTextNode(` ${L("B04_Surface_SheetLidar")}`),
|
||||
);
|
||||
lidarCheck.addEventListener("change", () => {
|
||||
void sheetViewer
|
||||
.showOverlay(
|
||||
lidarCheck.checked ? filterGroup.select.value : "",
|
||||
"dtm",
|
||||
terrainViewer.isSmoothingEnabled(),
|
||||
)
|
||||
.then((loaded) => {
|
||||
if (lidarCheck.checked && !loaded) {
|
||||
showToast(L("B04_Surface_SheetLidar_Missing"), "warning");
|
||||
lidarCheck.checked = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
sheetSection.append(sheetTitle, sheetToolbar, sheetViewer.root);
|
||||
sheetSection.hidden = true;
|
||||
|
||||
let syncingCamera = false;
|
||||
@@ -437,17 +488,47 @@ export async function renderB04Surface(root: HTMLElement): Promise<void> {
|
||||
renderInputInfo();
|
||||
updateSelectedModel();
|
||||
|
||||
// 도엽등고 3D 서피스 — sheet/dtm 모델이 있으면 별도 컨테이너로 보여준다.
|
||||
const hasSheetModel = models.some(
|
||||
(model) =>
|
||||
model.model_type.toLowerCase() === "dtm" &&
|
||||
getModelFilter(model) === "sheet",
|
||||
);
|
||||
sheetSection.hidden = !hasSheetModel;
|
||||
if (hasSheetModel) {
|
||||
sheetViewer.setSelection("sheet", "dtm");
|
||||
// 도엽등고 3D 서피스 — sheet_* 모델이 있으면 별도 컨테이너로 보여준다.
|
||||
// 보간 방식마다 모델이 하나씩 있으므로 버튼으로 갈아 끼운다.
|
||||
const sheetMethods = models
|
||||
.filter(
|
||||
(model) =>
|
||||
model.model_type.toLowerCase() === "dtm" &&
|
||||
getModelFilter(model).startsWith("sheet_"),
|
||||
)
|
||||
.map((model) => ({
|
||||
key: getModelFilter(model).slice("sheet_".length),
|
||||
label:
|
||||
typeof model.generation_params?.interpolation_label === "string"
|
||||
? (model.generation_params.interpolation_label as string)
|
||||
: getModelFilter(model).slice("sheet_".length),
|
||||
}))
|
||||
// 모델 목록은 최신순이라 버튼이 뒤섞인다 — 정의 순서로 고정한다.
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(SHEET_METHOD_ORDER.indexOf(a.key) + 1 || 99) -
|
||||
(SHEET_METHOD_ORDER.indexOf(b.key) + 1 || 99),
|
||||
);
|
||||
sheetSection.hidden = sheetMethods.length === 0;
|
||||
if (sheetMethods.length) {
|
||||
sheetToolbar.replaceChildren();
|
||||
sheetMethodButtons.clear();
|
||||
for (const method of sheetMethods) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "b04-surface__sheet-method";
|
||||
button.textContent = method.label;
|
||||
button.addEventListener("click", () => selectSheetMethod(method.key));
|
||||
sheetToolbar.append(button);
|
||||
sheetMethodButtons.set(method.key, button);
|
||||
}
|
||||
sheetToolbar.append(lidarLabel);
|
||||
sheetViewer.setSmoothing(false);
|
||||
sheetViewer.render(projectId, models);
|
||||
selectSheetMethod(
|
||||
sheetMethods.some((method) => method.key === sheetMethod)
|
||||
? sheetMethod
|
||||
: sheetMethods[0].key,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user