사용자 지시(2026-09-06) — 노드를 제어해 계획노선을 고친다. 그동안 모달은 서버가 준 폴리라인 정점을 그대로 잡았는데, 거기에는 원호 위 점이 섞여 있어 편집 대상이 아님. - 그려 보이는 선(폴리라인, 원호 포함)과 잡는 점(노드)을 나눔. 선은 plannedLine, 노드는 서버가 내려 준 nodes. - 노드에 붙은 반지름·내각·법정 위반을 화면에 실음 — 위반 노드는 붉게, 상태줄에 곡선 수와 기준 R, 미달 개수. - 노드를 옮기면 폴리라인은 낡은 값이므로 지우고 직선으로 미리 보임. 곡선은 [확인] 때 서버가 같은 R 규칙으로 다시 끼움(계산을 두 벌로 짜지 않음). - API 타입에 nodes·min_radius_m·curve_count·violation_count 추가. 자체검증(공용 브라우저, 용화) — 모달이 노드 28개로 열리고 상태줄에 「노드 28개 · 초기 폴리라인 · 곡선 13곳(R 12m)」. 전에는 폴리라인 정점 142개를 잡았음. typecheck 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
106 lines
4.1 KiB
TypeScript
106 lines
4.1 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_Api_Replan.ts
|
|
* 계획노선 두 벌(예상노선·계획노선) 읽기와 노선 갈아 끼우기 요청.
|
|
*
|
|
* GET /projects/{id}/route/plan → 예상노선·계획노선 정점(사업지 좌표계 m)
|
|
* POST /projects/{id}/route/replan → 고친 계획노선으로 갈아 끼우고 재계산
|
|
* POST /projects/{id}/route/replan/reset → 계획노선을 예상노선으로 되돌리고 재계산
|
|
*
|
|
* 재계산은 배수유역부터 전 단계를 다시 도는 무거운 작업이라(용화 67측점 기준 3분대)
|
|
* 타임아웃을 길게 잡는다 — 기본값으로 두면 중간에 끊긴다.
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
|
|
/** 노선 재계산 대기 상한 — 배수유역 분석(90초대)까지 포함해 넉넉히 잡는다. */
|
|
const REPLAN_TIMEOUT_MS = 15 * 60 * 1000;
|
|
|
|
/** 사용자가 잡아 옮기는 제어점 하나 — 서버가 이 노드로 폴리라인을 만든다. */
|
|
export interface RoutePlanNode {
|
|
x: number;
|
|
y: number;
|
|
/** 직전·직후 구간이 이루는 내각(도). 끝점은 null. */
|
|
inner_angle_deg: number | null;
|
|
/** 이 자리에 끼운 원호 반지름(m). 곡선을 안 둔 자리(내각 155° 이상)는 null. */
|
|
radius_m: number | null;
|
|
tangent_m: number | null;
|
|
/** 법정 기준 위반 표시 — 값은 내되 막지 않는다. */
|
|
violations: string[];
|
|
}
|
|
|
|
export interface RoutePlanResponse {
|
|
status: string;
|
|
project_id: string;
|
|
/** 예상노선(원본) **점 묶음** [[x, y], …] — 사업지 좌표계(m). 폴리라인이 아니다. */
|
|
expected: Array<[number, number]>;
|
|
/** 계획노선 폴리라인(원호 포함) — 그려 보이는 선. 잡는 대상이 아니다. */
|
|
planned: Array<[number, number]>;
|
|
/** 잡아 옮기는 노드(꺾임점). 편집은 이것으로 한다(2026-09-06 사용자 지시). */
|
|
nodes: RoutePlanNode[];
|
|
/** 이 프로젝트에 적용한 법정 최소곡선반지름(m). */
|
|
min_radius_m: number;
|
|
curve_count: number;
|
|
violation_count: number;
|
|
/** 사용자가 고친 계획노선이 저장돼 있으면 true. */
|
|
edited: boolean;
|
|
}
|
|
|
|
export interface RouteReplanResponse {
|
|
status: string;
|
|
project_id: string;
|
|
route_id: number | null;
|
|
total_length_m: number | null;
|
|
vertex_count: number;
|
|
}
|
|
|
|
async function requestJson<T>(path: string, init: RequestInit, timeoutMs: number): Promise<T> {
|
|
const controller = new AbortController();
|
|
const timer = window.setTimeout(() => controller.abort(), timeoutMs);
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}${path}`, {
|
|
...init,
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json", ...(init.headers ?? {}) },
|
|
signal: controller.signal,
|
|
});
|
|
const payload = (await response.json()) as T & { message?: string };
|
|
if (!response.ok) throw new Error(payload.message ?? `HTTP ${response.status}`);
|
|
return payload;
|
|
} finally {
|
|
window.clearTimeout(timer);
|
|
}
|
|
}
|
|
|
|
/** 예상노선·계획노선을 함께 읽는다(편집 모달이 점선·실선으로 그린다). */
|
|
export async function fetchRoutePlan(projectId: string): Promise<RoutePlanResponse> {
|
|
return requestJson<RoutePlanResponse>(
|
|
`/projects/${projectId}/route/plan`,
|
|
{ method: "GET" },
|
|
60000,
|
|
);
|
|
}
|
|
|
|
/** 고친 계획노선으로 갈아 끼우고 배수유역부터 다시 계산한다. */
|
|
export async function replanRoute(
|
|
projectId: string,
|
|
vertices: Array<[number, number]>,
|
|
): Promise<RouteReplanResponse> {
|
|
return requestJson<RouteReplanResponse>(
|
|
`/projects/${projectId}/route/replan`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ vertices: vertices.map(([x, y]) => ({ x, y })) }),
|
|
},
|
|
REPLAN_TIMEOUT_MS,
|
|
);
|
|
}
|
|
|
|
/** 계획노선을 예상노선으로 되돌리고 같은 재계산을 돈다(노선 초기화). */
|
|
export async function resetRoutePlan(projectId: string): Promise<RouteReplanResponse> {
|
|
return requestJson<RouteReplanResponse>(
|
|
`/projects/${projectId}/route/replan/reset`,
|
|
{ method: "POST" },
|
|
REPLAN_TIMEOUT_MS,
|
|
);
|
|
}
|