- 공용 A00_Common/map/map_lidar_upload — 파일 고름 · POST multipart · 1 초 묻기 · 서클 · 끝난 뒤 일(주소 · 진행 묻기만 인자) - 예정노선 UI_LidarUpload = 부품 부르기(화면 · 동작 그대로) · 안 쓰는 uploadPlanLidar 걷음 - B05 UI_RouteEdit_Terrain — 칩 · 단추 · 서클 한 칸 · 끝나면 확인창 「설계 지형을 라이다로 바꿈?」(이미 라이다 = force 다시 셈) · covered 거짓 = 바꿈 막고 알림 · 들어올 때 도는 일 이어 봄 - common_util_lidar_upload.drop_stale_filters — 갈음 뒤 안 지은 라이다 필터(옛 LAS) 모델 · 등고선 · 마스크 · 지면점 · manifest 줄 지움 · save_models drop = 그 행만(확정 행 남김) - 시험 test_637_lidar(필터 지움 · 행 지움 · B05 화면) · test_624_4b(올리기 = 공용 부품으로 옮김) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
5.8 KiB
TypeScript
143 lines
5.8 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` 만 씀) · 올리기 = 공용 `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<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 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 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<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 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 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 }),
|
|
};
|
|
}
|