Files
Aislo/common_util/common_util_cross_design_areas.ts
T
eomsangdonandClaude Opus 5 c19af779e4 feat(B06): 횡단 설계 계산 TS 미러 추가 — 브라우저에서 절·성토 면적 산출
사용자 조작 중 계산은 브라우저 안에서 끝나야 함(2026-09-03 사용자 확정). 계획선을
만질 때마다 전 측점 횡단 계산이 서버로 나가 조작 속도를 왕복이 지배했음.

- `common_util_cross_design_areas.ts` — `B06_Section_Engine_Areas.py` 미러
  (사다리꼴 적분 + 절토 토사/암반 분리).
- `common_util_cross_design.ts` — `B06_Section_Engine_Design.py` 미러
  (표준횡단 설계선 구성 + 단면적). config 수치는 복제하지 않고
  `sections/context.standard_cross_section` 을 입력으로 받음.

두 파일 머리에 짝 파일·회귀 테스트를 가리키는 경고 블록을 둠 — 한쪽만 고치면 두 화면
값이 갈림. 아직 어느 화면도 이 모듈을 부르지 않음(다음 커밋에서 연결).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 19:42:31 +09:00

98 lines
4.0 KiB
TypeScript

/* =============================================================================
* 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];
}