feat(B06): 측구터파기 단면적을 토사/암으로 가름 — 새 입력 없이 암반 경계선으로
별표2 Ⅰ.1.나.(5) 「측구터파기 단면적」이 횡단도 표의 법정 칸인데 한 값뿐이라 「측구 토사 / 측구 암석」 두 칸이 반만 채워졌다. - 가르는 근거는 **절토 분리와 같은 것**(지반 유형 + 암반 경계선). 새 입력을 만들지 않았다. - 측구 상단에서 암반 경계선까지의 깊이로 공칭 도형(사다리꼴·L형)을 가로로 가른다. - ⚠ 근거가 없으면 **나누지 않는다.** 사유를 `ditch_split_basis` 로 함께 냄: rock_boundary / soil_ground / rock_ground_no_boundary / no_ditch. - 기존 `ditch_area_m2` 는 **합계로 그대로** 두고 갈래를 덧붙였다 — B08 이 순서대로 옮겨 갈 수 있게. - 파이썬·TS 짝을 함께 고침. 거울 테스트 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,12 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import type { BermSpec } from "./common_util_cross_berm";
|
||||
import { benchCutLength, splitCutAreas, trapezoidAreas } from "./common_util_cross_design_areas";
|
||||
import {
|
||||
benchCutLength,
|
||||
splitCutAreas,
|
||||
splitDitchArea,
|
||||
trapezoidAreas,
|
||||
} from "./common_util_cross_design_areas";
|
||||
// 단면 기하(노면·측구·사면 설계고)는 파일이 700줄을 넘어 떼어냈다(2026-09-04).
|
||||
import {
|
||||
CURVE_WIDENING_MAX_WIDTH_M,
|
||||
@@ -136,6 +141,10 @@ export interface CrossDesignResult {
|
||||
slope_unclosed: boolean;
|
||||
fill_ground_slope: number | null;
|
||||
ditch_area_m2: number;
|
||||
/** 측구터파기 내역(합=ditch_area_m2)과 가른 근거. 짝: 파이썬 `ditch_split_basis`. */
|
||||
ditch_soil_area_m2: number;
|
||||
ditch_rock_area_m2: number;
|
||||
ditch_split_basis: string;
|
||||
design_line: CrossDesignEdge[];
|
||||
/** 절토 사면 경사 구간(소단 제외). 짝: `cut_slope_segments`.
|
||||
* ⚠ **지금 읽는 곳은 없다**(2026-09-07) — 임자였던 별표2 검사는 폐기됐고 저장분에도
|
||||
@@ -395,6 +404,37 @@ export function computeCrossDesign(
|
||||
};
|
||||
}
|
||||
|
||||
// 측구터파기 토사/암 분리 — **새 입력을 만들지 않는다.** 절토 분리와 같은 근거
|
||||
// (지반 유형 + 암반 경계선)를 그대로 쓴다. 근거가 없으면 나누지 않고 사유를 낸다.
|
||||
// ⚠ 파이썬 짝: `B06_Section_Engine_Design` 의 같은 자리.
|
||||
let ditchSoilArea: number;
|
||||
let ditchRockArea: number;
|
||||
let ditchSplitBasis: string;
|
||||
if (!geometry.hasDitch) {
|
||||
ditchSoilArea = 0;
|
||||
ditchRockArea = 0;
|
||||
ditchSplitBasis = "no_ditch";
|
||||
} else if (presetKey !== "rock") {
|
||||
ditchSoilArea = ditchArea;
|
||||
ditchRockArea = 0;
|
||||
ditchSplitBasis = "soil_ground";
|
||||
} else if (
|
||||
rockBoundaryOffsetM === null ||
|
||||
rockBoundaryOffsetM === undefined ||
|
||||
!geometry.ditchPoints.length
|
||||
) {
|
||||
ditchSoilArea = 0;
|
||||
ditchRockArea = ditchArea;
|
||||
ditchSplitBasis = "rock_ground_no_boundary";
|
||||
} else {
|
||||
const ditchTopZ = geometry.ditchPoints[0][1];
|
||||
const midOffset =
|
||||
geometry.ditchPoints.reduce((sum, point) => sum + point[0], 0) / geometry.ditchPoints.length;
|
||||
const boundaryZ = groundAt(midOffset) - Math.abs(rockBoundaryOffsetM);
|
||||
[ditchSoilArea, ditchRockArea] = splitDitchArea(ditchSpec, ditchTopZ - boundaryZ);
|
||||
ditchSplitBasis = "rock_boundary";
|
||||
}
|
||||
|
||||
// 자동 판정된 절/성토 역할에서 실제 단면 유형을 도출해 echo 한다(D-2).
|
||||
let resolvedMode: string;
|
||||
if (geometry.leftRole === "cut" && geometry.rightRole === "cut") resolvedMode = "both_cut";
|
||||
@@ -457,6 +497,9 @@ export function computeCrossDesign(
|
||||
slope_unclosed: slopeUnclosed,
|
||||
fill_ground_slope: fillGroundSlope === null ? null : round4(fillGroundSlope),
|
||||
ditch_area_m2: round4(ditchArea),
|
||||
ditch_soil_area_m2: round4(ditchSoilArea),
|
||||
ditch_rock_area_m2: round4(ditchRockArea),
|
||||
ditch_split_basis: ditchSplitBasis,
|
||||
design_line: designLine,
|
||||
cut_slope_segments: geometry.cutSlopeSegments(),
|
||||
};
|
||||
|
||||
@@ -126,3 +126,36 @@ export function benchCutLength(offsets: number[], grounds: number[], diffs: numb
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 측구 단면적을 [토사, 암반]으로 가른다 — 측구 상단에서 암반 경계선까지의 깊이(m) 기준.
|
||||
* ⚠ 파이썬 짝: `B06_Section_Engine_Areas._split_ditch_area`. 한 벌로 움직인다.
|
||||
* 측구 단면은 공칭 도형이라 지반선을 따라 적분하지 않는다 — 경계선도 그 자리 한 높이로 본다.
|
||||
*/
|
||||
export function splitDitchArea(
|
||||
ditchSpec: Record<string, unknown>,
|
||||
depthToBoundaryM: number | null,
|
||||
): [number, number] {
|
||||
const kind = String(ditchSpec.type ?? "none");
|
||||
const clamp = (depth: number): number => Math.min(Math.max(depthToBoundaryM ?? 0, 0), depth);
|
||||
if (kind === "l_type") {
|
||||
const width = Number(ditchSpec.width_m ?? 0);
|
||||
const depth = Number(ditchSpec.depth_m ?? 0);
|
||||
if (depth <= 0 || width <= 0) return [0, 0];
|
||||
const total = (width * depth) / 2;
|
||||
const d0 = clamp(depth);
|
||||
const soil = width * d0 - (width * d0 * d0) / (2 * depth);
|
||||
return [soil, Math.max(total - soil, 0)];
|
||||
}
|
||||
if (kind === "standard") {
|
||||
const top = Number(ditchSpec.top_width_m ?? 0);
|
||||
const bottom = Math.min(Number(ditchSpec.bottom_width_m ?? 0), top);
|
||||
const depth = Number(ditchSpec.depth_m ?? 0);
|
||||
if (depth <= 0 || top <= 0) return [0, 0];
|
||||
const total = ((top + bottom) / 2) * depth;
|
||||
const d0 = clamp(depth);
|
||||
const soil = top * d0 - ((top - bottom) * d0 * d0) / (2 * depth);
|
||||
return [soil, Math.max(total - soil, 0)];
|
||||
}
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user