- 판 = resources/master_template/calc_sites.json(기본값 = 등록부 자리 그대로 · 전부 both) - common_util_calc_sites — 묶음 8 줄(등록부 45 #) · 읽기 · 검사(잠금 · 모르는 줄 · 값 = 400) · 통째 쓰기 - GET /api/dashboard/calc-sites(로그인) · PUT /api/dashboard/admin/calc-sites(시스템 관리자) · 바뀐 줄마다 CALC_SITE_CHANGE 시스템 로그 - 대시보드 시스템 설정 「계산 자리」 카드(시스템 관리자만 · ☰ 엔 없음) · 체크 창(칸 둘 · 잠긴 줄 까닭 · 지금 서버 N 줄 · 다음 열기부터) - design_calc_sites — B05 · B06 열 때 한 번 받음 · G1 server = ground 단계 안에서만 서버 표본(cross 격자 그대로) · D3 server = 워커 안 띄움 - 시험 test_309_2_calc_sites 새 · test_b01_settings_cards · test_225_drawer_shortcuts 덧 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
4.6 KiB
TypeScript
106 lines
4.6 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_Api_TerrainGrid.ts
|
|
* 설계 지형 격자를 받아 둔다 — 노선 편집이 지반고를 브라우저에서 바로 읽게(PLAN 344-1).
|
|
*
|
|
* GET /projects/{id}/route/terrain-sources → 설계 지형 · 격자 받을 자리(`grid`)
|
|
* GET /projects/{id}/surface/models/{mid}/grid → DTM 격자 원시 이진(ETag · IndexedDB)
|
|
* POST /projects/{id}/route/elevations → 격자를 못 쓸 때 계산 묶음 ground 단계의 서버 길
|
|
*
|
|
* 주소에 manifest 서명을 붙임 — 보관함은 옛 저장본을 먼저 내므로(`fetchCachedBytes`) 지형이
|
|
* 바뀐 뒤 옛 격자로 한 번 그리는 일을 막음. DTM 아닌 표현 · 못 받음 = null → 서버 표본 길.
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
import { fetchCachedBytes } from "../A00_Common/b_asset_cache";
|
|
import { calcOnServer } from "../A00_Common/design/design_calc_sites";
|
|
import { type DtmGrid, parseDtmGrid } from "../common_util/common_util_surface_sampler";
|
|
import type { GroundStageDeps } from "./logic/B05_Profile_Pipeline_RouteGround";
|
|
|
|
export interface DesignTerrainGrid {
|
|
/** 이 격자의 지형 — 편집 창 분석 지형이 이것과 같을 때만 씀. */
|
|
design: "las" | "sheet";
|
|
grid: DtmGrid;
|
|
}
|
|
|
|
interface TerrainSourcesPayload {
|
|
design?: "las" | "sheet";
|
|
grid?: { model_id: number; smooth: boolean; signature: string } | null;
|
|
}
|
|
|
|
const loaded = new Map<string, Promise<DesignTerrainGrid | null>>();
|
|
/** 받기가 끝난 격자 — 끝나기 전엔 칸이 없음(그동안 서버 길). */
|
|
const ready = new Map<string, DesignTerrainGrid | null>();
|
|
|
|
async function download(projectId: string): Promise<DesignTerrainGrid | null> {
|
|
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/route/terrain-sources`, {
|
|
credentials: "include",
|
|
});
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
const payload = (await response.json()) as TerrainSourcesPayload;
|
|
const info = payload.grid;
|
|
if (!info) return null;
|
|
const url =
|
|
`${API_BASE_URL}/projects/${projectId}/surface/models/${info.model_id}/grid` +
|
|
`?smooth=${info.smooth}&sig=${encodeURIComponent(info.signature)}`;
|
|
const grid = parseDtmGrid(await fetchCachedBytes(projectId, url));
|
|
return { design: payload.design === "sheet" ? "sheet" : "las", grid };
|
|
}
|
|
|
|
/** 설계 지형 격자 — 페이지 수명 동안 한 번만 받음 · 받다 실패하면 null(다음 부름에 다시 해 봄). */
|
|
export function loadDesignTerrainGrid(projectId: string): Promise<DesignTerrainGrid | null> {
|
|
let pending = loaded.get(projectId);
|
|
if (!pending) {
|
|
pending = download(projectId).then(
|
|
(value) => {
|
|
ready.set(projectId, value);
|
|
return value;
|
|
},
|
|
() => {
|
|
loaded.delete(projectId);
|
|
return null;
|
|
},
|
|
);
|
|
loaded.set(projectId, pending);
|
|
}
|
|
return pending;
|
|
}
|
|
|
|
/** 지금 쓸 수 있는 격자 — 받는 중이면 null 을 돌려주고 받기를 띄움(기다리지 않음). */
|
|
export function designTerrainGridNow(projectId: string): DesignTerrainGrid | null {
|
|
if (!ready.has(projectId)) void loadDesignTerrainGrid(projectId);
|
|
return ready.get(projectId) ?? null;
|
|
}
|
|
|
|
/** 한 번에 물을 수 있는 점 수 — 서버 `MAX_POINTS` 와 같은 값. */
|
|
const ELEVATION_CHUNK = 4000;
|
|
|
|
/** 계산 묶음 ground 단계에 넣을 격자 · 서버 길(PLAN 346-1). 서버 길도 소수 6자리 — 격자 길과 같은 값. */
|
|
export function groundStageDeps(
|
|
projectId: string,
|
|
): Pick<GroundStageDeps, "grid" | "remote" | "serverOnly"> {
|
|
return {
|
|
grid: () => designTerrainGridNow(projectId),
|
|
serverOnly: () => calcOnServer("ground_sample"),
|
|
async remote(points, terrain, signal) {
|
|
const chunks: Array<Array<[number, number]>> = [];
|
|
for (let i = 0; i < points.length; i += ELEVATION_CHUNK) {
|
|
chunks.push(points.slice(i, i + ELEVATION_CHUNK));
|
|
}
|
|
const answers = await Promise.all(
|
|
chunks.map(async (chunk) => {
|
|
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/route/elevations`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ points: chunk, terrain, decimals: 6 }),
|
|
signal,
|
|
});
|
|
if (!response.ok) throw new Error(`지반고 요청 실패: HTTP ${response.status}`);
|
|
return ((await response.json()) as { z: Array<number | null> }).z;
|
|
}),
|
|
);
|
|
return answers.flat();
|
|
},
|
|
};
|
|
}
|