- A00_Common/design/design_profile.ts 본체 · design_profile_slots.ts 판 자리 약속 · Profile_Panel 827 → 감싸개 37줄(Guard · Drag 로 나눔) - B06: 옛 붙박이 종단(Section_View_Draw · Chart · Panel) 걷음 · Section_View_Profile 이음(선택 · 구조물 편집 · 카드 갱신 · 최소 흙덮기 관 목록) - 크기 · 접기 키 하나(profile-height · profile-collapsed) · 옛 section-panel-* 값 옮겨 받음 - 소스 문자열 시험 넷을 옮긴 자리로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
100 lines
4.4 KiB
TypeScript
100 lines
4.4 KiB
TypeScript
/* =============================================================================
|
||
* B05_Profile_UI_Profile_Guard.ts
|
||
* 종단도가 편집 · 경고에 쓰는 **셈만** — 계획선 샘플 보간, 평면 최소곡선반지름 경고,
|
||
* 구조물 측점을 종단 정본 측점으로 맞추기.
|
||
*
|
||
* 종단도 한 벌(`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";
|
||
import type { IrregularStation } from "./B05_Profile_UI_IrregularStations";
|
||
|
||
/** 구조물 측점을 종단 정본 측점으로 맞춰 주는 허용 오차(m). 층마다 반올림이 달라 생기는
|
||
* mm~cm 어긋남만 흡수할 크기다 — 이보다 멀면 서로 다른 측점이다. */
|
||
const STATION_SNAP_TOLERANCE_M = 0.1;
|
||
|
||
/** 계획선 샘플에서 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;
|
||
}
|
||
|
||
/**
|
||
* 구조물 측점의 누가거리를 **종단 정본 값으로 갈아 끼운다**(2026-09-04).
|
||
*
|
||
* 같은 구조물이 층마다 다른 누가거리를 들고 있었다 — 횡단 측점 `264.054626`,
|
||
* 종단 정본 변화점 `264.055`, 배수 정본 파일 `264.06`(cm 반올림). 사이드 목록이 든
|
||
* 값으로 계획고를 편집하면 정본 옆 mm 자리에 **가짜 변화점**이 하나 더 서고, 둘 사이
|
||
* 종단곡선이 mm 로 쭈그러들어 기울기가 155,791% 로 튀었다(2026-09-04 사용자 보고).
|
||
*
|
||
* 2026-09-03 에 ▲▼ 버튼 경로만 막았는데 측점 테이블·도구 경로가 남아 다시 터졌다.
|
||
* 값이 화면에 들어오는 **이 한 자리**에서 정본으로 맞춰 모든 경로를 함께 막는다.
|
||
*/
|
||
export function snapToMasterStations(
|
||
alignment: ProfileAlignment | null,
|
||
stations: IrregularStation[],
|
||
): IrregularStation[] {
|
||
const master = alignment?.stations;
|
||
if (!master?.length) return stations;
|
||
return stations.map((station) => {
|
||
let best: number | null = null;
|
||
let gap = STATION_SNAP_TOLERANCE_M;
|
||
for (const node of master) {
|
||
const distance = Math.abs(node.chainage_m - station.chainage_m);
|
||
if (distance <= gap) {
|
||
gap = distance;
|
||
best = node.chainage_m;
|
||
}
|
||
}
|
||
return best === null || best === station.chainage_m
|
||
? station
|
||
: { ...station, chainage_m: best };
|
||
});
|
||
}
|