Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
112 lines
4.2 KiB
TypeScript
112 lines
4.2 KiB
TypeScript
/* =============================================================================
|
|
* common_util_structure_pavement.ts
|
|
* 노면공(G군) 구조물 → 횡단 측점 포장 제원(`section.pavement`).
|
|
* 파이썬 `common_util/common_util_structure_pavement.py` 의 **짝** — 거울 시험
|
|
* `resources/tester/test_b06_structure_pavement_mirror.py`.
|
|
*
|
|
* 서버는 저장분으로 · 브라우저는 저장 전 목록으로 같은 제원을 얹음.
|
|
* 두께 = options.height_m · 무늬 = options.pave_spec · 확폭 끈 구간 = options.widening_off.
|
|
* ========================================================================== */
|
|
|
|
/** 구조물 정본 한 건(필요한 칸만) — `structures.json` 과 같은 이름. */
|
|
export interface PavementStructureInput {
|
|
type_id: string;
|
|
chainage_m?: number | null;
|
|
start_m?: number | null;
|
|
end_m?: number | null;
|
|
options?: Record<string, unknown> | null;
|
|
}
|
|
|
|
/** 측점에 얹는 포장 한 건 — `section.pavement[]`. */
|
|
export interface PavementEntry {
|
|
type_id: string;
|
|
/** 포장사양 — 콘크리트 · 아스콘 · 쇄석(비면 표준 그림). */
|
|
spec: string | null;
|
|
/** 두께(m) — 노면선 아래로. 비면 표준 횡단 포장 두께. */
|
|
thickness_m: number | null;
|
|
start_m: number;
|
|
end_m: number;
|
|
/** false = 확폭 끈 구간(규격 폭 끝까지만). */
|
|
widening_on: boolean;
|
|
}
|
|
|
|
export interface PavementSpec extends Omit<PavementEntry, "widening_on"> {
|
|
widening_off: Array<[number, number]>;
|
|
}
|
|
|
|
/** 파이썬 `_EDGE_TOLERANCE_M` — C군 벽과 같은 값. */
|
|
const EDGE_TOLERANCE_M = 0.02;
|
|
|
|
const num = (value: unknown): number | null =>
|
|
typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
|
|
/** 「시작-끝,시작-끝」(측점 m) → 구간 목록. 못 읽는 조각은 버림(= 켬). */
|
|
export function wideningOffRanges(text: unknown): Array<[number, number]> {
|
|
const ranges: Array<[number, number]> = [];
|
|
for (const part of String(text ?? "").split(",")) {
|
|
const bounds = part.split("-");
|
|
if (bounds.length !== 2 || !bounds[0].trim() || !bounds[1].trim()) continue;
|
|
const start = Number(bounds[0]);
|
|
const end = Number(bounds[1]);
|
|
if (Number.isFinite(start) && Number.isFinite(end))
|
|
ranges.push([Math.min(start, end), Math.max(start, end)]);
|
|
}
|
|
return ranges;
|
|
}
|
|
|
|
/** G군 구조물 → 구간 제원(파이썬 `pavement_specs_from`). */
|
|
export function pavementSpecsFrom(
|
|
structures: readonly PavementStructureInput[],
|
|
typeIds: ReadonlySet<string>,
|
|
): PavementSpec[] {
|
|
const specs: PavementSpec[] = [];
|
|
for (const item of structures) {
|
|
if (!typeIds.has(item.type_id)) continue;
|
|
const anchor = num(item.chainage_m);
|
|
const start = num(item.start_m) ?? anchor;
|
|
const end = num(item.end_m) ?? anchor;
|
|
if (start === null || end === null) continue;
|
|
const options = (item.options ?? {}) as Record<string, unknown>;
|
|
const spec = options.pave_spec;
|
|
specs.push({
|
|
type_id: item.type_id,
|
|
spec: typeof spec === "string" && spec ? spec : null,
|
|
thickness_m: num(options.height_m),
|
|
start_m: Math.min(start, end),
|
|
end_m: Math.max(start, end),
|
|
widening_off: wideningOffRanges(options.widening_off),
|
|
});
|
|
}
|
|
return specs;
|
|
}
|
|
|
|
const inside = (chainage: number, start: number, end: number): boolean =>
|
|
start - EDGE_TOLERANCE_M <= chainage && chainage <= end + EDGE_TOLERANCE_M;
|
|
|
|
/** 범위 안 측점에 `pavement` 목록을 얹음(파이썬 `attach_pavement_specs`). 얹은 측점 수. */
|
|
export function attachPavementSpecs(
|
|
sections: Array<Record<string, unknown>>,
|
|
specs: readonly PavementSpec[],
|
|
): number {
|
|
let attached = 0;
|
|
for (const section of sections) {
|
|
const chainage = num(section.chainage_m);
|
|
if (chainage === null) continue;
|
|
const found: PavementEntry[] = specs
|
|
.filter((spec) => inside(chainage, spec.start_m, spec.end_m))
|
|
.map((spec) => ({
|
|
type_id: spec.type_id,
|
|
spec: spec.spec,
|
|
thickness_m: spec.thickness_m,
|
|
start_m: spec.start_m,
|
|
end_m: spec.end_m,
|
|
widening_on: !spec.widening_off.some(([start, end]) => inside(chainage, start, end)),
|
|
}));
|
|
if (found.length) {
|
|
section.pavement = found;
|
|
attached += 1;
|
|
}
|
|
}
|
|
return attached;
|
|
}
|