/* ============================================================================= * D03_PlanAnalysis_Api.ts * D03 분석 화면 서버 요청(PLAN 530-3) — 지점 정본(D02) · 지형 받기 · 분석 · 진행 · 저장본 · [저장] · * 연도별 지형면(모델 행 · 격자). 끈 지점은 `points`(작업 좌표계 m)로 실어 보냄. * ========================================================================== */ import { API_BASE_URL } from "@config/config_frontend"; import { fetchCachedBytes } from "../A00_Common/b_asset_cache"; import { listSurfaceModels } from "../B04_PreProcess/B04_PreProcess_Api_Fetch"; import { type DtmGrid, parseDtmGrid } from "../common_util/common_util_surface_sampler"; import type { ChainRow, Vertex, YearAnalysis } from "./logic/D03_PlanAnalysis_Plan"; export interface PlanPointsView { year_start: number; year_end: number; epsg: number | null; years: Array<{ year: number; start: { x: number; y: number } | null; end: { x: number; y: number } | null; }>; } export interface PlanJob { running: boolean; percent: number; message: string | null; error: string | null; } export interface PlanAnalysisView { years: Array<{ year: number; start: Vertex; end: Vertex; analysis: YearAnalysis | null }>; chain: ChainRow[] | null; /** 등록 노선 시작 누가(m). */ start_chainage_m: number; /** 등록 노선 종료 누가(계획 목표 · m) — 없으면 null. */ end_chainage_target_m?: number | null; } export type PlanPoints = Array<{ year: number; start: Vertex; end: Vertex }>; async function request(path: string, init: RequestInit = {}): Promise { const response = await fetch(`${API_BASE_URL}${path}`, { ...init, credentials: "include", headers: { "Content-Type": "application/json", ...(init.headers ?? {}) }, }); const body = (await response.json().catch(() => ({}))) as T & { detail?: unknown }; if (!response.ok) { const detail = typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`; throw new Error(detail); } return body; } const base = (projectId: string): string => `/projects/${projectId}/plan`; const pointsBody = (points: PlanPoints | null): string => JSON.stringify(points ? { points } : {}); export const fetchPlanPoints = (projectId: string): Promise => request(`${base(projectId)}/points`); export const startPlanJob = ( projectId: string, kind: "terrain" | "analyze", points: PlanPoints | null, ): Promise => request(`${base(projectId)}/${kind}`, { method: "POST", body: pointsBody(points) }); export const fetchPlanProgress = ( projectId: string, ): Promise<{ terrain: PlanJob; analyze: PlanJob }> => request(`${base(projectId)}/progress`); export const fetchPlanAnalysis = ( projectId: string, points: PlanPoints | null, ): Promise => request( `${base(projectId)}/analysis` + (points ? `?points=${encodeURIComponent(JSON.stringify(points))}` : ""), ); export const savePlanAnalysis = ( projectId: string, choices: Record, points: PlanPoints | null, ): Promise<{ chain: ChainRow[] }> => request(`${base(projectId)}/analysis`, { method: "PUT", body: JSON.stringify(points ? { choices, points } : { choices }), }); export interface YearSurface { modelId: number; grid: DtmGrid; } /** 연도별 지형면 — 모델 행 경로 `…/models_<연도>/…` · 격자 = 분석과 같은 평활본. */ export async function fetchYearSurfaces(projectId: string): Promise> { const { models } = await listSurfaceModels(projectId); const out = new Map(); for (const model of models) { const year = Number(/models_(\d+)\//.exec(model.model_file_path ?? "")?.[1]); if (!year || out.has(year)) continue; const stamp = String(model.generation_params?.file_stamp ?? ""); const url = `${API_BASE_URL}/projects/${projectId}/surface/models/${model.id}/grid` + `?smooth=true&sig=${encodeURIComponent(stamp)}`; out.set(year, { modelId: model.id, grid: parseDtmGrid(await fetchCachedBytes(projectId, url)), }); } return out; }