- Router_Design enforce 셋 · 넓힘 뒤 설계 — 파이썬은 입력 조립만 · 측점 묶음을 Node 한 번(B06_Section_Engine_Design_Node · cross.design) · Node 못 부르면 파이썬 짝 - 소단 구간 셈을 화면 파일에서 common_util_berm_spans.ts 로 · 서버도 같은 TS(cross.berm_spans) · Persist 가 다시 내보냄 - 값: 고정 자료 기준값 0 차(면적 · 계획고 · 누가토량 −17,570.7) · 설계선 점 13 곳 1e-4 m(반올림 동점 · 브레인 승인) — 기준값 시험에 판정 더함 - 시험: compute_cross_design 갈아 끼우던 시험 다섯을 묶음 자리로(까닭: [저장] 다시 셈이 묶음으로 부름) · 소단 글 시험 자리 옮김 - 등록부 · 위키 계산 목록 · B06 지침 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
4.5 KiB
TypeScript
108 lines
4.5 KiB
TypeScript
/* =============================================================================
|
|
* common_util_berm_spans.ts
|
|
* 소단 구간 — 구조물 목록(표 형상 berm)에서 구간 · 측점마다 제원(PLAN 472-4 C4 · 화면 파일
|
|
* `B06_Section_UI_Page_Persist.ts` 에서 옮김 · 그쪽이 다시 내보냄).
|
|
* 브라우저(시설 줄 · 다시 셈)와 서버 Node 입구(`design_calc_node` · 등록부 `cross.berm_spans`)가 같은 셈.
|
|
* 파이썬 짝 = `B06_Section_Engine_Structures_Wall.py` `load_berm_spans` · `berm_span_at`
|
|
* (Node 를 못 부를 때만 · 거울 시험 `test_272_berm_source.py`).
|
|
* ========================================================================== */
|
|
|
|
import {
|
|
BERM_DEFAULT_DEPTH_M,
|
|
BERM_DEFAULT_INTERVAL_M,
|
|
BERM_DEFAULT_SLOPE_DEG,
|
|
BERM_DEFAULT_WIDTH_M,
|
|
} from "./common_util_cross_berm";
|
|
import { catalogRow } from "./common_util_structure_catalog";
|
|
|
|
/** 측점 하나의 소단 제원 — 서버 payload 와 같은 이름을 쓴다(그대로 실어 보낸다). */
|
|
export interface BermSessionSpec {
|
|
width_m: number;
|
|
interval_m: number;
|
|
slope_deg: number;
|
|
/** 노면 끝 아래 깊이(m · ≥ 0 · 178-2). */
|
|
depth_m: number;
|
|
}
|
|
|
|
/**
|
|
* 사용자가 놓은 소단 **한 구간** — 종단 범위 + 제원.
|
|
*
|
|
* 사용자는 측점 하나가 아니라 **구간**에 놓는다(2026-09-07 확정: 「길이 + 기준측점 전·후」).
|
|
* 그래서 세션에는 구간 목록으로 두고, 측점별 제원은 읽는 자리에서 편다 — 구간을 측점으로
|
|
* 펴서 저장하면 나중에 「어디부터 어디까지 놓았나」를 되짚을 수 없다.
|
|
*/
|
|
export interface BermSpan extends BermSessionSpec {
|
|
start_m: number;
|
|
end_m: number;
|
|
}
|
|
|
|
/** 소단 = 표 형상 berm(323-4 · 파이썬 `load_berm_spans` 짝) — 종류 키로 안 가름. */
|
|
export const isBermType = (typeId: string): boolean => catalogRow(typeId)?.shape === "berm";
|
|
|
|
/**
|
|
* 구조물 목록에서 **소단 구간**을 뽑는다 — 사용자는 「구조물 배치」에서 놓는다
|
|
* (2026-09-07 사용자 확정: 별도 폼이 아니라 다른 옹벽·기슭막이와 같은 자리).
|
|
*
|
|
* 세션 열쇠 `berm` 은 그 결과를 담는 **사본**이다. 재계산(브라우저·서버)이 측점마다 값을
|
|
* 읽어야 하는데 구조물 목록은 비동기로 오므로, 목록이 바뀔 때마다 여기서 펴 두고 계산은
|
|
* 그 사본만 본다.
|
|
*/
|
|
export function bermSpansFromStructures(
|
|
structures: ReadonlyArray<{
|
|
type_id: string;
|
|
start_m?: number | null;
|
|
end_m?: number | null;
|
|
chainage_m?: number | null;
|
|
options?: Record<string, string | number>;
|
|
}>,
|
|
): BermSpan[] {
|
|
const spans: BermSpan[] = [];
|
|
for (const item of structures) {
|
|
if (!isBermType(item.type_id)) continue;
|
|
const anchor = item.chainage_m ?? item.start_m ?? null;
|
|
const start = item.start_m ?? anchor;
|
|
const end = item.end_m ?? anchor;
|
|
if (start === null || end === null) continue;
|
|
const options = item.options ?? {};
|
|
const number = (key: string, fallback: number): number => {
|
|
const parsed = Number(options[key]);
|
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
};
|
|
const width = number("width_m", BERM_DEFAULT_WIDTH_M);
|
|
const interval = number("interval_m", BERM_DEFAULT_INTERVAL_M);
|
|
if (width <= 0) continue; // 폭이 0이면 계단이 없다(간격은 성토 한 단에 안 씀 · 178-2)
|
|
// 깊이 = 노면 끝 아래 · 음수는 0(도로보다 높을 수 없음) · 빈 값은 기본 1.0
|
|
const rawDepth = Number(options.depth_m);
|
|
const depth =
|
|
options.depth_m === undefined || options.depth_m === "" || !Number.isFinite(rawDepth)
|
|
? BERM_DEFAULT_DEPTH_M
|
|
: Math.max(rawDepth, 0);
|
|
spans.push({
|
|
start_m: Math.min(start, end),
|
|
end_m: Math.max(start, end),
|
|
width_m: width,
|
|
interval_m: interval,
|
|
slope_deg: number("slope_deg", BERM_DEFAULT_SLOPE_DEG),
|
|
depth_m: depth,
|
|
});
|
|
}
|
|
return spans;
|
|
}
|
|
|
|
/** 그 측점을 덮는 소단 제원 — 없으면 null. 겹치면 먼저 놓은 것이 이긴다. */
|
|
export function bermSpecAt(spans: BermSpan[], chainageM: number): BermSessionSpec | null {
|
|
const found = spans.find(
|
|
(span) =>
|
|
chainageM >= Math.min(span.start_m, span.end_m) - 1e-6 &&
|
|
chainageM <= Math.max(span.start_m, span.end_m) + 1e-6,
|
|
);
|
|
return found
|
|
? {
|
|
width_m: found.width_m,
|
|
interval_m: found.interval_m,
|
|
slope_deg: found.slope_deg,
|
|
depth_m: found.depth_m ?? BERM_DEFAULT_DEPTH_M,
|
|
}
|
|
: null;
|
|
}
|