- 579-3: D02 시점 · 종점 목록 받음 · 점 그림 · 맞춤 · 등고선 범위 · 「범위 밖」 판정에 시점 · 종점 · 들어올 때 받은 범위가 못 덮으면 바로 받기(진행 막대 · 직전 실패면 안 함) - 선 이름 = 갈래 없는 선 「본선」(둘 이상 「본선 N」) · 갈래 「갈래 N」 — TS lineNames ↔ D04 line_names 짝 · 거울 시험 - 580-1: [긋기] 하나 — 시점 · 종점(끝낼 때 뒤집어 No.0 = 반대쪽) · 선 꼬리 · 선 머리 · 선 위(갈래)에서 시작 · 머리가 다른 선 꼬리에 닿으면 한 선 · 꺾임점이면 갈래 · 꺾임점 끌기 · 더하기 · 지우기(마크는 구간 비율) - 580-2: 미리보기 글 = 거리 + 가상 종단 기울기 % · ° · 기울기 고정 칸 옆 ° 환산 · 고른 선 [방향 바꾸기] - 580-3: 지도에서 선 · 조각 고름(좌측 줄 밝힘) · Delete · [선 지움] · 선 통째 연도 · 긋는 해(긋는 중 바꾸면 다음 점부터) · [연도 구분] 뒤 연도 고름 바로 · 마크 끌기 - 새 파일: D03_PlanAnalysis_UI_Edit.ts(고르기 · 고치기) · D03_PlanAnalysis_UI_DrawPaint.ts(그림 · 집기 — UI_Draw 700 줄 넘어 나눔) - UI_Map = 겹 꼴에 release · pointerup · 끌기 중 포인터 잡기 · [?] 안내 줄(그리기 본체 draw() 는 sub2 몫이라 손 안 댐) - 시험: 530-3 새 꼴 함수 · 서버 derive 대조 · 선 이름 거울 / 553-4 그림 자리 옮김 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
154 lines
5.8 KiB
TypeScript
154 lines
5.8 KiB
TypeScript
/* =============================================================================
|
|
* D03_PlanAnalysis_Api.ts
|
|
* D03 분석 화면 서버 요청(PLAN 563-1) — 지형 받기 범위 · 받기 · 진행 ·
|
|
* 사업 전체 지형면 한 벌(`models_plan/` · 격자) · 그은 선 정본(562-2 GET · PUT network · 응답에 D02 시점 · 종점) ·
|
|
* 지도 겹(569 — 지적도 · 도엽 겹 GeoJSON 경위도 · 급경사 그림).
|
|
* ========================================================================== */
|
|
|
|
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 { GeoJsonCollection } from "../B04_PreProcess/B04_PreProcess_UI_MapRender";
|
|
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;
|
|
}
|
|
|
|
/** D02 정본 — 시점 목록 · 종점 목록(작업 좌표계 m + 경위도 · 579-1). */
|
|
export interface PlanBaseView {
|
|
epsg: number | null;
|
|
starts: BasePoint[];
|
|
ends: BasePoint[];
|
|
}
|
|
|
|
export interface PlanJob {
|
|
running: boolean;
|
|
percent: number;
|
|
message: string | null;
|
|
error: string | null;
|
|
}
|
|
|
|
/** 받기 전 범위(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;
|
|
}
|
|
|
|
/** 그은 선 서버 꼴(562-2) — 마크 = 조각 경계 · 조각 없는 구역 = 연도 없음. */
|
|
export interface ServerNetwork {
|
|
epsg: number;
|
|
lines: PlanLine[];
|
|
marks: Array<{ line: string; at_m: number }>;
|
|
pieces: PlanPiece[];
|
|
}
|
|
|
|
export interface PlanNetworkView {
|
|
/** D02 정본 전체 시점 · 종점. */
|
|
base: PlanBaseView;
|
|
network: ServerNetwork | null;
|
|
derived: {
|
|
lines: Array<{ id: string; length_m: number; stations: number[] }>;
|
|
pieces: Array<PlanPiece & { length_m: number; planned: Vertex[] }>;
|
|
} | null;
|
|
/** 옛 연도 노선을 선으로 바꾼 것인가. */
|
|
legacy: boolean;
|
|
/** 사업연도 시작(있으면). */
|
|
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` = 더 덮을 점(그은 꺾임점). */
|
|
export const fetchTerrainExtent = (
|
|
projectId: string,
|
|
marginM: number | null,
|
|
extra: Vertex[],
|
|
): Promise<TerrainExtent> => {
|
|
const query = new URLSearchParams();
|
|
if (marginM !== null) query.set("margin_m", String(marginM));
|
|
if (extra.length) query.set("extra", JSON.stringify(extra));
|
|
return request(`${base(projectId)}/terrain/extent?${query}`);
|
|
};
|
|
|
|
export const startTerrain = (
|
|
projectId: string,
|
|
marginM: number | null,
|
|
extra: Vertex[],
|
|
): Promise<PlanJob> =>
|
|
request(`${base(projectId)}/terrain`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ margin_m: marginM, extra }),
|
|
});
|
|
|
|
export const fetchPlanProgress = (projectId: string): Promise<{ terrain: PlanJob }> =>
|
|
request(`${base(projectId)}/progress`);
|
|
|
|
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) });
|
|
|
|
/** 사업 전체 지형면 — 모델 행 경로 `…/models_plan/…` · 격자 = 평활본 · 없으면 null. */
|
|
export async function fetchPlanSurface(projectId: string): Promise<PlanSurface | null> {
|
|
const { models } = await listSurfaceModels(projectId);
|
|
const model = models.find((entry) => /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)) };
|
|
}
|
|
|
|
/** 지도 겹 그림(569-1) — 작업 좌표계 m 상자(칸 가장자리) · data URL(위 = 북) · 단계 색 · 넓이 비율. */
|
|
export interface PlanRaster {
|
|
box_m: [number, number, number, number];
|
|
image: string;
|
|
bands: Array<{ min_deg: number; max_deg: number; color: string; ratio: number }>;
|
|
}
|
|
|
|
export const fetchPlanCadastral = (projectId: string): Promise<GeoJsonCollection> =>
|
|
request(`${base(projectId)}/layers/cadastral`);
|
|
|
|
/** 도엽 겹(`도로중심선` 등) — 없으면 서버가 받은 도엽에서 풀어 냄. */
|
|
export const fetchPlanSheetLayer = (projectId: string, key: string): Promise<GeoJsonCollection> =>
|
|
request(`${base(projectId)}/layers/sheet?key=${encodeURIComponent(key)}`);
|
|
|
|
export const fetchPlanRaster = (projectId: string, kind: "slope"): Promise<PlanRaster> =>
|
|
request(`${base(projectId)}/layers/raster?kind=${kind}`);
|