독립 기슭막이(구조물 정본 D군 구간형)를 배수관 측점(pipe_points)의 시설 종류 "revetment"로 옮긴다. 관을 숨긴(hidden_pipe) 배관 세트로 얹혀 컴퓨트·렌더·패널· 연동·경사·3D·수량이 배관 경로를 그대로 탄다. - common_util_drainage_pipes: PIPE_FACILITY_REVET 추가, 여유고 0 - B06_Section_Engine_Culvert: _revet_set/_revet_side — 관 숨김 세트 생성 - B06_Section_Router: attach_revetments 제거(관 세트에 통합) - B05_Profile_Engine_Sections: 기슭막이는 시작·기준·종료 세 측점 심기 - B05_Profile_Structure_Types.json: revetment placement=point, managed_by=pipe_points, 설치 측에 "양쪽" 추가 - B05_Profile_UI_Drainage_Facility: 기슭막이 옵션 폼(설치 측·형태·높이·길이·전후·단 수) - B06_Section_UI_Cross_Culvert_Geom: 관 숨김 시 관경 하한·역경사 클램프·유출 가드 해제 - B06_Section_UI_Cross_Culvert_Wire: restrictToSide — 설치 측(좌/우)만 남기기 - B06_Section_UI_Cross_View: 옛 D군 경로 이중그리기 가드, appliedAdjust 되받기 - B06_Section_UI_Cross_Wall: 기슭막이 벽 공용 기하·그리기 층 신설 - B05_Profile_UI_Corridor_Structures: 3D도 같은 규칙(관 실린더 숨김, 관통 컷 없음) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
562 lines
21 KiB
TypeScript
562 lines
21 KiB
TypeScript
/* =============================================================================
|
|
* B04_PreProcess_Api_Fetch.ts
|
|
* 1차 워크플로우(지표면 분석) API 클라이언트
|
|
*
|
|
* 백엔드 계약 (B04_PreProcess_Router.py):
|
|
* POST /api/projects/{project_id}/surface/analyze → 분석 실행 + DB 기록
|
|
* GET /api/projects/{project_id}/surface/models → 모델 목록 조회
|
|
*
|
|
* 규칙:
|
|
* - 모든 제어 상수는 config_frontend에서 참조 (하드코딩 금지).
|
|
* - 오류 응답 형식 {status:"error", message:"..."}을 Error로 변환.
|
|
* ========================================================================== */
|
|
|
|
import { API_ANALYSIS_TIMEOUT_MS, API_BASE_URL, API_TIMEOUT_MS } from "@config/config_frontend";
|
|
|
|
/** 지표면 분석 실행 요청 (SurfaceAnalyzeRequest) */
|
|
export interface SurfaceAnalyzeRequest {
|
|
input_file_id: number;
|
|
source_filters?: string[];
|
|
methods?: string[];
|
|
force?: boolean;
|
|
}
|
|
|
|
/** 지표면 분석 실행 결과 (SurfaceAnalyzeResponse) */
|
|
export interface SurfaceAnalyzeResponse {
|
|
status: string;
|
|
project_id: string;
|
|
ground_summary: Record<string, unknown>;
|
|
manifest_status: string;
|
|
surface_model_ids: number[];
|
|
}
|
|
|
|
export interface SurfaceConfirmResponse {
|
|
status: string;
|
|
project_id: string;
|
|
model_id: number;
|
|
confirmed: boolean;
|
|
}
|
|
|
|
export interface SurfaceConfirmOptions {
|
|
smooth: boolean;
|
|
contour_interval_m: number;
|
|
}
|
|
|
|
/** 저장된 지표면 모델 요약 (SurfaceModelSummary) */
|
|
export interface SurfaceModelSummary {
|
|
id: number;
|
|
model_type: string;
|
|
status: string;
|
|
resolution_m: number | null;
|
|
model_file_path: string | null;
|
|
generation_params: Record<string, unknown> | null;
|
|
created_at: string | null;
|
|
}
|
|
|
|
export interface SurfaceInputFileSummary {
|
|
id: number;
|
|
file_type: string;
|
|
original_filename: string;
|
|
raw_file_path: string;
|
|
file_size_mb: number | null;
|
|
crs_epsg: number | null;
|
|
status: string | null;
|
|
created_at: string | null;
|
|
}
|
|
|
|
export interface SurfaceInputFileListResponse {
|
|
status: string;
|
|
project_id: string;
|
|
files: SurfaceInputFileSummary[];
|
|
}
|
|
|
|
export interface SurfaceBounds {
|
|
x_min: number;
|
|
x_max: number;
|
|
y_min: number;
|
|
y_max: number;
|
|
z_min: number;
|
|
z_max: number;
|
|
}
|
|
|
|
export interface SurfacePointCloudSampleResponse {
|
|
status: string;
|
|
project_id: string;
|
|
point_count: number;
|
|
sampled_count: number;
|
|
bounds: SurfaceBounds;
|
|
points: [number, number, number][];
|
|
rgb?: [number, number, number][];
|
|
}
|
|
|
|
export interface SurfaceGroundStatsResponse {
|
|
status: string;
|
|
project_id: string;
|
|
filters: Record<string, Record<string, unknown>>;
|
|
}
|
|
|
|
export interface SurfaceStatusResponse {
|
|
project_id: string;
|
|
status: "pending" | "in_progress" | "completed" | "failed";
|
|
model_count: number;
|
|
progress_percent: number;
|
|
current_stage: string;
|
|
message: string;
|
|
}
|
|
|
|
/** 지표면 모델 목록 응답 (SurfaceModelListResponse) */
|
|
export interface SurfaceModelListResponse {
|
|
status: string;
|
|
project_id: string;
|
|
models: SurfaceModelSummary[];
|
|
}
|
|
|
|
/** 확정 지표면 요약 (SurfaceConfirmedResponse).
|
|
* 포인트 배열 없이 확정 구성과 지형 가장자리만 담는다 — 진입 판정·준비화면·B05 공용. */
|
|
export interface SurfaceConfirmedResponse {
|
|
status: string;
|
|
project_id: string;
|
|
model_id: number | null;
|
|
source_filter: string | null;
|
|
method: string | null;
|
|
smooth: boolean | null;
|
|
contour_interval_m: number | null;
|
|
/** 확정 구성이 바뀌었는지 한 줄로 비교하기 위한 값. */
|
|
signature: string;
|
|
point_count: number | null;
|
|
bounds: {
|
|
x_min: number;
|
|
x_max: number;
|
|
y_min: number;
|
|
y_max: number;
|
|
z_min: number;
|
|
z_max: number;
|
|
} | null;
|
|
/** 계획노선(B03 CSV)의 평면 범위. 지도 초기 화면을 도로 중심으로 맞출 때 쓴다. */
|
|
route_bounds: { x_min: number; x_max: number; y_min: number; y_max: number } | null;
|
|
}
|
|
|
|
/** 공통 fetch 헬퍼: 타임아웃 + 인증 헤더 + 오류 응답 변환.
|
|
*
|
|
* `timeoutMs`를 주면 그 값으로 끊는다. 배수유역 격자 해석처럼 수십 초가 걸리는 요청은
|
|
* `API_ANALYSIS_TIMEOUT_MS`를 넘긴다 — 기본값으로 두면 계산 도중 abort 된다. */
|
|
async function requestJson<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
timeoutMs: number = API_TIMEOUT_MS,
|
|
): Promise<T> {
|
|
const controller = new AbortController();
|
|
const timeoutId = 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;
|
|
} catch (error) {
|
|
// AbortError 원문("signal is aborted without reason")은 원인을 알 수 없으니 바꿔 준다.
|
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
throw new Error(`요청이 ${Math.round(timeoutMs / 1000)}초 안에 끝나지 않았습니다.`);
|
|
}
|
|
throw error;
|
|
} finally {
|
|
window.clearTimeout(timeoutId);
|
|
}
|
|
}
|
|
|
|
/** 지표면 분석을 실행한다 (LAS 구조화 → 지면 필터 → 지표면 모델 생성). */
|
|
export async function analyzeSurface(
|
|
projectId: string,
|
|
request: SurfaceAnalyzeRequest,
|
|
): Promise<SurfaceAnalyzeResponse> {
|
|
return requestJson<SurfaceAnalyzeResponse>(`/projects/${projectId}/surface/analyze`, {
|
|
method: "POST",
|
|
body: JSON.stringify(request),
|
|
});
|
|
}
|
|
|
|
/** 프로젝트의 지표면 모델 목록을 조회한다. */
|
|
export async function listSurfaceModels(projectId: string): Promise<SurfaceModelListResponse> {
|
|
return requestJson<SurfaceModelListResponse>(`/projects/${projectId}/surface/models`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
/** 선택한 지표면 모델을 확정하고 WF1 단계를 완료한다. */
|
|
export async function confirmSurfaceModel(
|
|
projectId: string,
|
|
modelId: number,
|
|
options: SurfaceConfirmOptions,
|
|
): Promise<SurfaceConfirmResponse> {
|
|
return requestJson<SurfaceConfirmResponse>(`/projects/${projectId}/surface/confirm`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ model_id: modelId, ...options }),
|
|
});
|
|
}
|
|
|
|
export async function listSurfaceInputFiles(
|
|
projectId: string,
|
|
): Promise<SurfaceInputFileListResponse> {
|
|
return requestJson<SurfaceInputFileListResponse>(`/projects/${projectId}/surface/input-files`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
export async function fetchSurfacePointCloud(
|
|
projectId: string,
|
|
filter?: string,
|
|
): Promise<SurfacePointCloudSampleResponse> {
|
|
const query = filter ? `?filter=${encodeURIComponent(filter)}` : "";
|
|
return requestJson<SurfacePointCloudSampleResponse>(
|
|
`/projects/${projectId}/surface/point-cloud${query}`,
|
|
{
|
|
method: "GET",
|
|
},
|
|
);
|
|
}
|
|
|
|
/** 확정 지표면 구성 + 지형 가장자리만 조회한다(수 KB).
|
|
* 포인트클라우드 전체(수십 MB)를 받지 않고도 3D 좌표 환산에 필요한 값을 얻는다. */
|
|
export async function fetchConfirmedSurface(projectId: string): Promise<SurfaceConfirmedResponse> {
|
|
return requestJson<SurfaceConfirmedResponse>(`/projects/${projectId}/surface/confirmed`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
export async function fetchSurfaceGroundStats(
|
|
projectId: string,
|
|
): Promise<SurfaceGroundStatsResponse> {
|
|
return requestJson<SurfaceGroundStatsResponse>(`/projects/${projectId}/surface/ground-stats`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
export async function fetchSurfaceStatus(projectId: string): Promise<SurfaceStatusResponse> {
|
|
return requestJson<SurfaceStatusResponse>(`/projects/${projectId}/surface/status`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
export interface VWorldMeta {
|
|
x_min: number;
|
|
x_max: number;
|
|
y_min: number;
|
|
y_max: number;
|
|
width_meters: number;
|
|
height_meters: number;
|
|
center_x: number;
|
|
center_y: number;
|
|
lon_min: number;
|
|
lon_max: number;
|
|
lat_min: number;
|
|
lat_max: number;
|
|
}
|
|
|
|
export function getVWorldMapUrl(projectId: string, layerName: string): string {
|
|
return `${API_BASE_URL}/projects/${projectId}/vworld-map?layer_name=${layerName}`;
|
|
}
|
|
|
|
export async function fetchVWorldMeta(projectId: string, layerName: string): Promise<VWorldMeta> {
|
|
return requestJson<VWorldMeta>(`/projects/${projectId}/vworld-meta?layer_name=${layerName}`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
export async function fetchGisGeoJson(projectId: string, layer: string): Promise<any> {
|
|
return requestJson<any>(`/projects/${projectId}/geojson?layer=${encodeURIComponent(layer)}`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
/** 계획노선(B03 업로드 CSV)의 평면 점 목록. 사업지 좌표계(m) — 배경 지도 메타와 같은 좌표계다. */
|
|
export interface PlannedRouteResponse {
|
|
status: string;
|
|
points: Array<{ x: number; y: number }>;
|
|
}
|
|
|
|
/** 2D 지도에 계획선을 겹쳐 그리기 위한 점 목록을 받는다. 없으면 빈 목록이 온다. */
|
|
export async function fetchPlannedRoute(projectId: string): Promise<PlannedRouteResponse> {
|
|
return requestJson<PlannedRouteResponse>(`/projects/${projectId}/planned-route`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
/* ── 배수유역 분석 (B04_PreProcess_Router_Watershed.py) ────────────────────
|
|
* 관리자 확인용. 계획 노선(B03 CSV) + 도엽 등고선·세류선으로 유역을 끝까지 분석하고
|
|
* 결과를 영구저장소에 남긴다. 30초 안팎이 걸리므로 여기서 한 번만 돌린다.
|
|
* ------------------------------------------------------------------------ */
|
|
|
|
/** 관 매설 지점 1개. reason: stream=세류 교차, spacing=간격 보충, confirmed=사용자 확정. */
|
|
export interface WatershedPipe {
|
|
chainage_m: number;
|
|
x: number;
|
|
y: number;
|
|
lon: number;
|
|
lat: number;
|
|
reason: string;
|
|
stream_name: string | null;
|
|
}
|
|
|
|
/** 1차 배수유역 근거(단계 검증용). TIN·흐름 계산 없이 세류 상·하류 판정과 격자 범위만 준다. */
|
|
export interface WatershedAnalysis {
|
|
status: string;
|
|
project_id: string;
|
|
/** 분석에 쓴 계획 노선 파일명(B03 업로드). */
|
|
route_source: string;
|
|
radius_m: number;
|
|
/** 도로와 만난 세류선의 상류측 = 1차 영역의 기준선. */
|
|
upstream_lines: Array<Array<[number, number]>>;
|
|
/** 교차했으나 하류로 판정해 제외한 조각. 판정이 맞는지 눈으로 대조하는 용도. */
|
|
downstream_lines: Array<Array<[number, number]>>;
|
|
/** 상·하류 어느 망에도 이어지지 않아 제외한 세류 조각 수. */
|
|
no_contact_count: number;
|
|
/** 노선이 1차 영역 밖으로 나간 길이(m). 크면 반경을 올려야 한다는 신호. */
|
|
road_outside_m: number;
|
|
/** 1차 영역(상류 세류망 버퍼 합집합)의 외곽 링 목록. */
|
|
region_rings: Array<Array<[number, number]>>;
|
|
grid: {
|
|
cell_m: number;
|
|
rows: number;
|
|
cols: number;
|
|
/** bbox 전체 셀 수(참고값). */
|
|
bbox_cells: number;
|
|
/** 1차 영역에 걸쳐 실제로 생성된 셀 수. */
|
|
cells: number;
|
|
width_m: number;
|
|
height_m: number;
|
|
/** 격자 bbox 링. 화면은 이 사각형을 rows×cols로 나눠 셀 좌표를 얻는다. */
|
|
bbox_lonlat: Array<[number, number]>;
|
|
/** 실제 생성된 셀 구간 [행, 시작열, 끝열(포함)]. 낱개 셀 대신 구간으로 온다. */
|
|
row_spans: Array<[number, number, number]>;
|
|
};
|
|
/** 최외곽 적색 셀 주변 확장 결과. */
|
|
expansion: {
|
|
rounds: number;
|
|
/** 새로 추가한 셀에 적색이 없어 스스로 멈췄는가. */
|
|
closed: boolean;
|
|
added_cells: number;
|
|
/** 확장 전(1차 영역) 셀 수. */
|
|
initial_cells: number;
|
|
};
|
|
/** 셀별 흐름 방향과 도로 도달 여부. 등고선이 없어 판정을 못하면 null. */
|
|
flow: {
|
|
encoding: "base64-uint8";
|
|
/** 방위 분해능(32). 코드 0 = 화면 오른쪽, 시계방향 증가. */
|
|
azimuth_steps: number;
|
|
/** 제자리(더 낮은 이웃 없음)를 뜻하는 코드. */
|
|
sink_code: number;
|
|
/** 표고가 없어 판정 못한 셀 코드. */
|
|
invalid_code: number;
|
|
cells: number;
|
|
reaches_road: number;
|
|
no_road: number;
|
|
/** 격자에는 있으나 등고선 TIN 밖이라 표고가 없어 판정 못한 셀. */
|
|
unanalyzed: number;
|
|
/** 확정된 상류 세류망을 따라 흐름을 강제로 새긴 셀 수. */
|
|
burned: number;
|
|
outer_seeds: number;
|
|
interior_seeds: number;
|
|
/** 셀당 1바이트. 하위 6비트=32방위 코드(32=제자리, 33=무효), 0x80=도로 도달.
|
|
* 순서는 grid.row_spans를 행 → 구간 → 열 오름차순으로 훑은 순서와 같다. */
|
|
data: string;
|
|
} | null;
|
|
/** 2차 전체 배수유역 외곽선(= 분수령). 적색 셀 전체의 외곽. */
|
|
basin_polygon_lonlat: Array<[number, number]>;
|
|
basin_area_m2: number;
|
|
/** 도로 위 흐름 강도 — [누가거리 m, 그 구간으로 모이는 상류 면적 ㎡].
|
|
* 1m 간격 **구간 합**이다(평균·리샘플이 아니라 계산 원본 그대로). */
|
|
strength_profile: Array<[number, number]>;
|
|
/** 유입 집중점 — [누가거리 m, 유입면적 ㎡, 구역 번호, 구역 내 순위].
|
|
* 구역은 시점·기본 관·종점으로 자른 구간이며, 구역마다 `floor(길이/관 최대간격)+1`개를 뽑는다. */
|
|
inflow_hotspots: Array<[number, number, number, number]>;
|
|
/** 기본 관 매설 위치 — 도로 × 세류선 교차점. */
|
|
pipes: WatershedPipe[];
|
|
/** B05용 평균 흐름 화살표 — [lon, lat, 방위(도), 도로도달, 셀 수].
|
|
* 세류·도로 셀을 뺀 10m 블록 평균이라 사면 경향만 남는다. */
|
|
flow_arrows: Array<[number, number, number, boolean, number]>;
|
|
/** 화살표 사이 실제 간격(m). 화면이 화살표를 이보다 짧게 그려 서로 닿지 않게 한다. */
|
|
arrow_spacing_m: number;
|
|
/** 계산하지 않고 저장분을 그대로 돌려준 응답인지. */
|
|
from_cache: boolean;
|
|
/** 영구저장소에 남긴 검증용 GeoJSON 경로. */
|
|
saved_to: string | null;
|
|
}
|
|
|
|
/** 배수유역 분석 결과를 받는다.
|
|
*
|
|
* `refresh`를 주지 않으면 영구저장소에 남은 결과를 그대로 받아 즉시 끝난다.
|
|
* `refresh=true`면 처음부터 다시 계산하므로 수십 초가 걸린다. */
|
|
export async function fetchWatershedAnalysis(
|
|
projectId: string,
|
|
refresh = false,
|
|
): Promise<WatershedAnalysis> {
|
|
return requestJson<WatershedAnalysis>(
|
|
`/projects/${projectId}/drainage/primary-region?refresh=${refresh}`,
|
|
{ method: "GET" },
|
|
refresh ? API_ANALYSIS_TIMEOUT_MS : API_TIMEOUT_MS,
|
|
);
|
|
}
|
|
|
|
/** 도로 한 지점으로 들어오는 셀들의 외곽선(검토용). 계산이 아니라 저장된 귀속 배열 조회다. */
|
|
export interface RoadInflowResponse {
|
|
status: string;
|
|
chainage_m: number;
|
|
/** 기여 셀을 모은 도로 구간 길이(m). */
|
|
span_m: number;
|
|
cell_count: number;
|
|
area_m2: number;
|
|
/** 가장 먼 셀이 이 지점까지 흘러온 물길 길이(m). */
|
|
max_path_length_m: number;
|
|
/** 기여 셀 덩어리들의 바깥 링(WGS84). 큰 조각부터. */
|
|
rings_lonlat: Array<Array<[number, number]>>;
|
|
}
|
|
|
|
/** 취소는 지원하지 않는다(`requestJson`이 자체 타임아웃 신호를 쓴다) — 호출측에서 늦게 온
|
|
* 응답을 버리는 방식으로 처리한다. */
|
|
export async function fetchRoadInflow(
|
|
projectId: string,
|
|
chainageM: number,
|
|
): Promise<RoadInflowResponse> {
|
|
return requestJson<RoadInflowResponse>(
|
|
`/projects/${projectId}/drainage/road-inflow?chainage_m=${chainageM}`,
|
|
{ method: "GET" },
|
|
);
|
|
}
|
|
|
|
/* ── 상세 배수유역(관 매설 지점 + 세부유역 분할) ─────────────────────────── */
|
|
|
|
/** 관이 그 자리에 있는 이유. 백엔드 `common_util_drainage_pipes`가 정의처다. */
|
|
export type PipeSource = "stream" | "spacing" | "user";
|
|
|
|
/** 계곡 통과 시설 종류(2026-08-17 컨테이너 병합). 같은 계곡 교차점에서 유량·지형에
|
|
* 따라 택일한다 — 정의처는 백엔드 `common_util_drainage_pipes`. 교량은 임도용이 아니다. */
|
|
export type PipeFacility = "pipe" | "box_culvert" | "ford_pavement" | "ford_bridge" | "revetment";
|
|
|
|
export const PIPE_FACILITY_LABELS: ReadonlyArray<[PipeFacility, string]> = [
|
|
["pipe", "배수관"],
|
|
["box_culvert", "BOX암거"],
|
|
["ford_pavement", "물넘이포장"],
|
|
["ford_bridge", "세월교"],
|
|
["revetment", "기슭막이"],
|
|
];
|
|
|
|
export interface DetailPipePoint {
|
|
chainage_m: number;
|
|
lonlat: [number, number];
|
|
source: PipeSource;
|
|
/** 시설 종류 — 응답에서 생략되면 기본 배관. */
|
|
facility?: PipeFacility;
|
|
/** 기준점 앞뒤 구간(유입·유출 부속 폭). 없으면 폭 0 — 사용자가 필요할 때 벌린다. */
|
|
start_m?: number;
|
|
end_m?: number;
|
|
/** 유무·종류 수준의 시설 옵션(예: 세월교 관 종류/크기/수량). 상세 치수는 B06/B07. */
|
|
options?: Record<string, string | number>;
|
|
}
|
|
|
|
/** 편집·저장 요청에 싣는 관 1건 — 좌표(lonlat)는 서버가 다시 계산하므로 뺀다. */
|
|
export type DetailPipeInput = Omit<DetailPipePoint, "lonlat">;
|
|
|
|
export interface DetailBasin {
|
|
index: number;
|
|
chainage_m: number;
|
|
outlet_lonlat: [number, number];
|
|
polygon_lonlat: Array<[number, number]>;
|
|
area_m2: number;
|
|
relief_m: number;
|
|
flow_length_m: number;
|
|
/** 배수 유효직경(합리식 산출, mm). 강우량표가 아직 없으면 null → "미정" 표기. */
|
|
pipe_diameter_mm: number | null;
|
|
/** 산출 근거 — 홍수도달시간(분), 설계강우강도(mm/hr), 설계유량(m³/s, 2.0배 반영). */
|
|
tc_minutes?: number | null;
|
|
intensity_mm_hr?: number | null;
|
|
design_flow_m3s?: number | null;
|
|
/** 유효직경이 관 최대 규격 초과 — 세월교·물넘이·교량 검토 대상(임도설치규정 제12조). */
|
|
bridge_required?: boolean;
|
|
/** 필요 통수단면적(㎡) = 설계유량 / 유속. 물넘이·세월교 개략 단면의 출발값. */
|
|
required_area_m2?: number | null;
|
|
/** 유량 근거 추천 구조물(2026-08-17 사용자 확정) — pipe/box_culvert/ford_bridge. */
|
|
recommended_facility?: PipeFacility;
|
|
/** 추천 관경(㎜) — 배관일 때만. 레지스트리 선택지로 스냅한 값이다. */
|
|
recommended_diameter_mm?: number | null;
|
|
}
|
|
|
|
export interface DetailBasinResponse {
|
|
status: string;
|
|
project_id: string;
|
|
/** 종단 Z 출처(design_profile / route_points / surface / csv). */
|
|
z_source: string;
|
|
route_length_m: number;
|
|
max_spacing_m: number;
|
|
min_spacing_m: number;
|
|
/** 응답의 관 목록이 저장분에서 온 것인지. */
|
|
saved: boolean;
|
|
pipe_points: DetailPipePoint[];
|
|
basins: DetailBasin[];
|
|
pipe_count: number;
|
|
/** 도로 1m 구간별 유입 면적 — [누가거리, 면적]. 계획선 색칠에 쓴다. */
|
|
strength_profile: Array<[number, number]>;
|
|
/** 유입 집중점 — [누가거리, 유입면적, 구역번호, 구역 내 순위]. */
|
|
inflow_hotspots: Array<[number, number, number, number]>;
|
|
/** B04가 분석에 쓴 계획 노선 선형(lon/lat). */
|
|
route_lonlat: Array<[number, number]>;
|
|
/** 2차 전체 배수유역 외곽선 = 분수령. 해석 결과 그대로. */
|
|
main_polygon_lonlat: Array<[number, number]>;
|
|
/** B04 해석 격자 한 변(m). */
|
|
grid_cell_m: number;
|
|
/** 평균 흐름 화살표 — [x, y(사업지 CRS m), 방위(도), 도로도달, 셀 수]. */
|
|
flow_arrows: Array<[number, number, number, boolean, number]>;
|
|
/** 화살표 사이 실제 간격(m). */
|
|
arrow_spacing_m: number;
|
|
/** 유역 안쪽 상류 세류망 — 하이라이트 토글용. */
|
|
upstream_lonlat: Array<Array<[number, number]>>;
|
|
}
|
|
|
|
/** 저장된 관 매설 지점과 그 세부유역. 저장분이 없으면 백엔드가 자동 생성해 돌려준다. */
|
|
export async function fetchDetailPipePoints(projectId: string): Promise<DetailBasinResponse> {
|
|
return requestJson<DetailBasinResponse>(`/projects/${projectId}/drainage/pipe-points`, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
/** 편집 중인 관 목록으로 세부유역을 다시 나눈다(저장하지 않는다).
|
|
*
|
|
* `points`를 비우면 저장분을 무시하고 기본 관 + 자동 보충으로 되돌린다. */
|
|
export async function computeDetailBasins(
|
|
projectId: string,
|
|
points: DetailPipeInput[],
|
|
): Promise<DetailBasinResponse> {
|
|
return requestJson<DetailBasinResponse>(`/projects/${projectId}/drainage/detail-basins`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ points }),
|
|
});
|
|
}
|
|
|
|
/** 저장된 관 지점을 버리고 기본 관 + 자동 보충 배치로 되돌린다("초기화").
|
|
*
|
|
* 화면만 되돌리면 다시 들어왔을 때 옛 관이 살아나므로 저장분까지 지운다. */
|
|
export async function resetDetailPipePoints(projectId: string): Promise<DetailBasinResponse> {
|
|
return requestJson<DetailBasinResponse>(`/projects/${projectId}/drainage/pipe-points`, {
|
|
method: "DELETE",
|
|
});
|
|
}
|
|
|
|
/** 관 매설 지점을 정본으로 확정하고 세부유역 산출물까지 남긴다(모델 확정 시점). */
|
|
export async function saveDetailPipePoints(
|
|
projectId: string,
|
|
points: DetailPipeInput[],
|
|
): Promise<DetailBasinResponse> {
|
|
return requestJson<DetailBasinResponse>(`/projects/${projectId}/drainage/pipe-points`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ points }),
|
|
});
|
|
}
|