fix(b06): 횡단 보기 반폭 [적용] = 요청값만 — 교차점 맞춤 폭으로 누를 때마다 재생성하던 옛 줄 지움(173-6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
2026-10-10 08:47:40 +09:00
co-authored by Claude Opus 5.5
parent 3f82ea259f
commit 3e20341810
4 changed files with 102 additions and 47 deletions
+3 -42
View File
@@ -9,8 +9,7 @@
* 교차점이 여전히 화면 밖에 남는다(2026-08-23 사용자 보고).
*
* 설계선이 지반과 만나기 전에 끝나면(계산 반폭 밖) **추세로 외삽**한다 — 끝단의 사면
* 기울기와 지반 기울기 차이로 만나는 자리를 추정하고 1m 올림한다. 그 값이 계산 반폭을
* 넘으면 페이지가 백엔드 재생성으로 샘플을 넓힌다.
* 기울기와 지반 기울기 차이로 만나는 자리를 추정한다.
* ========================================================================== */
import type { CrossDesign, CrossSection } from "./B06_Section_Api_Fetch";
@@ -25,43 +24,10 @@ const TREND_SPAN_M = 2;
/** 외삽 결과의 상한(m) — 발산하는 지형에서 폭이 무한정 커지는 것을 막는다. */
const MAX_FIT_HALF_WIDTH_M = 60;
/**
* 절·성토 교차점이 모두 들어오는 표시 반폭(m). 설계선·지반선이 없으면 null.
*
* 반환값은 1m 단위 올림 — 교차점이 축선에 딱 붙지 않게 한다.
*/
export function toeFitHalfWidth(section: CrossSection): number | null {
const design = section.design;
if (!design) return null;
const groundAt = groundInterpolator(section.samples);
const designAt = designInterpolator(design.design_line);
const edges = design.road_edges;
if (!groundAt || !designAt || !edges) return null;
// 설계선이 실제로 정의된 범위 — 그 밖은 외삽 영역이다.
const lineOffsets = design.design_line.map((point) => point.offset_m);
if (lineOffsets.length < 2) return null;
const lineMax = Math.max(...lineOffsets);
const lineMin = Math.min(...lineOffsets);
// 노면·측구 구간은 건너뛴다 — 계획고가 지반과 우연히 겹쳐도 사면 끝이 아니다.
const { protectMax, protectMin } = protectedSpan(design, edges);
let extent = 0;
for (const side of ["left", "right"] as const) {
const outward = side === "left" ? 1 : -1;
const start = side === "left" ? protectMax : protectMin;
const limit = side === "left" ? lineMax : lineMin;
const meet = meetOffset(designAt, groundAt, start, limit, outward);
extent = Math.max(extent, Math.abs(meet));
}
if (!(extent > 0)) return null;
return Math.min(Math.ceil(extent + 1), MAX_FIT_HALF_WIDTH_M);
}
/**
* 절·성토 사면이 원지반과 **실제로 만나는** 좌·우 offset(m). 그 측에서 못 만나면 null.
*
* `toeFitHalfWidth` 와 같은 교차점을 쓰되 **좌우를 따로** 낸다(2026-09-12 사용자 지시 ②) —
* 교차점을 **좌우를 따로** 낸다(2026-09-12 사용자 지시 ②) —
* 반폭 하나로 자르면 한쪽만 성토인 측점에서 반대쪽 원지반이 필요보다 넓게 남는다.
* **외삽으로 짚은 자리는 안 쓴다**(null) — 지반을 모르는 자리라 거기까지 그으면 없는 지반을
* 그리는 셈이 된다.
@@ -129,7 +95,7 @@ export function fillSlopeLengths(section: CrossSection): {
const outward = side === "left" ? 1 : -1;
const start = side === "left" ? protectMax : protectMin;
const limit = side === "left" ? Math.max(...lineOffsets) : Math.min(...lineOffsets);
// 미교차 측점은 **재지 못한 것**이라 계산 반폭까지의 하한값만 준다 — `toeFitHalfWidth`가
// 미교차 측점은 **재지 못한 것**이라 계산 반폭까지의 하한값만 준다 — `meetOffset` 이
// 쓰는 추세 외삽은 지반이 사면과 거의 나란한 자리에서 수십 m씩 튀어 길이 표기에는 못 쓴다
// (2026-09-03 실측: 740m 측점 좌측 외삽 101.53m, 실제 반폭 20m까지 고저차 7.16m 유지).
const meet = meetOffset(designAt, groundAt, start, limit, outward);
@@ -328,8 +294,3 @@ function extrapolateMeet(
if (!Number.isFinite(extra)) return limit;
return limit + outward * Math.min(extra, MAX_FIT_HALF_WIDTH_M);
}
/** 전 측점 중 교차점을 보려면 필요한 최대 반폭(m). 없으면 0. */
export function maxToeFitHalfWidth(sections: CrossSection[]): number {
return sections.reduce((max, section) => Math.max(max, toeFitHalfWidth(section) ?? 0), 0);
}