Files
Aislo/B05_wf2_Route/B05_wf2_Route_UI_Profile_Data.ts
T
eomsangdonandClaude Opus 5 b023698e38 refactor(B04/B05): 배수유역 저장소·API 일원화 + 종단 테이블 구조물 라인 연동
데이터 흐름 문제
- B04 해석 산출물 7개(7.2MB)가 B05로 전량 복사되고 있었고, 관 지점 정본
  (pipe_points.json)은 B05가 아예 참조하지 않았다. B04에서 확정한 관이 B05에
  보이지 않고 진입할 때마다 자동 배치로 되돌아갔다.
- 사본은 "B05 편집이 원본을 덮어쓴다"를 막으려던 것인데, 외곽선 편집을 제거한
  뒤로 B05는 아무것도 저장하지 않아 존재 이유가 사라졌다.

일원화
- B05 전용 모듈 3개 삭제: Engine_Drainage_Store / Engine_Drainage_Basin /
  Router_Drainage. 두 화면이 B04_wf1_Surface/drainage/ 한 폴더를 직접 읽는다.
- POST /drainage/basins 폐기 → GET/PUT /drainage/pipe-points ·
  POST /drainage/detail-basins 한 벌로 통합. 응답에 route_lonlat ·
  main_polygon_lonlat · flow_arrows · upstream_lonlat · inflow_hotspots를 추가해
  B05가 그리는 데 필요한 값을 같은 응답으로 받는다.
- 경로 확정 시 관 지점을 B04와 같은 저장소에 커밋한다.

B05 화면
- 유역 폴리곤을 눌러 고르기(지도에서도 선택), [집중유역] 토글, 관 개수·유역 수·
  종단 Z 출처 요약 줄 추가.
- 관 마커는 좌클릭으로만 잡는다(우클릭은 메뉴, 가운데 버튼은 팬).
- 유역 목록은 3행까지 보이고 나머지는 스크롤.

종단 테이블 구조물 라인
- 관 매설 지점을 "배관" 구조물로 실체화(origin: "pipe"). 정본은 관 지점 파일이며
  목록은 그 투영이라, 어디서 옮기든 pipeEditor 한 경로로 전파된다.
- 테이블 위 세로선을 끌어 이동(배관은 세부유역까지 재산정), 우클릭으로 배관
  추가/구조물 삭제.

정리
- B05_wf2_Route/drainage/ 사본과 debug/(삭제된 Subdivide 엔진 검증 스크립트) 삭제.
- config DRAINAGE_B05_DIRNAME 제거.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 23:37:17 +09:00

79 lines
2.9 KiB
TypeScript

/* =============================================================================
* 종단면 패널 자료 어댑터 (B05)
*
* 저장된 종단면 자료(`longitudinal_sections.data`)를 화면이 쓰는 모양으로 바꾸는 순수 함수들.
* 상태를 갖지 않으며 DOM도 만들지 않는다 — 패널 본체가 700줄 한계에 닿아 분리했다.
* ========================================================================== */
import type {
DesignProfile,
LongitudinalSection,
} from "../B06_wf3_ProfileCross/B06_wf3_ProfileCross_Api_Fetch";
import type { ProfileAlignment } from "./B05_wf2_Route_UI_Profile_Alignment";
export function readAlignment(data: LongitudinalSection): ProfileAlignment | null {
const candidate = data.profile_alignment as ProfileAlignment | undefined;
if (!candidate?.base_pvi?.length || !candidate.samples?.length) return null;
if (!Number.isFinite(candidate.policy?.default_curve_radius_m)) return null;
if (!candidate.stations || !candidate.segments || !candidate.curves) return null;
candidate.edits = {
station_offsets: candidate.edits?.station_offsets ?? {},
curve_radii: candidate.edits?.curve_radii ?? {},
};
return candidate;
}
/** 저장분이 구버전이라 편집을 붙일 수 없는 상태인가 (재계산 안내용). */
export function hasLegacyAlignment(data: LongitudinalSection): boolean {
return Boolean(data.profile_alignment) && readAlignment(data) === null;
}
/** 편집 결과를 종단면도 렌더러가 받는 계획선 형태로 감싼다. */
export function toDesignProfile(
alignment: ProfileAlignment,
original: DesignProfile | undefined,
): DesignProfile {
const balance = alignment.balance;
return {
id: original?.id ?? "design_grade_line",
name: original?.name ?? "계획선",
basis: original?.basis ?? "station_alignment",
samples: alignment.samples,
balance_segments: [
{
index: 0,
start_chainage_m: alignment.samples[0]?.chainage_m ?? 0,
end_chainage_m: alignment.samples[alignment.samples.length - 1]?.chainage_m ?? 0,
cut_area_m2: balance.cut_area_m2,
fill_area_m2: balance.fill_area_m2,
balance_error_m2: balance.net_area_m2,
},
],
summary: {
...(original?.summary ?? {
max_grade_pct: 0,
vertical_curve_count: 0,
pvi_count: 0,
balance_segment_count: 1,
main_direction: "none",
suggested_elevation_offset_m: null,
warnings: [],
}),
cut_area_m2: balance.cut_area_m2,
fill_area_m2: balance.fill_area_m2,
balance_error_m2: balance.net_area_m2,
balanced: balance.within_tolerance,
},
};
}
export function normalizedLongitudinal(data: LongitudinalSection): LongitudinalSection {
return {
...data,
samples: data.samples.map((sample: LongitudinalSection["samples"][number]) => ({
...sample,
elevation_m: sample.elevation_m ?? sample.z ?? null,
})),
};
}