/* ============================================================================= * D03_PlanAnalysis_UI_LidarUpload.ts * 예정노선 분석 지형(PLAN 624-4 화면 · 요구 530 · 531) — 도구줄 끝 칸: 공용 지형 칩(수치지형도 · 라이다) + * 라이다 지형이 없으면 「라이다」 대신 [LAS 올리기](LAS · LAZ + .prj → 서버가 뒤에서 지형 · 진행 = 위치 칸 서클 하나 「라이다」) · * 끝나면 「라이다」를 고를 수 있음(저절로 안 바꿈). * 고른 원천 = 화면 캐시(세션 `d03-terrain`)만 · 기본 = 수치지형도 · 라이다 지형이 없어지면 수치지형도로. * 바꾸면 높이 격자(긋기 기울기)를 그 원천으로 받은 뒤 `onChange` — 겹(급경사 · 음영 · 0차곡)은 페이지가 다시 받음. * 칩 안은 공용 그대로(`map_terrain_chip` · `onChange` · `setHave` 만 씀) · 올리기 = 공용 `map_lidar_upload`(637-3). * ========================================================================== */ import { showToast } from "@ui/ui_template_elements"; import { readState, writeState } from "../A00_Common/b_page_state"; import { createLidarUpload } from "../A00_Common/map/map_lidar_upload"; import { createTerrainChip, type TerrainSource } from "../A00_Common/map/map_terrain_chip"; import { fetchPlanLidar, fetchPlanProgress, fetchPlanSurface, type PlanSurface, } from "./D03_PlanAnalysis_Api"; import { errorText, type WorkCircle } from "./D03_PlanAnalysis_UI_Terrain"; export interface PlanTerrain { /** 도구줄 `terrain` 칸 — 칩 + [LAS 올리기]. */ root: HTMLElement; /** 고른 원천(겹 받기 `terrain=`). */ source: () => TerrainSource; /** 높이 격자 — 수치지형도 = 넘긴 사업 지형면 · 라이다 = 라이다 격자(범위 밖 = 빈칸). */ surface: (sheet: PlanSurface | null) => PlanSurface | null; /** 올리기 단추 보임(편집 권한). */ setEditable: (on: boolean) => void; /** 들어올 때 — 라이다 지형 있나 · 캐시 원천 되살림 · 도는 올리기 지켜봄(겹 받기 전에). */ load: () => Promise; /** 검증 창구. */ state: () => { source: TerrainSource; ready: boolean; uploading: boolean; heights: boolean }; } export function createPlanTerrain(opts: { projectId: string; /** 위치 칸 진행 서클(604-3 · 하나). */ work: WorkCircle; /** 원천이 바뀌고 높이 격자를 받은 뒤. */ onChange: (source: TerrainSource) => void; }): PlanTerrain { const { projectId, work } = opts; let source: TerrainSource = "sheet"; let ready = false; let editable = false; let modelFile = ""; let lidar: PlanSurface | null = null; const chip = createTerrainChip({ page: "d03", value: source, have: ["sheet"], onChange: (next) => void choose(next), }); const lidarItem = chip.root.querySelector('[data-value="lidar"]')!; const lidarUpload = createLidarUpload({ path: `/projects/${projectId}/plan/lidar`, poll: async () => (await fetchPlanProgress(projectId)).lidar, show: (state) => work.terrain(state && { ...state, label: "라이다" }), track: (job) => work.track(job), onBusy: () => render(), onDone: async () => { try { await readLidar(); if (ready) showToast("라이다 지형 끝 — 지형 칩에서 「라이다」를 고를 수 있음", "success"); } catch (error) { showToast(`라이다 진행을 읽지 못했습니다 — ${errorText(error)}`, "error"); } render(); }, }); const upload = lidarUpload.button; // 마우스 글 = 지형 칸 표 호버제목(634-2 · 「수치지형도/라이다」 · 단추 글 「LAS 올리기」 그대로) const root = document.createElement("div"); root.className = "d03-plan__terrain"; root.append(chip.root, upload); function render(): void { chip.setHave(ready ? ["sheet", "lidar"] : ["sheet"]); chip.set(source); lidarItem.style.display = !ready && editable ? "none" : ""; // 올릴 수 있으면 막힌 「라이다」 대신 단추 upload.hidden = ready || !editable; } async function readLidar(): Promise { const info = await fetchPlanLidar(projectId); if (info.model_file !== modelFile) lidar = null; // 다시 올림 = 새 격자 [ready, modelFile] = [info.ready, info.model_file ?? ""]; } /** 라이다 높이 격자 — 한 번 받아 둠. */ async function lidarSurface(): Promise { lidar ??= await fetchPlanSurface(projectId, modelFile); return lidar; } async function choose(next: TerrainSource): Promise { if (next === source) return; try { if (next === "lidar" && !(await lidarSurface())) throw new Error("라이다 높이 격자가 없습니다"); } catch (error) { showToast(`라이다 지형을 읽지 못했습니다 — ${errorText(error)}`, "error"); return render(); } source = next; writeState("d03-terrain", source, projectId); render(); opts.onChange(source); } async function load(): Promise { try { await readLidar(); if (ready && readState("d03-terrain", projectId) === "lidar") { if (await lidarSurface()) source = "lidar"; } if (source === "sheet") writeState("d03-terrain", null, projectId); } catch (error) { showToast(`라이다 지형을 읽지 못했습니다 — ${errorText(error)}`, "error"); } render(); const job = await fetchPlanProgress(projectId).catch(() => null); if (job?.lidar.running) void lidarUpload.follow(); } render(); return { root, source: () => source, surface: (sheet) => (source === "lidar" ? lidar : sheet), setEditable(on) { editable = on; render(); }, load, state: () => ({ source, ready, uploading: lidarUpload.busy(), heights: !!lidar }), }; }