This commit is contained in:
2026-07-10 18:12:17 +09:00
parent 50d75fcfcd
commit e3d66e717c
28 changed files with 2542 additions and 99 deletions
+289 -4
View File
@@ -25,9 +25,17 @@ import {
import { workflowSteps } from "../A00_Common/b_page_scaffold";
import {
analyzeSurface,
fetchSurfaceGroundStats,
fetchSurfacePointCloud,
fetchSurfaceStatus,
listSurfaceInputFiles,
listSurfaceModels,
type SurfaceInputFileSummary,
type SurfaceModelSummary,
type SurfaceStatusResponse,
} from "./B04_wf1_Surface_Api_Fetch";
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
import { createSurfacePointCloudViewer } from "./B04_wf1_Surface_UI_Viewer";
import "./B04_wf1_Surface_UI_Style.css";
/** locale 헬퍼 */
@@ -73,7 +81,7 @@ function buildCheckboxGroup(
return { root, selected };
}
export function renderB04Surface(root: HTMLElement): void {
export function renderB04SurfaceLegacy(root: HTMLElement): void {
const shell = createWorkflowShell({
title: L("B04_Surface_Title"),
steps: workflowSteps(),
@@ -166,9 +174,7 @@ export function renderB04Surface(root: HTMLElement): void {
const meta = document.createElement("div");
meta.className = "b04-surface__model-meta";
const resolution = document.createElement("span");
resolution.textContent = `${L("B04_Surface_Model_Resolution")}: ${
model.resolution_m ?? "-"
}`;
resolution.textContent = `${L("B04_Surface_Model_Resolution")}: ${model.resolution_m ?? "-"}`;
const path = document.createElement("span");
path.textContent = `${L("B04_Surface_Model_Path")}: ${model.model_file_path ?? "-"}`;
meta.append(resolution, path);
@@ -248,3 +254,282 @@ export function renderB04Surface(root: HTMLElement): void {
const initialProjectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
if (initialProjectId) void loadModels(initialProjectId);
}
function buildInfoLine(label: string, value: unknown): HTMLElement {
const row = document.createElement("div");
row.className = "b04-surface__line";
const key = document.createElement("span");
key.textContent = label;
const val = document.createElement("strong");
val.textContent = value == null || value === "" ? "-" : String(value);
row.append(key, val);
return row;
}
export function renderB04Surface(root: HTMLElement): void {
let selectedInputFile: SurfaceInputFileSummary | null = null;
let inputFiles: SurfaceInputFileSummary[] = [];
const inputSelect = document.createElement("select");
inputSelect.className = "b04-surface__select";
const inputInfo = document.createElement("div");
inputInfo.className = "b04-surface__input-info";
const statusBox = document.createElement("div");
statusBox.className = "b04-surface__status";
const modelList = document.createElement("div");
modelList.className = "b04-surface__models";
const statsList = document.createElement("div");
statsList.className = "b04-surface__stats";
const viewer = createSurfacePointCloudViewer();
const filterGroup = buildCheckboxGroup(L("B04_Surface_Group_Filters"), SOURCE_FILTERS, [
"grid_min_z",
"csf",
"pmf",
]);
const methodGroup = buildCheckboxGroup(L("B04_Surface_Group_Methods"), MODEL_METHODS, [
"dtm",
"tin",
]);
const forceLabel = document.createElement("label");
forceLabel.className = "b04-surface__check";
const forceBox = document.createElement("input");
forceBox.type = "checkbox";
const forceText = document.createElement("span");
forceText.textContent = L("B04_Surface_Field_Force");
forceLabel.append(forceBox, forceText);
const analyzeButton = createButton({
label: L("B04_Surface_Btn_Analyze"),
variant: "filled",
onClick: () => void onB04_Surface_Analyze_Click(),
});
const refreshButton = createButton({
label: L("B04_Surface_Btn_Refresh"),
variant: "ghost",
onClick: () => void onB04_Surface_Refresh_Click(),
});
const panel = document.createElement("div");
panel.className = "b04-surface__form";
const inputGroup = document.createElement("section");
inputGroup.className = "b04-surface__group";
const inputTitle = document.createElement("h3");
inputTitle.className = "b04-surface__panel-title";
inputTitle.textContent = L("B04_Surface_InputFiles");
inputGroup.append(inputTitle, inputSelect, inputInfo);
panel.append(
inputGroup,
filterGroup.root,
methodGroup.root,
forceLabel,
analyzeButton,
refreshButton,
);
const workspace = document.createElement("div");
workspace.className = "b04-surface__workspace";
const topbar = document.createElement("div");
topbar.className = "b04-surface__topbar";
const title = document.createElement("h3");
title.textContent = L("B04_Surface_PointCloud_Title");
topbar.append(title, statusBox);
const bottom = document.createElement("div");
bottom.className = "b04-surface__bottom";
const statsSection = document.createElement("section");
statsSection.className = "b04-surface__section";
const statsTitle = document.createElement("h3");
statsTitle.textContent = L("B04_Surface_GroundStats_Title");
statsSection.append(statsTitle, statsList);
const modelsSection = document.createElement("section");
modelsSection.className = "b04-surface__section";
const modelsTitle = document.createElement("h3");
modelsTitle.textContent = L("B04_Surface_Result_Title");
modelsSection.append(modelsTitle, modelList);
bottom.append(statsSection, modelsSection);
workspace.append(topbar, viewer.root, bottom);
const layout = createWorkflowLayout({
title: L("B04_Surface_Title"),
steps: workflowSteps(),
activeStep: 1,
leftPanel: panel,
mainContent: workspace,
});
function getProjectId(): string | null {
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
if (!projectId) showToast(L("B04_Surface_Error_Project"), "error");
return projectId;
}
function renderStatus(status: SurfaceStatusResponse | null): void {
statusBox.replaceChildren();
if (!status) {
statusBox.append(createTag(L("B04_Surface_Status_Unknown"), "neutral"));
return;
}
const variant =
status.status === "completed" ? "success" : status.status === "failed" ? "danger" : "warning";
statusBox.append(
createTag(`${status.progress_percent}%`, variant),
document.createTextNode(status.message),
);
}
function renderInputFiles(files: readonly SurfaceInputFileSummary[]): void {
inputFiles = [...files];
inputSelect.replaceChildren();
if (inputFiles.length === 0) {
selectedInputFile = null;
const option = document.createElement("option");
option.value = "";
option.textContent = L("B04_Surface_InputFiles_Empty");
inputSelect.append(option);
inputInfo.replaceChildren();
analyzeButton.disabled = true;
return;
}
selectedInputFile = inputFiles[0];
for (const file of inputFiles) {
const option = document.createElement("option");
option.value = String(file.id);
option.textContent = `${file.original_filename} (#${file.id})`;
inputSelect.append(option);
}
inputSelect.value = String(selectedInputFile.id);
analyzeButton.disabled = false;
renderInputInfo();
}
function renderInputInfo(): void {
inputInfo.replaceChildren();
if (!selectedInputFile) return;
inputInfo.append(
buildInfoLine(L("B04_Surface_Input_FileName"), selectedInputFile.original_filename),
buildInfoLine(L("B04_Surface_Input_Crs"), selectedInputFile.crs_epsg),
buildInfoLine(L("B04_Surface_Input_Size"), selectedInputFile.file_size_mb?.toFixed(2)),
);
}
function renderModels(models: readonly SurfaceModelSummary[]): void {
modelList.replaceChildren();
if (models.length === 0) {
const empty = document.createElement("p");
empty.className = "b04-surface__empty";
empty.textContent = L("B04_Surface_Result_Empty");
modelList.append(empty);
return;
}
for (const model of models) {
const card = document.createElement("article");
card.className = "b04-surface__model-card";
const head = document.createElement("div");
head.className = "b04-surface__model-head";
const type = document.createElement("strong");
type.textContent = model.model_type;
const variant =
model.status === "CONFIRMED" || model.status === "COMPLETE" ? "success" : "neutral";
head.append(type, createTag(model.status, variant));
const meta = document.createElement("div");
meta.className = "b04-surface__model-meta";
meta.append(
buildInfoLine(L("B04_Surface_Model_Resolution"), model.resolution_m),
buildInfoLine(L("B04_Surface_Model_Path"), model.model_file_path),
);
card.append(head, meta);
modelList.append(card);
}
}
function renderGroundStats(filters: Record<string, Record<string, unknown>>): void {
statsList.replaceChildren();
const entries = Object.entries(filters);
if (entries.length === 0) {
const empty = document.createElement("p");
empty.className = "b04-surface__empty";
empty.textContent = L("B04_Surface_GroundStats_Empty");
statsList.append(empty);
return;
}
for (const [name, value] of entries) {
const item = document.createElement("article");
item.className = "b04-surface__stat-card";
item.append(
buildInfoLine(L("B04_Surface_GroundStats_Filter"), name),
buildInfoLine(L("B04_Surface_GroundStats_SourcePoints"), value.source_point_count),
);
statsList.append(item);
}
}
async function loadProjectData(projectId: string): Promise<void> {
const [inputs, status, models, stats] = await Promise.all([
listSurfaceInputFiles(projectId),
fetchSurfaceStatus(projectId),
listSurfaceModels(projectId),
fetchSurfaceGroundStats(projectId),
]);
renderInputFiles(inputs.files);
renderStatus(status);
renderModels(models.models);
renderGroundStats(stats.filters);
try {
viewer.render(await fetchSurfacePointCloud(projectId));
} catch {
viewer.render(null);
}
}
async function onB04_Surface_Analyze_Click(): Promise<void> {
const projectId = getProjectId();
if (!projectId || !selectedInputFile) return;
if (filterGroup.selected.size === 0 || methodGroup.selected.size === 0) {
showToast(L("B04_Surface_Error_Selection"), "error");
return;
}
showLoadingOverlay();
try {
const response = await analyzeSurface(projectId, {
input_file_id: selectedInputFile.id,
source_filters: [...filterGroup.selected],
methods: [...methodGroup.selected],
force: forceBox.checked,
});
showToast(
`${L("B04_Surface_Analyze_Success")} (${response.surface_model_ids.length})`,
"success",
);
await loadProjectData(projectId);
} catch (error) {
const detail = error instanceof Error ? error.message : L("B04_Surface_Analyze_Failed");
showToast(`${L("B04_Surface_Analyze_Failed")} ${detail}`, "error");
} finally {
hideLoadingOverlay();
}
}
async function onB04_Surface_Refresh_Click(): Promise<void> {
const projectId = getProjectId();
if (!projectId) return;
showLoadingOverlay();
try {
await loadProjectData(projectId);
} catch {
showToast(L("B04_Surface_Load_Failed"), "error");
} finally {
hideLoadingOverlay();
}
}
inputSelect.addEventListener("change", () => {
selectedInputFile = inputFiles.find((file) => String(file.id) === inputSelect.value) ?? null;
renderInputInfo();
});
root.replaceChildren(layout.root);
const projectId = getProjectId();
if (projectId) void onB04_Surface_Refresh_Click();
}