- 규칙: 구조물 · 관 기준 측점 = 정수 m 사사오입(일반 측점 1 m 붙기 뺌) · 측점 심는 자리 TS 짝(plantedChainages ↔ resolve_extra_stations) · 같은 m 면 구조물 측점 하나(격자 자리 grid) · 떠나면 일반 측점(oneStationList ↔ _merge_irregular_into_longitudinal) - 서버: B05 [저장] · B06 저장 · [측점 만들기]가 목록 전체 다시 맞춤(빈 목록도 · 옛 글 · 횡단 파일 글) · 계획선 측점 줄 다시 셈 · 정본에 없는 횡단 행은 0.5 m 안 새 측점으로 옮기고 나머지 지움 · 옛 소수 측점 꼭짓점 · 편집은 정수 자리로 · 맞춤 실패는 저장을 안 막음 - 화면: 종단 그래프 · 3D · 표 · ▲▼ 한 목록(초안 반영) · C군 구간형 · 독립 기슭막이 시작 · 끝 측점 · B06 카드는 초안에 없는 옛 구조물 측점 숨김 · 고름은 같은 m 구조물 측점 먼저 · 폼 거리 칸 정수 표기 · C군 계산 구간 · 포장 나눔 경계 정수 - 옛 자료: [정수 맞춤] 단추 없앰 · 종단 보기 열 때 한 번 자동 정수 · 알림 한 줄 · [저장]하면 남음 - 시험: test_272_1 · test_272_2 새로 · 234_1 · 238_1 · b05_extra_stations_supplier · b06_patch_station · b05_station_roundtrip · b05_b06_save_roundtrip 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
62 lines
2.7 KiB
TypeScript
62 lines
2.7 KiB
TypeScript
/* =============================================================================
|
||
* 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;
|
||
}
|