- 새 D03_PlanAnalysis_UI_LidarUpload — 공용 칩(안 고침) + 라이다 없으면 [LAS 올리기](부품 단추 · LAS · LAZ · .prj) · 진행 = 위치 칸 서클 「라이다」 · 고른 원천 = 세션 d03-terrain(등록부 한 줄) - 공용 겹 받기 fetchPlanRaster · fetchPlanZeroOrder 에 terrain 인자(안 넘김 = 옛 그대로 · B05 영향 없음) - 겹 reterrain = 지형 겹 셋만 비우고 다시 · 높이 = 고른 원천 격자(라이다 범위 밖 = 빈칸) - 지도는 지형 칸을 넘겨받기만 · 낡은 시험 둘(475 · 624_2)은 글 대조 줄만 새 꼴로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
177 lines
6.7 KiB
TypeScript
177 lines
6.7 KiB
TypeScript
/* =============================================================================
|
|
* D03_PlanAnalysis_UI_LidarUpload.ts
|
|
* 예정노선 분석 지형(PLAN 624-4 화면 · 요구 530 · 531) — 도구줄 끝 칸: 공용 지형 칩(수치지형도 · 라이다) +
|
|
* 라이다 지형이 없으면 「라이다」 대신 [LAS 올리기](LAS · LAZ + .prj → 서버가 뒤에서 지형 · 진행 = 위치 칸 서클 하나 「라이다」) ·
|
|
* 끝나면 「라이다」를 고를 수 있음(저절로 안 바꿈).
|
|
* 고른 원천 = 화면 캐시(세션 `d03-terrain`)만 · 기본 = 수치지형도 · 라이다 지형이 없어지면 수치지형도로.
|
|
* 바꾸면 높이 격자(긋기 기울기)를 그 원천으로 받은 뒤 `onChange` — 겹(급경사 · 음영 · 0차곡)은 페이지가 다시 받음.
|
|
* 칩 안은 공용 그대로(`map_terrain_chip` · `onChange` · `setHave` 만 씀).
|
|
* ========================================================================== */
|
|
|
|
import { createButton } from "@ui/ui_template_button";
|
|
import { pickFiles } from "@ui/ui_template_drop";
|
|
import { showToast } from "@ui/ui_template_elements";
|
|
import { readState, writeState } from "../A00_Common/b_page_state";
|
|
import { createTerrainChip, type TerrainSource } from "../A00_Common/map/map_terrain_chip";
|
|
import {
|
|
fetchPlanLidar,
|
|
fetchPlanProgress,
|
|
fetchPlanSurface,
|
|
uploadPlanLidar,
|
|
type PlanSurface,
|
|
} from "./D03_PlanAnalysis_Api";
|
|
import { errorText, sleep, type WorkCircle } from "./D03_PlanAnalysis_UI_Terrain";
|
|
|
|
const POLL_MS = 1000;
|
|
|
|
export interface PlanTerrain {
|
|
/** 도구줄 `terrain` 칸 — 칩 + [LAS 올리기]. */
|
|
root: HTMLElement;
|
|
/** 고른 원천(겹 받기 `terrain=`). */
|
|
source: () => TerrainSource;
|
|
/** 높이 격자 — 수치지형도 = 넘긴 사업 지형면 · 라이다 = 라이다 격자(범위 밖 = 빈칸). */
|
|
surface: (sheet: PlanSurface | null) => PlanSurface | null;
|
|
/** 올리기 단추 보임(편집 권한). */
|
|
setEditable: (on: boolean) => void;
|
|
/** 들어올 때 — 라이다 지형 있나 · 캐시 원천 되살림 · 도는 올리기 지켜봄(겹 받기 전에). */
|
|
load: () => Promise<void>;
|
|
/** 검증 창구. */
|
|
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 uploading = 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<HTMLElement>('[data-value="lidar"]')!;
|
|
const upload = createButton({
|
|
label: "LAS 올리기",
|
|
variant: "ghost",
|
|
size: 32,
|
|
iconStart: "upload",
|
|
onClick: () =>
|
|
pickFiles({ accept: ".las,.laz,.prj", multiple: true }, (files) => void send(files)),
|
|
});
|
|
upload.title =
|
|
"라이다 LAS · LAZ(+ 좌표계 .prj 하나) — 올리면 서버가 지형을 지음 · 끝나면 「라이다」";
|
|
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;
|
|
upload.disabled = uploading;
|
|
}
|
|
|
|
async function readLidar(): Promise<void> {
|
|
const info = await fetchPlanLidar(projectId);
|
|
if (info.model_file !== modelFile) lidar = null; // 다시 올림 = 새 격자
|
|
[ready, modelFile] = [info.ready, info.model_file ?? ""];
|
|
}
|
|
|
|
/** 라이다 높이 격자 — 한 번 받아 둠. */
|
|
async function lidarSurface(): Promise<PlanSurface | null> {
|
|
lidar ??= await fetchPlanSurface(projectId, modelFile);
|
|
return lidar;
|
|
}
|
|
|
|
async function choose(next: TerrainSource): Promise<void> {
|
|
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 send(files: File[]): Promise<void> {
|
|
try {
|
|
await uploadPlanLidar(projectId, files);
|
|
} catch (error) {
|
|
return showToast(`LAS 올리지 못함 — ${errorText(error)}`, "error");
|
|
}
|
|
await work.track(follow());
|
|
}
|
|
|
|
/** 올린 뒤 지형 짓기를 끝날 때까지 지켜봄 — 진행 = 서클. */
|
|
async function follow(): Promise<void> {
|
|
uploading = true;
|
|
work.terrain({ ratio: 0, text: "", label: "라이다" });
|
|
render();
|
|
try {
|
|
for (;;) {
|
|
if (!root.isConnected) return; // 페이지 떠남
|
|
const job = (await fetchPlanProgress(projectId)).lidar;
|
|
work.terrain({ ratio: job.percent / 100, text: job.message ?? "", label: "라이다" });
|
|
if (!job.running) {
|
|
if (job.error) showToast(`라이다 지형 못 지음 — ${job.error}`, "error");
|
|
break;
|
|
}
|
|
await sleep(POLL_MS);
|
|
}
|
|
await readLidar();
|
|
if (ready) showToast("라이다 지형 끝 — 지형 칩에서 「라이다」를 고를 수 있음", "success");
|
|
} catch (error) {
|
|
showToast(`라이다 진행을 읽지 못했습니다 — ${errorText(error)}`, "error");
|
|
} finally {
|
|
uploading = false;
|
|
work.terrain(null);
|
|
render();
|
|
}
|
|
}
|
|
|
|
async function load(): Promise<void> {
|
|
try {
|
|
await readLidar();
|
|
if (ready && readState<TerrainSource>("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 work.track(follow());
|
|
}
|
|
|
|
render();
|
|
return {
|
|
root,
|
|
source: () => source,
|
|
surface: (sheet) => (source === "lidar" ? lidar : sheet),
|
|
setEditable(on) {
|
|
editable = on;
|
|
render();
|
|
},
|
|
load,
|
|
state: () => ({ source, ready, uploading, heights: !!lidar }),
|
|
};
|
|
}
|