Files
Aislo/B06_Section/logic/B06_Section_Cross_Ford_Pavement.ts
T
eomsangdonandClaude Opus 5.5 e4befd158a refactor(472-3 B06 구조물): 관 · BOX · 세월교 · 물넘이 모양 계산을 logic 으로 옮김(518-2)
- logic 새 파일 9 — B06_Section_Cross_Culvert · _Culvert_Geom · _Culvert_Extra · _Culvert_Tiers ·
  _Culvert_Types · _Culvert_Wire · _Box_Geom · _Ford_Geom · _Ford_Pavement
- 그림 파일(UI_Cross_Culvert · _Culvert_Types · _Box · _Ford · _Ford_Pavement)은 받은 좌표로 그리기만 ·
  관 겹 꼭짓점(pipeShellCorners) · 물넘이 노면 끝 · 월류선 · 유출 성토선은 logic 으로
- 옛 순수 기하 6 파일(UI_Cross_Culvert_Geom · _Extra · _Tiers · _Wire · _Box_Geom · _Ford_Geom) 지움 ·
  부르는 쪽(B05 Corridor · Structure_Layouts · Route_Carry_Patch · Wall 등)은 import 줄만
- test_472_3 GEOMETRY_PULLS 자기 11 줄 + 더는 안 끌리는 Design_Surface 1 줄 걷음
- 시험 · helper 경로를 새 logic 파일로 — 옮긴 파일을 가리키던 낡은 경로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 19:17:23 +09:00

119 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* B06_Section_Cross_Ford_Pavement.ts
* 물넘이포장 기하 — 유입 쪽 · 노면 양 끝 · 월류 높이선 · 유출측 성토사면 선(287-2 · 294 · 315).
* 그리기는 `UI_Cross_Ford_Pavement.ts` · 3D 띠(B05)도 같은 선을 씀.
* ========================================================================== */
import type { CrossDesign } from "../B06_Section_Api_Fetch";
import type { OffsetPoint } from "./B06_Section_Cross_Culvert_Types";
/** 백엔드 `section.ford_pavement` 제원(치수 결정은 서버 몫 — 여기서는 좌표만 만든다). */
export interface FordPavementSpec {
/** 관 지점 누가거리 — 폭 절반 걸친 옆 카드도 같은 값. */
chainage_m?: number;
span_m: number;
/** 월류 높이(m) — 노면 위 점선. 없으면 점선을 안 그림(수치를 지어내지 않는다). */
depth_m: number | null;
/** 노면 경사(%) — 경사각도 환산 · 서버 설계 노면이 이 값으로 기욺. */
slope_pct: number | null;
/** 정본에 안 적혀 기본값으로 그린 칸 「이름 값」 — 카드 머리에 「기본값(미확정)」. */
defaulted?: string[];
/** 도수로 · 돌붙임(`ford_chute_{n}_*` · 136-2) — 유출측 성토사면 띠 · 길이 = 사면 따라(286-3 · 비면 원지반까지). */
chute?: { structure: string; thickness_m: number; length_m?: number };
}
/** 유입(상류)이 좌측(+offset)인가 — 기울인 노면이면 높은 노면 끝 · 아니면 설계선 양 끝 표고가 높은
* 쪽(286-2) · 같으면 단면유형. */
export function inflowLeft(design: CrossDesign): boolean {
const edges = design.road_edges;
if (design.tilted && edges && Math.abs(edges.left.elevation_m - edges.right.elevation_m) > 1e-6)
return edges.left.elevation_m > edges.right.elevation_m;
const line = [...(design.design_line ?? [])].sort((a, b) => a.offset_m - b.offset_m);
const gap = line.length >= 2 ? line[line.length - 1].elevation_m - line[0].elevation_m : 0;
return Math.abs(gap) > 1e-3 ? gap > 0 : design.section_mode !== "right_cut";
}
/** 콘크리트 · 노면 양 끝 — 낮은 쪽을 늘였으면(294-3 `tilt_reach`) 그 쪽은 원지반과 만난 자리까지. */
export function fordEdges(design: CrossDesign): CrossDesign["road_edges"] | undefined {
const edges = design.road_edges;
const reach = design.tilt_reach;
if (!edges || !reach) return edges;
const end = { offset_m: reach.offset_m, elevation_m: reach.elevation_m };
return reach.side === "left" ? { ...edges, left: end } : { ...edges, right: end };
}
/**
* 월류 높이선(315-2) — 노면 양 끝 위 월류 높이 · 노면과 같은 각도. 성토 쪽(노면 끝 밖 설계선이 내려감)은
* 성토 사면선을 노면 끝 너머 위 · 안쪽으로 늘여 월류 높이선과 만나는 자리에서 끝 → 노면 끝까지 그 연장선.
* 절토 쪽 · 늘인 노면 끝(294-3)은 노면 끝 위 그대로. 좌(+offset) → 우 차례.
*/
export function overflowLine(design: CrossDesign, depth: number): OffsetPoint[] {
const edges = fordEdges(design);
if (!edges) return [];
const { left, right } = edges;
const span = Math.abs(left.offset_m - right.offset_m);
const side = (edge: typeof left, other: typeof left): OffsetPoint[] => {
const top = { offset: edge.offset_m, elevation: edge.elevation_m + depth };
const outward = Math.sign(edge.offset_m - other.offset_m) || 1;
const next = (design.design_line ?? [])
.filter((p) => (p.offset_m - edge.offset_m) * outward > 1e-6)
.sort((a, b) => (a.offset_m - b.offset_m) * outward)[0];
const cut = edge.offset_m === design.tilt_reach?.offset_m; // 늘인 끝 = 원지반(294-3 · 절토)
if (cut || !next || next.elevation_m >= edge.elevation_m - 1e-6 || span < 1e-9) return [top];
// 안쪽 d m 에서 성토선 = edge + fill·d · 월류선 = edge + depth + road·d → d = depth / (fill − road)
const fill = (edge.elevation_m - next.elevation_m) / Math.abs(next.offset_m - edge.offset_m);
const road = (other.elevation_m - edge.elevation_m) / span;
if (fill <= road + 1e-9) return [top];
const d = Math.min(depth / (fill - road), span);
const meet = { offset: edge.offset_m - outward * d, elevation: edge.elevation_m + fill * d };
return [{ offset: edge.offset_m, elevation: edge.elevation_m }, meet];
};
return [...side(left, right), ...side(right, left).reverse()];
}
/** 원지반 꺾은선의 그 오프셋 표고(밖은 끝값). */
function groundZ(ground: readonly OffsetPoint[], offset: number): number | null {
if (!ground.length) return null;
if (offset <= ground[0].offset) return ground[0].elevation;
for (let i = 1; i < ground.length; i += 1) {
const [a, b] = [ground[i - 1], ground[i]];
if (offset > b.offset) continue;
const span = b.offset - a.offset;
return span > 1e-9
? a.elevation + ((b.elevation - a.elevation) * (offset - a.offset)) / span
: b.elevation;
}
return ground[ground.length - 1].elevation;
}
/**
* 유출측(낮은 쪽) 성토사면 선(136-2 · 294-4) — 낮은 쪽 끝단(늘인 노면 끝 · 아니면 유출 쪽 노견 끝)에서
* 설계선을 바깥으로 따라 내려가는 동안만(오르면 절토라 멈춤) · 원지반(`ground`)에 닿으면 끝.
* 성토가 없으면(끝단이 이미 원지반 · 양측 절토 물넘이) 점 하나 = 띠 없음.
*/
export function outletFillLine(
design: CrossDesign,
ground?: readonly OffsetPoint[],
): OffsetPoint[] {
const edges = fordEdges(design);
if (!edges || !design.design_line?.length) return [];
const reach = design.tilt_reach;
const outLeft = reach ? reach.side === "left" : !inflowLeft(design); // +offset = 좌측
const outward = outLeft ? 1 : -1;
const edge = outLeft
? Math.max(edges.left.offset_m, edges.right.offset_m)
: Math.min(edges.left.offset_m, edges.right.offset_m);
const points = design.design_line
.filter((p) => (p.offset_m - edge) * outward >= -1e-6)
.sort((a, b) => (a.offset_m - b.offset_m) * outward);
const line: OffsetPoint[] = [];
for (const p of points) {
const last = line[line.length - 1];
if (last && p.elevation_m > last.elevation + 1e-6) break;
line.push({ offset: p.offset_m, elevation: p.elevation_m });
const z = ground ? groundZ(ground, p.offset_m) : null;
if (z !== null && Math.abs(z - p.elevation_m) < 1e-3) break; // 원지반에 닿음 = 끝
}
return line;
}