- 직선화 첫 클릭이 직선 안이면 직선 통째 고름(붉은 강조)으로 새던 갈림 지움 · 빈 곳 클릭 측점선에 붙음 - 이미 직선인 범위 = 부분 구간 고름(보라 띠 · 안내) · ▲▼ = 가운데 + 양 끝 기본 L 곡선 - 직선화 · 자유지정 끝 R 이 저장 자동 R 이면 지움 = 끝 곡선 L 20 · 사용자 R 은 둠 - 법정 미달 판정 오차 1e-3 짝(TS · 파이썬) — L 19.99998 거짓 미달 없앰 - 새 도우미 design_profile_range_band — 직선화 · 자유지정 범위 단계 실시간 띠 · 시작–끝 · 길이 글 - Esc = 시작만 취소 · 모드 유지 · 고름 풀기로 안 샘 - 시험 test_271_straighten_pick (거울 포함) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
87 lines
3.8 KiB
TypeScript
87 lines
3.8 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_Profile_CurveLaw.ts
|
|
* 종단곡선 초기값 직선 · 법정 미달 판정(432 · 요구 262).
|
|
*
|
|
* 짝: 파이썬 `B05_Profile_Engine_Grade_CurveLaw.py`(`flat_bends` · `straighten_held` ·
|
|
* `vertical_curve_shortfalls`) · config `FOREST_ROAD_PROFILE_ALIGNMENT` 의 `default_curve_length_m` ·
|
|
* `initial_straight_radius_m` — 거울 시험 `test_432_1_curve_law.py`.
|
|
*
|
|
* - 초기값에서만 기본 L 기준 R > 500 m(|대수차| < L/500) 꼭짓점 묶음을 앞뒤 경계(진짜 꺾임 · 시종점)를
|
|
* 잇는 직선 위로(438 · 종단 직선화 단추와 같은 규칙) — 직선 판정(426)이라 곡선 없음 · 되풀이는
|
|
* `passThrough`(`B05_Profile_Grade_Ground.ts`).
|
|
* - 법정 최소 R · L 미달은 막지 않고 경고 글만 · 비포장 대수차 5 % 이하(생략 가능)는 뺌.
|
|
* ========================================================================== */
|
|
|
|
import type { AlignmentCurve, AlignmentPolicy } from "./B05_Profile_UI_Profile_Alignment";
|
|
|
|
/** 기본 종단곡선 길이 L(m) — config `default_curve_length_m` 짝(새 정책에 담겨 옴 · 옛 저장분은 저장값). */
|
|
export const DEFAULT_CURVE_LENGTH_M = 20;
|
|
/** 초기값 직선 문턱 R(m) — config `initial_straight_radius_m` 짝. */
|
|
export const INITIAL_STRAIGHT_RADIUS_M = 500;
|
|
/** 법정 미달 판정 오차(m) — R = L/Δ 를 반올림 높이로 되셈한 L 19.99998 거짓 미달 막음(271-1) · 파이썬 짝. */
|
|
export const SHORTFALL_TOLERANCE_M = 1e-3;
|
|
|
|
/** 안쪽 꼭짓점 중 기본 L 기준 R > 500 m(|대수차| < L/500) 인 것 — 파이썬 `flat_bends` · 시종점 false. */
|
|
export function flatBends(
|
|
baseS: ReadonlyArray<number>,
|
|
baseZ: ReadonlyArray<number>,
|
|
curveLengthM: number | null | undefined,
|
|
): boolean[] {
|
|
const flat = baseS.map(() => false);
|
|
if (!curveLengthM || baseS.length < 3) return flat;
|
|
const limit = curveLengthM / INITIAL_STRAIGHT_RADIUS_M;
|
|
const grade = (index: number): number => {
|
|
const span = baseS[index + 1] - baseS[index];
|
|
return (baseZ[index + 1] - baseZ[index]) / (span > 0 ? span : 1);
|
|
};
|
|
for (let index = 1; index < baseS.length - 1; index += 1) {
|
|
flat[index] = Math.abs(grade(index) - grade(index - 1)) < limit;
|
|
}
|
|
return flat;
|
|
}
|
|
|
|
/** `held` 꼭짓점 묶음마다 앞뒤 경계(held 아닌 꼭짓점)를 잇는 직선 위로 — 파이썬 `straighten_held`. */
|
|
export function straightenHeld(
|
|
baseS: ReadonlyArray<number>,
|
|
baseZ: ReadonlyArray<number>,
|
|
held: ReadonlyArray<boolean>,
|
|
): number[] {
|
|
const z = [...baseZ];
|
|
let index = 1;
|
|
while (index < z.length - 1) {
|
|
if (!held[index]) {
|
|
index += 1;
|
|
continue;
|
|
}
|
|
let end = index;
|
|
while (end + 1 < z.length - 1 && held[end + 1]) end += 1;
|
|
const [a, b] = [index - 1, end + 1];
|
|
for (let k = index; k < b; k += 1) {
|
|
z[k] = z[a] + ((z[b] - z[a]) * (baseS[k] - baseS[a])) / (baseS[b] - baseS[a]);
|
|
}
|
|
index = b;
|
|
}
|
|
return z;
|
|
}
|
|
|
|
/** 법정 최소 R · L 을 못 채운 종단곡선 경고 글 — 파이썬 `vertical_curve_shortfalls` 와 같은 글. */
|
|
export function verticalCurveShortfalls(
|
|
curves: ReadonlyArray<AlignmentCurve>,
|
|
policy: AlignmentPolicy,
|
|
): string[] {
|
|
const minR = policy.min_vertical_radius_m ?? 0;
|
|
const minL = policy.min_curve_length_m ?? 0;
|
|
if (minR <= 0 && minL <= 0) return [];
|
|
return curves
|
|
.filter((curve) => !curve.omitted && !curve.skip_allowed)
|
|
.filter(
|
|
(curve) =>
|
|
curve.r_m < minR - SHORTFALL_TOLERANCE_M || curve.l_m < minL - SHORTFALL_TOLERANCE_M,
|
|
)
|
|
.map(
|
|
(curve) =>
|
|
`${curve.chainage_m.toFixed(1)}m: 종단곡선 R ${curve.r_m.toFixed(0)}m · L ${curve.l_m.toFixed(1)}m — ` +
|
|
`법정 최소 R ${minR.toFixed(0)}m · L ${minL.toFixed(0)}m 미달`,
|
|
);
|
|
}
|