Files
Aislo/B05_Profile/B05_Profile_UI_Profile_Guard.ts
T
eomsangdonandClaude Opus 5.5 38aae89b2d refactor(B05·B06): 종단도 한 벌 — B05 종단 패널 본체를 design_profile(createDesignProfile)로 옮김 · B06 위 종단을 같은 종단도(가운데 아래 · 카드 위 칸 스크롤)로 바꿈
- 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
2026-09-29 20:28:15 +09:00

100 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* 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 };
});
}