Files
Aislo/B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts
T
eomsangdonandClaude Opus 5.5 3b5ad313df merge(472-3): origin/main_laptop_1(518-2 B06 관 · BOX · 세월교 logic) 합침 · 518-3 과 양쪽 새 logic 경로로 살림 · 기하 끌림 빈 목록
- 충돌 24 파일 = import 줄 다툼 — 518-2 새 경로(Cross_Culvert · Culvert_Geom · Extra · Wire · Types · Box_Geom · Ford_Geom · Ford_Pavement) + 518-3 새 경로(Cross_Body_Guard · Wall_Geom · Wall_Hatch_Geom · Excavation_Geom · Revetment_Geom · Structure_Layouts · Route_Carry_Patch)
- 518-2 logic 셋이 부르던 UI_Cross_Wall · Wall_Hatch · Body_Guard = 518-3 logic 경로로 · 518-3 새 logic 넷의 옛 Culvert_Types · Extra 경로 = 518-2 logic 으로
- test_472_3 GEOMETRY_PULLS 남은 3 줄(Design_Surface · Excavation · Wall_Hatch) 걷음 = 빈 목록
- Corridor_Structures prettier
- 확인 = 518-1 · 518-2 · 518-3 고친 시험 + golden_472 73 파일 444 통과 · typecheck 0 · design-calc · corridor 묶음 · 옛 판(1d5d63875) ↔ 합친 판 덧그림 대조 938 측점 같음

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

150 lines
7.4 KiB
TypeScript

/* =============================================================================
* B06_Section_UI_Cross_Ford_Pavement.ts
* 물넘이포장 — 횡단도 그림(287-2 · 287-4 · 그림_183).
*
* · 콘크리트 윗면 = 설계 노면 — 서버(`compute_cross_design(tilted=…)`) · 짝 TS 가 노면을 경사각도
* (`ford_slope_deg` → `slope_pct`)로 지대 낮은 쪽 한 경사로 기울여 낸 면 · 설계 중심점이 그 위.
* 노면 밖은 설계선 그대로 — 노면 끝에서 성토 · 절토 사면이 이어짐(늘어나지 못한 자리 = 성토).
* · 포장층 = 노면공 포장 형상(`appendPavementOverlay`)을 노견까지.
* · 월류 높이선(회색 점선) = 노면 위로 월류 높이(`depth_m`)만큼 · 노면과 같은 각도 · 성토 쪽은 성토선을
* 점선으로 늘여 이음(315-2).
* · 도수로 · 돌붙임 띠 = 유출(지대 낮은 쪽) 성토사면.
* ========================================================================== */
import type { CrossDesign } from "./B06_Section_Api_Fetch";
import type { OffsetPoint } from "./logic/B06_Section_Cross_Culvert_Types";
import { fordEdges, outletFillLine, overflowLine } from "./logic/B06_Section_Cross_Ford_Pavement";
import type { FordPavementSpec } from "./logic/B06_Section_Cross_Ford_Pavement";
import { appendPavementOverlay } from "./B06_Section_UI_Cross_Design_Surface";
import { pipeTagId, tagShape } from "../common_util/common_util_pick_tag";
import { appendChuteBand } from "./B06_Section_UI_Cross_Wall_Hatch";
import { chuteRun } from "./logic/B06_Section_Cross_Wall_Hatch_Geom";
function line(points: string[], className: string): SVGPolylineElement {
const polyline = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
polyline.setAttribute("points", points.join(" "));
polyline.setAttribute("class", className);
return polyline;
}
/**
* 세월교로 **내려 앉힌 노면** 위에 "월류가 없었다면" 노면을 점선으로 되그린다
* (2026-08-30 사용자 확정). 계획고는 이미 백엔드가 월류 높이만큼 내려 잡았으므로
* (`design.surface_drop_m`) 여기서는 그 양만큼 위로 올린 선을 얹는다.
* 물넘이포장의 「기존 계획고 점선」과 같은 표기라 사용자가 한 규칙으로 읽는다.
*
* 양 끝은 **측벽 상단의 계류측 꼭짓점까지 대각으로 내려 잇는다**(2026-08-30 사용자:
* 좌측 벽이면 좌상단, 우측 벽이면 우상단, 각도는 무시). 월류 전 노면이 구체 밖에서
* 어디로 떨어지는지가 그 선으로 읽힌다.
*/
export function appendFordSurfaceDropPlan(
svg: SVGElement,
design: CrossDesign,
x: (offset: number) => number,
y: (elevation: number) => number,
ford?: {
sides: ReadonlyArray<{ outward: number; wallTopOuter: OffsetPoint }>;
ford?: { overflow_depth_m?: number };
} | null,
): void {
// 점선 = 노면 + 월류 높이(66-3) — 패널 월류 높이(세트 제원)가 먼저 · 저장 뒤 내린 양과 같은 값
const drop = ford?.ford?.overflow_depth_m ?? design.surface_drop_m ?? 0;
if (!(drop > 0) || !design.road_edges) return;
const { left, right } = design.road_edges;
// +offset이 좌측이다 — 좌측 벽 상단 → 좌 노견 → 우 노견 → 우측 벽 상단 순.
const cornerAt = (side: 1 | -1): OffsetPoint | undefined =>
ford?.sides.find((entry) => Math.sign(entry.outward) === side)?.wallTopOuter;
const points: OffsetPoint[] = [
{ offset: left.offset_m, elevation: left.elevation_m + drop },
{ offset: right.offset_m, elevation: right.elevation_m + drop },
];
const leftCorner = cornerAt(1);
if (leftCorner) points.unshift(leftCorner);
const rightCorner = cornerAt(-1);
if (rightCorner) points.push(rightCorner);
svg.append(
line(
points.map((point) => `${x(point.offset)},${y(point.elevation)}`),
"b06-chart__ford-deck-plan",
),
);
}
/**
* 물넘이포장을 그린다(287-2 · 287-4) — 포장층(노면공 형상 · 노견까지) · 월류 높이선 · 도수로 띠.
* 그렸으면 true — 호출부는 일반 포장층을 건너뜀(두 겹으로 안 깔림). 포장층에 물넘이 표식
* (`b06-chart__ford-pavement` · 287-5 고름이 노면공 포장 고름과 가름).
*/
export function appendFordPavementOverlay(
svg: SVGElement,
spec: FordPavementSpec | undefined,
design: CrossDesign,
x: (offset: number) => number,
y: (elevation: number) => number,
/** 원지반 — 도수로 띠가 원지반에 닿으면 끝 · 길이가 사면보다 길면 이어 그림(294-4). */
ground?: readonly OffsetPoint[],
): boolean {
const edges = fordEdges(design);
if (!spec || !edges) return false;
const from = svg.children.length;
// 노면공 항목은 안 넘김 — 물넘이 자체 포장(노면공 포장 고름과 안 섞임)
appendPavementOverlay(svg, { ...design, paved: true, carriageway_edges: edges }, x, y, null);
[...svg.children]
.slice(from)
.filter((el) => el.getAttribute("class") === "b06-chart__pavement")
.forEach((el) => {
el.setAttribute("class", "b06-chart__pavement b06-chart__ford-pavement");
// 형상 고름 표식(287-5) — 관 지점 알약 id 와 같은 글
if (typeof spec.chainage_m === "number") tagShape(el, pipeTagId(spec.chainage_m), "concrete"); // 관 세션 id · 부분(323-5)
});
// 월류 높이선 — 콘크리트 윗면(설계 노면) 위로 월류 높이만큼 · 같은 각도(그림_183) · 성토 쪽 = 성토선 연장(315-2)
const depth = spec.depth_m ?? 0;
if (depth > 0)
svg.append(
line(
overflowLine(design, depth).map((p) => `${x(p.offset)},${y(p.elevation)}`),
"b06-chart__ford-deck-plan",
),
);
if (spec.chute)
appendChuteBand(svg, [{ points: fordChuteLine(design, spec.chute, ground) }], spec.chute, x, y);
return true;
}
/**
* 물넘이 도수로 · 돌붙임 선(286-3 · 294-4) — 낮은 쪽 끝단에서 성토사면 따라 · 길이 비움 = 원지반 닿으면 끝 ·
* 길이 넣음 = 그 길이(사면보다 길면 원지반 따라 이어 · 315-1 이은 조각까지 한 선) · 양측 절토(늘인 노면 끝이
* 원지반) = 길이 없으면 안 그림. 횡단 띠 · 3D 띠가 같이 씀(그림 전용).
*/
export function fordChuteLine(
design: CrossDesign,
chute?: { length_m?: number },
ground?: readonly OffsetPoint[],
): OffsetPoint[] {
return chuteRun([{ points: outletFillLine(design, ground) }], chute?.length_m, ground).flatMap(
(run, i) => (i ? run.points.slice(1) : run.points),
);
}
/**
* 좌측 패널의 물넘이 칸을 캐시 제원에 얹는다(66-2 · 66-3) — 월류 높이 = 파임 깊이 · 경사각(°) → %.
* 서버 `_ford_pavement_set` 과 같은 환산(저장 뒤 서버가 정본으로 다시 냄). 바꿨으면 true.
*/
export function applyFordPavementOptions(
spec: FordPavementSpec,
patch: Record<string, number | string>,
): boolean {
const num = (key: string): number | undefined => {
const value = patch[key] === "" ? NaN : Number(patch[key]);
return Number.isFinite(value) ? value : undefined;
};
const height = num("ford_height_m");
const degree = num("ford_slope_deg");
if (height !== undefined) spec.depth_m = height > 0 ? height : null;
if (degree !== undefined) {
spec.slope_pct = Number((Math.tan((degree * Math.PI) / 180) * 100).toFixed(4));
spec.defaulted = spec.defaulted?.filter((item) => !item.startsWith("경사각 "));
}
return height !== undefined || degree !== undefined;
}