266 lines
8.3 KiB
TypeScript
266 lines
8.3 KiB
TypeScript
/* =============================================================================
|
|
* B06_wf3_ProfileCross_Api_Fetch.ts
|
|
* 3차 워크플로우(종·횡단 생성) API 클라이언트
|
|
*
|
|
* 백엔드 계약 (B06_wf3_ProfileCross_Router.py):
|
|
* GET /api/projects/{project_id}/sections/context → 확정 경로 + 기본 옵션
|
|
* GET /api/projects/{project_id}/sections/{route_id} → 종단 요약 조회
|
|
* GET /api/projects/{project_id}/sections/{route_id}/detail → 종횡단 원시 샘플 조회
|
|
* POST /api/projects/{project_id}/sections/{route_id}/regenerate → 횡단 반폭 재생성
|
|
* POST /api/projects/{project_id}/sections/{route_id}/confirm → 종횡단 확정
|
|
*
|
|
* 규칙:
|
|
* - 모든 제어 상수는 config_frontend에서 참조 (하드코딩 금지).
|
|
* - 오류 응답 형식 {status:"error", message:"..."}을 Error로 변환.
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL, API_TIMEOUT_MS } from "@config/config_frontend";
|
|
|
|
export interface SectionOptionDefaults {
|
|
station_interval_m: number;
|
|
cross_half_width_m: number;
|
|
cross_sample_interval_m: number;
|
|
long_sample_interval_m: number;
|
|
vertical_exaggeration: number;
|
|
}
|
|
|
|
export interface SectionContextResponse {
|
|
project_id: string;
|
|
route_id: number | null;
|
|
filter_key: string | null;
|
|
method: string | null;
|
|
smooth: boolean | null;
|
|
crs_epsg: number | null;
|
|
defaults: SectionOptionDefaults;
|
|
}
|
|
|
|
/** 종단 요약 조회 결과 (SectionSummaryResponse) */
|
|
export interface SectionSummaryResponse {
|
|
status: string;
|
|
project_id: string;
|
|
route_id: number;
|
|
longitudinal: Record<string, unknown> | null;
|
|
length_m: number | null;
|
|
cross_section_count: number;
|
|
}
|
|
|
|
export interface SectionSample {
|
|
chainage_m?: number;
|
|
offset_m?: number;
|
|
elevation_m?: number | null;
|
|
z?: number | null;
|
|
valid: boolean;
|
|
}
|
|
|
|
export interface SectionStation {
|
|
station_id: string;
|
|
chainage_m: number;
|
|
label: string;
|
|
/** irregular = 사용자가 구조물용으로 추가한 비정규 측점(프론트 주입, 백엔드 미영속). */
|
|
kind: "bp" | "ep" | "regular" | "irregular";
|
|
/** 비정규 측점의 구조물 설명(백엔드가 확정 시 부여). 복귀 시 사이드바 목록 복원에 쓴다. */
|
|
structure?: string;
|
|
center_z: number | null;
|
|
azimuth_deg: number | null;
|
|
center_x: number;
|
|
center_y: number;
|
|
frame: { left_xy: [number, number] };
|
|
}
|
|
|
|
/** 계획선 샘플 (계획고와 지반고, 그 차이). */
|
|
export interface DesignProfileSample {
|
|
chainage_m: number;
|
|
elevation_m: number;
|
|
ground_elevation_m: number;
|
|
difference_m: number;
|
|
}
|
|
|
|
/** 절·성토 균형을 판정하는 구역 단위 결과. */
|
|
export interface DesignProfileSegment {
|
|
index: number;
|
|
start_chainage_m: number;
|
|
end_chainage_m: number;
|
|
cut_area_m2: number;
|
|
fill_area_m2: number;
|
|
balance_error_m2: number;
|
|
}
|
|
|
|
export interface DesignProfileSummary {
|
|
cut_area_m2: number;
|
|
fill_area_m2: number;
|
|
balance_error_m2: number;
|
|
max_grade_pct: number;
|
|
vertical_curve_count: number;
|
|
pvi_count: number;
|
|
balance_segment_count: number;
|
|
balanced: boolean;
|
|
main_direction: string;
|
|
suggested_elevation_offset_m: number | null;
|
|
warnings: string[];
|
|
}
|
|
|
|
/**
|
|
* 종단 계획선. 횡단 설계 기반 계획선이 추가될 수 있어 배열로 전달된다.
|
|
*/
|
|
export interface DesignProfile {
|
|
id: string;
|
|
name: string;
|
|
basis: string;
|
|
samples: DesignProfileSample[];
|
|
balance_segments: DesignProfileSegment[];
|
|
summary: DesignProfileSummary;
|
|
}
|
|
|
|
export interface LongitudinalSection {
|
|
length_m: number;
|
|
samples: SectionSample[];
|
|
stations: SectionStation[];
|
|
design_profiles?: DesignProfile[];
|
|
/**
|
|
* 계획선 변화점(PVI) 구조. B05가 편집 기준선으로 사용하며 `design_profiles`는
|
|
* 여기서 파생된다. 구 데이터에는 없을 수 있어 optional이다.
|
|
* 구조는 `B05_wf2_Route_UI_Profile_Alignment.ProfileAlignment`.
|
|
*/
|
|
profile_alignment?: unknown;
|
|
}
|
|
|
|
export interface CrossSection extends SectionStation {
|
|
samples: SectionSample[];
|
|
/** DB에 저장된 잠정 설계 지정(있을 때만). 상세 조회 시 얹혀 온다. */
|
|
design?: CrossDesign;
|
|
}
|
|
|
|
export interface SectionDetailResponse {
|
|
longitudinal: LongitudinalSection;
|
|
cross_sections: CrossSection[];
|
|
}
|
|
|
|
/** 종횡단 확정 결과 (SectionConfirmResponse) */
|
|
export interface SectionConfirmResponse {
|
|
status: string;
|
|
project_id: string;
|
|
route_id: number;
|
|
confirmed: boolean;
|
|
}
|
|
|
|
/** 측점 표준횡단 설계 지정값 (버튼 상태). */
|
|
export type GroundType = "soil" | "ripping_rock" | "blasting_rock";
|
|
export type SectionMode = "left_cut" | "right_cut" | "both_cut" | "both_fill";
|
|
export type DitchSide = "left" | "right";
|
|
|
|
/** 측점 표준횡단 설계 계산 결과(잠정치). data.design에 저장되는 구조와 동일. */
|
|
export interface CrossDesign {
|
|
ground_type: GroundType;
|
|
geometry_preset: "soil" | "rock";
|
|
section_mode: SectionMode;
|
|
ditch_side: DitchSide;
|
|
cut_slope_ratio: number;
|
|
fill_slope_ratio: number;
|
|
roadbed_width_m: number;
|
|
carriageway_width_m: number;
|
|
ditch: { width_m: number; depth_m: number };
|
|
design_elevation_m: number;
|
|
cut_area_m2: number;
|
|
fill_area_m2: number;
|
|
ditch_area_m2: number;
|
|
design_line: Array<{ offset_m: number; elevation_m: number }>;
|
|
}
|
|
|
|
export interface CrossDesignResponse {
|
|
status: string;
|
|
chainage_m: number;
|
|
design: CrossDesign;
|
|
}
|
|
|
|
export interface CrossDesignRequest {
|
|
chainage_m: number;
|
|
ground_type: GroundType;
|
|
section_mode: SectionMode;
|
|
ditch_side?: DitchSide | null;
|
|
}
|
|
|
|
/** 공통 fetch 헬퍼: 타임아웃 + 인증 헤더 + 오류 응답 변환. */
|
|
async function requestJson<T>(path: string, init: RequestInit): Promise<T> {
|
|
const controller = new AbortController();
|
|
const timeoutId = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
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(timeoutId);
|
|
}
|
|
}
|
|
|
|
/** 최신 확정 경로와 B04 확정값, config 기반 생성 기본값을 조회한다. */
|
|
export async function fetchSectionContext(projectId: string): Promise<SectionContextResponse> {
|
|
return requestJson<SectionContextResponse>(`/projects/${projectId}/sections/context`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
/** 경로의 종단면 요약을 조회한다. */
|
|
export async function getSections(
|
|
projectId: string,
|
|
routeId: number,
|
|
): Promise<SectionSummaryResponse> {
|
|
return requestJson<SectionSummaryResponse>(`/projects/${projectId}/sections/${routeId}`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
/** 경로의 SVG 렌더링용 종단·횡단 원시 샘플을 조회한다. */
|
|
export async function fetchSectionDetail(
|
|
projectId: string,
|
|
routeId: number,
|
|
): Promise<SectionDetailResponse> {
|
|
return requestJson<SectionDetailResponse>(`/projects/${projectId}/sections/${routeId}/detail`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
/** 횡단 반폭을 반영해 종횡단을 재생성·저장하고 갱신된 상세를 반환한다. */
|
|
export async function regenerateSections(
|
|
projectId: string,
|
|
routeId: number,
|
|
crossHalfWidthM: number,
|
|
): Promise<SectionDetailResponse> {
|
|
return requestJson<SectionDetailResponse>(
|
|
`/projects/${projectId}/sections/${routeId}/regenerate`,
|
|
{ method: "POST", body: JSON.stringify({ cross_half_width_m: crossHalfWidthM }) },
|
|
);
|
|
}
|
|
|
|
/** 측점 표준횡단 설계(지반유형·단면유형)를 즉시 계산·저장하고 잠정 결과를 반환한다. */
|
|
export async function computeCrossDesign(
|
|
projectId: string,
|
|
routeId: number,
|
|
request: CrossDesignRequest,
|
|
): Promise<CrossDesignResponse> {
|
|
return requestJson<CrossDesignResponse>(
|
|
`/projects/${projectId}/sections/${routeId}/cross-design`,
|
|
{ method: "POST", body: JSON.stringify(request) },
|
|
);
|
|
}
|
|
|
|
/** 경로의 종·횡단면을 확정한다. */
|
|
export async function confirmSections(
|
|
projectId: string,
|
|
routeId: number,
|
|
): Promise<SectionConfirmResponse> {
|
|
return requestJson<SectionConfirmResponse>(`/projects/${projectId}/sections/${routeId}/confirm`, {
|
|
method: "POST",
|
|
});
|
|
}
|