feat(B06): 층따기 밑수를 횡단 설계에서 냄 — 성토부 아래 원지반 지표면 길이

사용자 확정(2026-09-09): 층따기 단위는 ㎡. 대신 그 면적이 B06 설계에서 나와야 한다.

- `_bench_cut_length`: 성토(diff<0) 구간에서 원지반 횡단기울기가 **1:4 보다 급한**
  조각만 골라 지표면을 따라간 길이(빗변)를 더한다. 절·성토 경계는 영교점까지만 센다.
  근거는 별표2·교본 6장 4절, 지식DB 성토_비탈면 §4 [구현].
- 결과에 `bench_cut_length_m` 을 실어 B08 이 측점 사이를 평균단면적법으로 이어 ㎡ 를 낸다.
  ⚠ 여기서 ㎥ 로 바꾸지 않는다 — 단의 높이·폭은 설계도서 값이라 지어낼 수 없다.
- 파이썬·TS 짝을 함께 고침(`common_util_cross_design_areas.ts`). 거울 테스트 통과.
- ⚠ 성토 비탈면 길이가 아니라 **원지반 표면**을 센다 — 층따기 대상 면이 그쪽이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 19:06:53 +09:00
co-authored by Claude Opus 5
parent 5556946f7b
commit 86437ce753
4 changed files with 95 additions and 1 deletions
+8 -1
View File
@@ -24,7 +24,7 @@
* ========================================================================== */
import type { BermSpec } from "./common_util_cross_berm";
import { splitCutAreas, trapezoidAreas } from "./common_util_cross_design_areas";
import { benchCutLength, splitCutAreas, trapezoidAreas } from "./common_util_cross_design_areas";
// 단면 기하(노면·측구·사면 설계고)는 파일이 700줄을 넘어 떼어냈다(2026-09-04).
import {
CURVE_WIDENING_MAX_WIDTH_M,
@@ -131,6 +131,8 @@ export interface CrossDesignResult {
cut_rock_area_m2: number;
cut_rock_kind: string | null;
fill_area_m2: number;
/** 층따기 밑수 — 성토부 아래 원지반(1:4 보다 급한 구간)의 지표면 길이(m). */
bench_cut_length_m: number;
slope_unclosed: boolean;
fill_ground_slope: number | null;
ditch_area_m2: number;
@@ -331,18 +333,22 @@ export function computeCrossDesign(
const merged = [...mergedSet].sort((a, b) => a - b);
const offsets: number[] = [];
const grounds: number[] = [];
const diffs: number[] = [];
const designLine: CrossDesignEdge[] = [];
for (const offsetM of merged) {
const groundM = groundAt(offsetM);
const designZ = geometry.designZ(offsetM, groundM);
offsets.push(offsetM);
grounds.push(groundM);
diffs.push(groundM - designZ);
designLine.push({ offset_m: round4(offsetM), elevation_m: round4(designZ) });
}
// 측구 굴착은 설계선에 포함돼 절토 면적에 자연 반영된다(별도 가산 없음).
const [cutArea, fillArea] = trapezoidAreas(offsets, diffs);
// 층따기 밑수(길이 m) — 성토부 아래 원지반이 1:4 보다 급한 구간의 지표면 길이.
const benchCut = benchCutLength(offsets, grounds, diffs);
const fillGroundSlope = geometry.fillGroundSlope();
const slopeUnclosed =
diffs.length > 0 &&
@@ -447,6 +453,7 @@ export function computeCrossDesign(
cut_rock_area_m2: round4(cutRockArea),
cut_rock_kind: cutRockKind,
fill_area_m2: round4(fillArea),
bench_cut_length_m: round4(benchCut),
slope_unclosed: slopeUnclosed,
fill_ground_slope: fillGroundSlope === null ? null : round4(fillGroundSlope),
ditch_area_m2: round4(ditchArea),
@@ -95,3 +95,34 @@ export function splitCutAreas(
}
return [soilArea, rockArea];
}
/** 층따기 대상 판정 기울기 — 원지반 횡단기울기 1:4(=25%)보다 급한 곳에만 한다.
* 근거: 별표2 · 임도기술교본 6장 4절(「1:4보다 급한 경사를 가진 지반 위에 성토」).
* ⚠ 파이썬 짝: `B06_Section_Engine_Areas._BENCH_CUT_MIN_GROUND_SLOPE`. */
export const BENCH_CUT_MIN_GROUND_SLOPE = 0.25;
/**
* 층따기 밑수 — **성토부 아래 원지반 표면의 경사길이(m)**.
* ⚠ 파이썬 짝: `B06_Section_Engine_Areas._bench_cut_length`. 한 벌로 움직인다.
* 면적(㎡)은 측점 사이를 평균단면적법으로 이어 B08 이 낸다.
*/
export function benchCutLength(offsets: number[], grounds: number[], diffs: number[]): number {
let total = 0;
for (let index = 1; index < offsets.length; index += 1) {
const run = offsets[index] - offsets[index - 1];
if (run <= 0) continue;
const d0 = diffs[index - 1];
const d1 = diffs[index];
if (d0 >= 0 && d1 >= 0) continue;
let share = 1;
if (d0 * d1 < 0) {
const zeroRatio = d0 / (d0 - d1);
share = d0 > 0 ? 1 - zeroRatio : zeroRatio;
}
if (share <= 0) continue;
const rise = grounds[index] - grounds[index - 1];
if (Math.abs(rise) / run < BENCH_CUT_MIN_GROUND_SLOPE) continue;
total += Math.sqrt(run * run + rise * rise) * share;
}
return total;
}