- 지도 층 단추 열다섯 안팎 · 표시 토글 셋 · 라이다 겹침 = 켜기 칩(층 색 점) · 도엽 보간 방식 = 분할 고르기 · 도구줄 부품 - 고르기 38 → 고르기 부품 · 점 크기 · 밀도 = 슬라이더 · 입력 파일 정보 = 값 목록 · 초기화 = 위험 고스트 · 묶음 상자 = 카드 + 패널 머리 - 죽은 CSS 열여섯 걷음 · 날 색 → 공통 값 · 가이드 촬영 선택자 · 지킴 시험 test_553_1_pages 늘림 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
202 lines
7.3 KiB
TypeScript
202 lines
7.3 KiB
TypeScript
/* =============================================================================
|
|
* B04_PreProcess_UI_TerrainViewer_Chrome.ts
|
|
* 지표면 3D 뷰어의 **DOM 뼈대** — 상태줄·표시 토글·등고선 간격 폼·뷰포트·축척 막대·
|
|
* 방위 컴파스·표고 범례·로딩 서클을 만들어 넘긴다.
|
|
*
|
|
* `B04_PreProcess_UI_TerrainViewer` 가 700줄을 넘겨 떼어낸 조각이다(2026-09-04).
|
|
* 만드는 순서·클래스·인라인 스타일은 옮기기 전 그대로다. 이벤트 배선과 THREE 렌더링은
|
|
* 뷰어 본체에 남아 있고, 여기서는 엘리먼트만 만든다.
|
|
* ========================================================================== */
|
|
|
|
import { createTerrainCompass } from "@ui/ui_template_compass";
|
|
import { createButton, createInputField, createSelectField } from "@ui/ui_template_elements";
|
|
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
|
import { createProgressCircle } from "@ui/ui_template_progress";
|
|
import { createToggle } from "@ui/ui_template_toggle";
|
|
|
|
function L(key: keyof typeof ui_locales): string {
|
|
return ui_locales[key][currentLanguageIndex];
|
|
}
|
|
|
|
/** 켜기 칩 부품에 옛 체크 칸 꼴(checked · change)을 입힘 — 뷰어 본체 배선은 그대로. */
|
|
function chipCheck(label: string, checked: boolean) {
|
|
const toggle = createToggle({ kind: "chip", label, checked, size: 32 });
|
|
return {
|
|
root: toggle.root,
|
|
get checked(): boolean {
|
|
return toggle.get();
|
|
},
|
|
set checked(on: boolean) {
|
|
toggle.set(on);
|
|
},
|
|
// 칩의 자기 클릭 처리가 먼저 서므로 여기서는 이미 바뀐 값을 읽는다.
|
|
addEventListener: (_type: "change", listener: () => void) =>
|
|
toggle.root.addEventListener("click", listener),
|
|
};
|
|
}
|
|
|
|
/** 뷰어 본체가 이어 쓸 엘리먼트 묶음 — 이름은 분리 전 지역변수와 같다. */
|
|
export type TerrainViewerChrome = ReturnType<typeof buildTerrainViewerChrome>;
|
|
|
|
export function buildTerrainViewerChrome() {
|
|
const root = document.createElement("div");
|
|
root.className = "ui-card terrain-model-group";
|
|
|
|
const statusSpan = document.createElement("span");
|
|
statusSpan.className = "terrain-status";
|
|
statusSpan.style.fontSize = "var(--text-caption)";
|
|
statusSpan.style.color = "var(--color-text-secondary)";
|
|
statusSpan.textContent = "모델 선택 대기 중...";
|
|
|
|
const axesCheck = chipCheck("축", false);
|
|
|
|
const rightControls = document.createElement("div");
|
|
rightControls.className = "model-display-options";
|
|
|
|
// Surface Toggle
|
|
const surfCheck = chipCheck("서피스", true);
|
|
|
|
// 스무딩(tin/dtm 전용) — 지면 필터·서피스와 함께 "지표면 분석" 컨테이너로 옮겼다.
|
|
// 주변 입력과 양식을 맞추려고 버튼이 아니라 드롭다운이다(2026-08-01 사용자 지시).
|
|
// 상태·재렌더 배선은 여기 그대로 두고, 페이지는 이 엘리먼트를 원하는 자리에 놓기만 한다.
|
|
const smoothField = createSelectField({
|
|
label: L("B04_Surface_Field_Smoothing"),
|
|
options: [
|
|
{ value: "on", text: L("B04_Surface_Smoothing_On") },
|
|
{ value: "off", text: L("B04_Surface_Smoothing_Off") },
|
|
],
|
|
value: "on",
|
|
size: 36,
|
|
});
|
|
const smoothLabel = smoothField.root;
|
|
smoothLabel.classList.add("b04-surface__field");
|
|
const smoothSelect = smoothField.select;
|
|
|
|
// Contour Toggle
|
|
const contourCheck = chipCheck("등고선", true);
|
|
|
|
// Contour Interval input form
|
|
const intervalForm = document.createElement("form");
|
|
intervalForm.className = "contour-interval-form";
|
|
|
|
const intervalField = createInputField({
|
|
type: "number",
|
|
value: "1.0",
|
|
min: 0.5,
|
|
unit: "m",
|
|
size: 32,
|
|
});
|
|
const intervalInput = intervalField.input;
|
|
intervalInput.step = "0.5";
|
|
|
|
const intervalSubmit = createButton({
|
|
label: "적용",
|
|
variant: "ghost",
|
|
size: 32,
|
|
type: "submit",
|
|
});
|
|
|
|
intervalForm.append(document.createTextNode("간격 "), intervalField.root, intervalSubmit);
|
|
|
|
rightControls.append(axesCheck.root, surfCheck.root, contourCheck.root, intervalForm);
|
|
|
|
// 표시 토글 묶음 + 상태 줄. 컨테이너(제목)는 페이지가 만든다 — 포인트 옵션과 한 칸을 쓴다.
|
|
const optionsContent = document.createElement("div");
|
|
optionsContent.className = "terrain-options-content";
|
|
optionsContent.append(rightControls, statusSpan);
|
|
|
|
// 3D View container
|
|
const viewerArea = document.createElement("div");
|
|
viewerArea.className = "three-viewer";
|
|
viewerArea.style.position = "relative";
|
|
viewerArea.style.borderRadius = "0 0 var(--radius-cards) var(--radius-cards)";
|
|
viewerArea.style.overflow = "hidden";
|
|
|
|
const canvas = document.createElement("canvas");
|
|
canvas.className = "b04-surface-viewer__canvas";
|
|
viewerArea.append(canvas);
|
|
root.append(viewerArea);
|
|
|
|
// Scale bar overlay
|
|
const scaleBar = document.createElement("div");
|
|
scaleBar.className = "b04-surface__scale";
|
|
scaleBar.hidden = true;
|
|
|
|
const scaleLabel = document.createElement("span");
|
|
scaleBar.append(scaleLabel);
|
|
viewerArea.append(scaleBar);
|
|
|
|
// 방위 콤파스 — 축척 막대 반대편(우하단). 바늘 각도는 애니메이션 루프가 맞춘다.
|
|
const compass = createTerrainCompass({ className: "b04-surface__compass" });
|
|
viewerArea.append(compass.root);
|
|
|
|
// Elevation bounds legend bar overlay (I-403)
|
|
const legendBar = document.createElement("div");
|
|
legendBar.style.position = "absolute";
|
|
legendBar.style.top = "16px";
|
|
legendBar.style.right = "16px";
|
|
legendBar.style.background = "rgba(255, 255, 255, 0.9)";
|
|
legendBar.style.border = "1px solid #cbd5e1";
|
|
legendBar.style.borderRadius = "6px";
|
|
legendBar.style.padding = "8px";
|
|
legendBar.style.width = "50px";
|
|
legendBar.style.display = "none"; // hidden until contours are loaded
|
|
legendBar.style.flexDirection = "column";
|
|
legendBar.style.alignItems = "center";
|
|
legendBar.style.zIndex = "10";
|
|
legendBar.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)";
|
|
legendBar.style.pointerEvents = "none";
|
|
|
|
const maxValSpan = document.createElement("span");
|
|
maxValSpan.style.fontSize = "10px";
|
|
maxValSpan.style.fontWeight = "bold";
|
|
maxValSpan.style.color = "#b91c1c";
|
|
maxValSpan.style.marginBottom = "4px";
|
|
|
|
const gradientDiv = document.createElement("div");
|
|
gradientDiv.style.width = "12px";
|
|
gradientDiv.style.height = "120px";
|
|
gradientDiv.style.background =
|
|
"linear-gradient(to bottom, #d60000 0%, #ff5100 25%, #e6a100 50%, #228b22 75%, #3a85ff 100%)";
|
|
gradientDiv.style.borderRadius = "2px";
|
|
gradientDiv.style.border = "1px solid #94a3b8";
|
|
|
|
const minValSpan = document.createElement("span");
|
|
minValSpan.style.fontSize = "10px";
|
|
minValSpan.style.fontWeight = "bold";
|
|
minValSpan.style.color = "#1d4ed8";
|
|
minValSpan.style.marginTop = "4px";
|
|
|
|
legendBar.append(maxValSpan, gradientDiv, minValSpan);
|
|
viewerArea.append(legendBar);
|
|
|
|
// 뷰포트 정중앙 로딩 서클 — 메쉬 파일은 수십 MB라 내려받는 동안 화면이 비어 보인다.
|
|
const progress = createProgressCircle({ overlay: true });
|
|
progress.root.hidden = true;
|
|
viewerArea.append(progress.root);
|
|
|
|
return {
|
|
root,
|
|
statusSpan,
|
|
axesCheck,
|
|
surfCheck,
|
|
smoothLabel,
|
|
smoothSelect,
|
|
contourCheck,
|
|
intervalForm,
|
|
intervalInput,
|
|
intervalSubmit,
|
|
optionsContent,
|
|
viewerArea,
|
|
canvas,
|
|
scaleBar,
|
|
scaleLabel,
|
|
compass,
|
|
legendBar,
|
|
maxValSpan,
|
|
gradientDiv,
|
|
minValSpan,
|
|
progress,
|
|
};
|
|
}
|