From 7a26d218815c56f610ddf4d0838cf4eb27afc766 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 16 Oct 2026 11:28:19 +0900 Subject: [PATCH] =?UTF-8?q?feat(=EA=B3=84=EC=82=B0=EB=AC=B6=EC=9D=8C):=203?= =?UTF-8?q?46-1=20=EA=B2=A9=EC=9E=90=20=EC=A4=80=EB=B9=84=20=E2=80=94=20de?= =?UTF-8?q?signTerrainGridNow(=EB=B0=9B=EB=8A=94=20=EC=A4=91=EC=97=94=20nu?= =?UTF-8?q?ll=20=C2=B7=20=EC=84=9C=EB=B2=84=20=EA=B8=B8=20=C2=B7=20?= =?UTF-8?q?=EB=B0=9B=EC=9D=80=20=EB=92=A4=20=EB=B8=8C=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=EC=A0=80=20=EA=B8=B8)=20=C2=B7=20groundStageDeps(=EA=B2=A9?= =?UTF-8?q?=EC=9E=90=20=C2=B7=20elevations=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EA=B8=B8=204,000=EC=A0=90=EC=94=A9=20=C2=B7=20AbortSignal)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf --- B05_Profile/B05_Profile_Api_TerrainGrid.ts | 54 ++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/B05_Profile/B05_Profile_Api_TerrainGrid.ts b/B05_Profile/B05_Profile_Api_TerrainGrid.ts index ce49bbc13..647f5dcad 100644 --- a/B05_Profile/B05_Profile_Api_TerrainGrid.ts +++ b/B05_Profile/B05_Profile_Api_TerrainGrid.ts @@ -4,6 +4,7 @@ * * 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 → 서버 표본 길. @@ -12,6 +13,7 @@ import { API_BASE_URL } from "@config/config_frontend"; import { fetchCachedBytes } from "../A00_Common/b_asset_cache"; import { type DtmGrid, parseDtmGrid } from "../common_util/common_util_surface_sampler"; +import type { GroundStageDeps } from "./logic/B05_Profile_Pipeline_RouteGround"; export interface DesignTerrainGrid { /** 이 격자의 지형 — 편집 창 분석 지형이 이것과 같을 때만 씀. */ @@ -25,6 +27,8 @@ interface TerrainSourcesPayload { } const loaded = new Map>(); +/** 받기가 끝난 격자 — 끝나기 전엔 칸이 없음(그동안 서버 길). */ +const ready = new Map(); async function download(projectId: string): Promise { const response = await fetch(`${API_BASE_URL}/projects/${projectId}/route/terrain-sources`, { @@ -45,11 +49,53 @@ async function download(projectId: string): Promise { export function loadDesignTerrainGrid(projectId: string): Promise { let pending = loaded.get(projectId); if (!pending) { - pending = download(projectId).catch(() => { - loaded.delete(projectId); - return null; - }); + 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). 서버 길 값은 mm 자리(응답 그대로). */ +export function groundStageDeps(projectId: string): Pick { + return { + grid: () => designTerrainGridNow(projectId), + async remote(points, terrain, signal) { + const chunks: Array> = []; + 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 }), + signal, + }); + if (!response.ok) throw new Error(`지반고 요청 실패: HTTP ${response.status}`); + return ((await response.json()) as { z: Array }).z; + }), + ); + return answers.flat(); + }, + }; +}