231 lines
9.1 KiB
TypeScript
231 lines
9.1 KiB
TypeScript
/* =============================================================================
|
|
* D03_PlanAnalysis_Api.ts
|
|
* D03 분석 화면 서버 요청(PLAN 563-1) — 지형 받기 범위 · 받기 · 진행 ·
|
|
* 사업 전체 지형면 한 벌(`models_plan/` · 격자) · 그은 선 정본(562-2 GET · PUT network · 지점 · 기준점 함께 591-2 ·
|
|
* 지점 · 선 이름 · 노드 기록 · [저장] 기준점 594) · 주소 · 장소 찾기(594-4 공용 `/map/search`) ·
|
|
* 라이다 지형(624-4 · LAS 올리기 · 지은 지형 · 높이 격자) · 노선 세트 꼴(638 · 조각 `set` · `derived.sets`) ·
|
|
* 지도 겹 받기 = 공용 `A00_Common/map/map_layer_api`(624-3 · 다시 내놓음).
|
|
* ========================================================================== */
|
|
|
|
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 { PlanLine, PlanPiece, Vertex } from "./logic/D03_PlanAnalysis_Plan";
|
|
|
|
export interface BasePoint {
|
|
lon: number;
|
|
lat: number;
|
|
x: number;
|
|
y: number;
|
|
/** 지점 이름(594-1) — 없음 = 「지점 N」. */
|
|
name?: string;
|
|
}
|
|
|
|
/** 지점(긋기에 붙는 점 · 작업 좌표계 m + 경위도) · 등록 기준점(처음 받는 기준 · 591-2) ·
|
|
* epsg = 파일 머리 · 없으면 기준점 경도 TM 띠. */
|
|
export interface PlanBaseView {
|
|
epsg: number | null;
|
|
points: BasePoint[];
|
|
site: { lon: number; lat: number; address: string | null } | null;
|
|
}
|
|
|
|
/** 진행(595-3) — 끊김 = running 거짓 · percent < 100 · message 없음 · interrupted(서버가 다시 켜짐) ·
|
|
* 받은 적 없음 = percent 100 · message = 「n/5 …」 단계 글. */
|
|
export interface PlanJob {
|
|
running: boolean;
|
|
percent: number;
|
|
message: string | null;
|
|
error: string | null;
|
|
interrupted?: boolean;
|
|
}
|
|
|
|
/** 받기 답(595-2) — 시작 · 멀다(받은 범위에서 5 km 넘음) · 다른 TM 띠 — 둘은 확인 뒤 `reset`. */
|
|
export type TerrainStart =
|
|
| ({ status: "accepted" } & PlanJob)
|
|
| { status: "far"; distance_m: number; message: string }
|
|
| { status: "band"; epsg: number; center_epsg: number; message: string };
|
|
|
|
/** 위치 칸 기준점(경위도 · 캐시) · 새로 받기(옛 받은 범위 버림). */
|
|
export interface TerrainAim {
|
|
center?: { lon: number; lat: number } | null;
|
|
reset?: boolean;
|
|
}
|
|
|
|
/** 받기 전 범위(561-2) — 상자(작업 좌표계 m · 경위도 네 귀) · 받은 범위 · 도엽 · 칸 수 어림. */
|
|
export interface TerrainExtent {
|
|
epsg: number;
|
|
margin_m: number;
|
|
box_m: [number, number, number, number];
|
|
box: Array<[number, number]>;
|
|
have_m: [number, number, number, number] | null;
|
|
have: Array<[number, number]> | null;
|
|
covered: boolean;
|
|
sheets: Array<{ no: string; ring: Array<[number, number]>; stored: boolean }>;
|
|
cells: number;
|
|
/** 공용 창고 이어 붙일 도엽 수 · 한 번에 상한(넘으면 받기 413). */
|
|
store_sheets: number;
|
|
max_sheets: number;
|
|
}
|
|
|
|
/** 그은 선 서버 꼴(562-2) — 마크 = 조각 경계 · 조각 없는 구역 = 연도 없음. */
|
|
export interface ServerNetwork {
|
|
epsg: number;
|
|
/** 지점(PUT 몸만 · 빈 값 = 저장된 지점 그대로). */
|
|
points?: Vertex[];
|
|
/** 지점 이름(PUT 몸만 · points 와 같은 차례 · points 와 함께 · 빈 칸 = 「지점 N」 · 594-1). */
|
|
point_names?: Array<string | null>;
|
|
/** 기준점(PUT 몸만 · 바뀌면 서버가 등록 칸 `projects.site_*` 고침 · 주소 = 서버가 다시 찾음 · 594-3). */
|
|
site?: { lon: number; lat: number; address?: string | null };
|
|
lines: PlanLine[];
|
|
marks: Array<{ line: string; at_m: number }>;
|
|
pieces: PlanPiece[];
|
|
}
|
|
|
|
/** 서버가 [저장] · 읽기 때 센 세트(638-1) — 끊김 · 갈래 경고는 이 값으로만(화면 셈 없음). */
|
|
export interface PlanSetView {
|
|
key: string;
|
|
year: number;
|
|
no: number;
|
|
parts: number;
|
|
branch: boolean;
|
|
length_m: number;
|
|
pieces: string[];
|
|
}
|
|
|
|
export interface PlanNetworkView {
|
|
base: PlanBaseView;
|
|
network: ServerNetwork | null;
|
|
derived: {
|
|
lines: Array<{ id: string; length_m: number; stations: number[] }>;
|
|
pieces: Array<PlanPiece & { set_key: string; length_m: number; planned: Vertex[] }>;
|
|
/** 세트(638) — 열쇠 「연도-번호」 · 덩이 수(`parts` > 1 = 끊김) · 세트 안 갈래 · 조각 id. */
|
|
sets: PlanSetView[];
|
|
} | null;
|
|
/** 사업연도 시작(있으면). */
|
|
year_start?: number | null;
|
|
}
|
|
|
|
export interface PlanSurface {
|
|
modelId: number;
|
|
grid: DtmGrid;
|
|
}
|
|
|
|
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
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`;
|
|
|
|
/** 받기 전 범위 — `extra` = 더 덮을 점(그은 꺾임점) · `center` = 위치 칸 기준점(둘레 ±1 km). */
|
|
export const fetchTerrainExtent = (
|
|
projectId: string,
|
|
marginM: number | null,
|
|
extra: Vertex[],
|
|
center: { lon: number; lat: number } | null = null,
|
|
): Promise<TerrainExtent> => {
|
|
const query = new URLSearchParams();
|
|
if (marginM !== null) query.set("margin_m", String(marginM));
|
|
if (extra.length) query.set("extra", JSON.stringify(extra));
|
|
if (center) {
|
|
query.set("lon", String(center.lon));
|
|
query.set("lat", String(center.lat));
|
|
}
|
|
return request(`${base(projectId)}/terrain/extent?${query}`);
|
|
};
|
|
|
|
/** 받기 — 도엽 상한 넘음 = 오류(413 글). */
|
|
export const startTerrain = (
|
|
projectId: string,
|
|
marginM: number | null,
|
|
extra: Vertex[],
|
|
aim: TerrainAim = {},
|
|
): Promise<TerrainStart> =>
|
|
request(`${base(projectId)}/terrain`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ margin_m: marginM, extra, ...aim }),
|
|
});
|
|
|
|
export const fetchPlanProgress = (
|
|
projectId: string,
|
|
): Promise<{ terrain: PlanJob; lidar: PlanJob }> => request(`${base(projectId)}/progress`);
|
|
|
|
/** 지은 라이다 지형(624-4) — 없음 = `ready` 거짓 · `model_file` = 높이 격자 모델 행 경로. */
|
|
export interface PlanLidar {
|
|
ready: boolean;
|
|
epsg?: number;
|
|
files?: string[];
|
|
point_count?: number;
|
|
model_file?: string;
|
|
}
|
|
|
|
export const fetchPlanLidar = (projectId: string): Promise<PlanLidar> =>
|
|
request(`${base(projectId)}/lidar`);
|
|
|
|
/** LAS · LAZ(+ .prj 하나) 올리기 → 뒤에서 지형(진행 = `fetchPlanProgress().lidar`) · 못 받으면 서버 글 그대로 오류. */
|
|
export async function uploadPlanLidar(projectId: string, files: File[]): Promise<PlanJob> {
|
|
const form = new FormData();
|
|
for (const file of files) form.append("files", file);
|
|
const response = await fetch(`${API_BASE_URL}${base(projectId)}/lidar`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
body: form,
|
|
});
|
|
const body = (await response.json().catch(() => ({}))) as PlanJob & { detail?: unknown };
|
|
if (!response.ok)
|
|
throw new Error(typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`);
|
|
return body;
|
|
}
|
|
|
|
export const fetchPlanNetwork = (projectId: string): Promise<PlanNetworkView> =>
|
|
request(`${base(projectId)}/network`);
|
|
|
|
export const savePlanNetwork = (
|
|
projectId: string,
|
|
network: ServerNetwork,
|
|
): Promise<PlanNetworkView> =>
|
|
request(`${base(projectId)}/network`, { method: "PUT", body: JSON.stringify(network) });
|
|
|
|
/** 주소 · 장소 찾기 후보(594-4) — 주소 = 도로명 · 지번 · 장소 = 이름(`title`) + 주소. */
|
|
export interface PlaceHit {
|
|
kind: "address" | "place";
|
|
title: string;
|
|
address: string;
|
|
lon: number;
|
|
lat: number;
|
|
}
|
|
|
|
/** 주소 · 장소 찾기(서버 VWorld · 등록 D01 · 예정노선 위치 칸이 같이 씀) — 없음 = 빈 줄. */
|
|
export const searchPlaces = async (query: string): Promise<PlaceHit[]> =>
|
|
(await request<{ items: PlaceHit[] }>(`/map/search?q=${encodeURIComponent(query)}`)).items;
|
|
|
|
/** 사업 전체 지형면 — 모델 행 경로 `…/models_plan/…`(`file` = 그 경로 행 · 라이다 624-4) · 격자 = 평활본 · 없으면 null. */
|
|
export async function fetchPlanSurface(
|
|
projectId: string,
|
|
file?: string,
|
|
): Promise<PlanSurface | null> {
|
|
const { models } = await listSurfaceModels(projectId);
|
|
const model = models.find((entry) =>
|
|
file ? entry.model_file_path === file : /models_plan\//.test(entry.model_file_path ?? ""),
|
|
);
|
|
if (!model) return null;
|
|
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)}`;
|
|
return { modelId: model.id, grid: parseDtmGrid(await fetchCachedBytes(projectId, url)) };
|
|
}
|
|
|
|
/** 지도 겹 받기 = 공용(624-3 · B05 평면도 씀). */
|
|
export * from "../A00_Common/map/map_layer_api";
|