/* ============================================================================= * B05_Profile_UI_Profile_Guard.ts * 종단도가 편집 · 경고에 쓰는 **셈만** — 계획선 샘플 보간, 평면 최소곡선반지름 경고 * (구조물 측점 맞추기는 측점 한 벌 `oneStationList` 로 갈음 · 272). * * 종단도 한 벌(`A00_Common/design/design_profile.ts`)이 700줄을 넘겨 떼어낸 조각이다 * (계획서 24-2c). 본문 로직과 수치는 그대로이고 화면 상태를 인자로 받는다. * ========================================================================== */ import type { SectionDetailResponse } from "../B06_Section/B06_Section_Api_Fetch"; import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment"; /** 계획선 샘플에서 chainage로 계획고·지반고를 되짚는 보간기(최소고 판정 입력). */ export function sampleAt( alignment: ProfileAlignment | null, chainageM: number, field: "elevation_m" | "ground_elevation_m", ): number | null { const samples = alignment?.samples; if (!samples?.length) return null; if (chainageM <= samples[0].chainage_m) return samples[0][field]; for (let i = 1; i < samples.length; i += 1) { if (chainageM <= samples[i].chainage_m) { const span = samples[i].chainage_m - samples[i - 1].chainage_m; const t = span <= 1e-12 ? 0 : (chainageM - samples[i - 1].chainage_m) / span; return samples[i - 1][field] + (samples[i][field] - samples[i - 1][field]) * t; } } return samples[samples.length - 1][field]; } /** * 법정 평면 최소곡선반지름을 밑도는 측점을 센다(별표2 Ⅰ.2.다.(1), 2026-09-06). * 반경은 횡단 측점마다 실려 오고(`plan_radius_m`), 하한은 계획선 정책이 들고 있다. * **경고만** 낸다 — 노선을 고칠지는 사용자 판단이다. */ export function planCurveWarning( alignment: ProfileAlignment | null, detail: SectionDetailResponse | null, ): { count: number; worstRadiusM: number; limitM: number; hairpin: number; } | null { const limit = alignment?.policy.min_plan_radius_m ?? 0; const sections = detail?.cross_sections ?? []; if (!limit || !sections.length) return null; const hairpinLimit = alignment?.policy.hairpin_min_radius_m ?? 0; let count = 0; let hairpin = 0; let worst = Number.POSITIVE_INFINITY; sections.forEach((section) => { const radius = section.plan_radius_m; if (typeof radius !== "number" || !Number.isFinite(radius) || radius >= limit) return; count += 1; worst = Math.min(worst, radius); if (hairpinLimit > 0 && radius < hairpinLimit) hairpin += 1; }); return count ? { count, worstRadiusM: worst, limitM: limit, hairpin } : null; }