/* ============================================================================= * common_util_cross_design_areas.ts * 횡단 단면적 적분 — 절·성토 면적과 절토의 토사/암반 분리. * * ⚠⚠ 파이썬 짝 파일과 **한 벌**이다 — 한쪽만 고치면 두 화면 값이 갈린다 ⚠⚠ * 짝: `B06_Section/B06_Section_Engine_Areas.py` * 같은 입력에 같은 값을 내야 한다. 회귀 테스트가 두 구현을 실제로 비교한다: * `tmp/tests/test_b06_cross_design_mirror.py` — 고칠 때 반드시 같이 돌릴 것. * 왜 두 벌인가: 사용자 조작 중 계산은 브라우저 안에서 끝나야 하고(2026-09-03 사용자 * 확정), 저장·확정·도면 산출은 서버가 정본으로 다시 계산하기 때문이다. * * 두 함수 모두 지반선과 설계선의 **차이 배열**만 받으므로 설계 로직을 전혀 모른다. * ========================================================================== */ /** * 오프셋 순 (지반-설계) 차이를 사다리꼴 적분해 [절토, 성토] 면적을 낸다. * * diff>0(지반이 설계보다 높음)=절토, diff<0=성토. 부호가 바뀌는 구간은 영교점에서 * 나눠 절·성토가 섞이지 않게 한다. */ export function trapezoidAreas(offsets: number[], diffs: number[]): [number, number] { let cutArea = 0; let fillArea = 0; for (let index = 1; index < offsets.length; index += 1) { const x0 = offsets[index - 1]; const x1 = offsets[index]; const d0 = diffs[index - 1]; const d1 = diffs[index]; const width = x1 - x0; if (width <= 0) continue; if (d0 === 0 && d1 === 0) continue; if (d0 * d1 < 0) { // 부호 변화: 영교점에서 두 삼각형으로 분리 const zeroRatio = d0 / (d0 - d1); const xZero = x0 + width * zeroRatio; const leftArea = 0.5 * (xZero - x0) * Math.abs(d0); const rightArea = 0.5 * (x1 - xZero) * Math.abs(d1); if (d0 > 0) { cutArea += leftArea; fillArea += rightArea; } else { fillArea += leftArea; cutArea += rightArea; } continue; } const area = 0.5 * (d0 + d1) * width; if (area >= 0) cutArea += area; else fillArea += -area; } return [cutArea, fillArea]; } /** * 절토 면적을 암반 경계선 기준으로 [토사, 암반]으로 나눈다. * * 암반 경계선은 지반선 평행 복사(`지반고 + rock_boundary_offset_m`)이므로 토사층 두께 * `t0`가 절토 구간 전체에서 균일하다. 따라서 오프셋별 절토 종거 `d = 지반고 - 설계고`에 * 대해 토사분은 `min(max(d, 0), t0)`, 암반분은 `max(d - t0, 0)`이며 두 값의 합은 항상 * `max(d, 0)`이라 `trapezoidAreas`의 절토 면적과 정확히 일치한다. * * 두 함수 모두 `d = 0`과 `d = t0`에서 꺾이므로 그 교차점을 구간 분할점으로 넣어야 * 사다리꼴 적분이 근사가 아닌 정확값이 된다. */ export function splitCutAreas( offsets: number[], diffs: number[], soilDepthM: number, ): [number, number] { const t0 = Math.max(soilDepthM, 0); let soilArea = 0; let rockArea = 0; for (let index = 1; index < offsets.length; index += 1) { const x0 = offsets[index - 1]; const x1 = offsets[index]; const d0 = diffs[index - 1]; const d1 = diffs[index]; const width = x1 - x0; if (width <= 0) continue; const ratios = [0, 1]; for (const level of [0, t0]) { if ((d0 - level) * (d1 - level) < 0) ratios.push((level - d0) / (d1 - d0)); } ratios.sort((a, b) => a - b); for (let step = 1; step < ratios.length; step += 1) { const ratioA = ratios[step - 1]; const ratioB = ratios[step]; const span = width * (ratioB - ratioA); if (span <= 0) continue; const dA = d0 + (d1 - d0) * ratioA; const dB = d0 + (d1 - d0) * ratioB; soilArea += ((Math.min(Math.max(dA, 0), t0) + Math.min(Math.max(dB, 0), t0)) / 2) * span; rockArea += ((Math.max(dA - t0, 0) + Math.max(dB - t0, 0)) / 2) * span; } } 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; }