Files
Aislo/B06_Section/B06_Section_UI_Cross_Design_Surface.ts
T
eomsangdonandClaude Opus 5.5 d3315da993 refactor(472-3 B06 벽 · 기슭막이 · 배치): 구조물 기하 logic 분리 · 그림 파일은 그리기만
- logic 새 파일 다섯 — Cross_Wall_Geom(벽 1매) · Cross_Wall_Hatch_Geom(무늬 띠 · 물빼기 · 도수로 띠 · 원지반선) · Cross_Excavation_Geom(벽 터파기) · Cross_Design_Surface_Geom(암선 · 포장층) · Cross_Revetment_Geom(독립 기슭막이 배치)
- logic 으로 옮김 — Body_Guard → Cross_Body_Guard · Structure_Layouts · Route_Carry_Patch(내용 그대로 · import 경로만)
- inferStationInterval → common_util_station(글자표 없는 자리 · common_util_svg 는 다시 내보냄) — Node 묶음 ui_template_locale 끌림 끊음
- 부르는 쪽 import 줄 · 등록부 ts 경로 · 시험 · 도우미 경로
- test_472_3 GEOMETRY_PULLS 다섯 줄 걷음 · Wall_Hatch · Excavation · Design_Surface 세 줄은 Culvert · Box · Ford 그림 파일(518-2)이 그리기 함수로 부르는 동안 남김

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

113 lines
4.7 KiB
TypeScript

import type { PavementEntry } from "@util/common_util_structure_pavement";
import type { CrossDesign } from "./B06_Section_Api_Fetch";
import {
pavementLayers,
rockBoundarySegments,
} from "./logic/B06_Section_Cross_Design_Surface_Geom";
const SVG_NS = "http://www.w3.org/2000/svg";
export function appendRockBoundaryOverlay(
svg: SVGElement,
samples: Array<{ offset_m?: number; elevation_m?: number | null; valid: boolean }>,
offsetM: number,
x: (offset: number) => number,
y: (elevation: number) => number,
): void {
for (const segment of rockBoundarySegments(samples, offsetM)) {
const polyline = document.createElementNS(SVG_NS, "polyline");
polyline.setAttribute(
"points",
segment.map((p) => `${x(p.offset_m)},${y(p.elevation_m)}`).join(" "),
);
polyline.setAttribute("class", "b06-chart__rock-boundary");
svg.append(polyline);
}
}
/** 포장사양 → 무늬(88) — 콘크리트 빗금 · 아스콘 점 · 쇄석 돌. 그 밖 = 채움만. */
const PAVE_MARKS: Record<string, { size: number; marks: string }> = {
콘크리트: { size: 6, marks: '<path d="M0,6 L6,0" class="b06-chart__pave-mark"/>' },
아스콘: {
size: 5,
marks: '<circle cx="1.5" cy="1.5" r="0.7" class="b06-chart__pave-mark--dot"/>',
},
쇄석: {
size: 9,
marks:
'<circle cx="2.5" cy="2.5" r="1.6" class="b06-chart__pave-mark"/>' +
'<ellipse cx="6.8" cy="6.5" rx="1.8" ry="1.2" class="b06-chart__pave-mark"/>',
},
};
/** 무늬 id 는 문서 안에서 유일 — 카드 · 다시 그리기마다 새 번호(물넘이 빗금과 같은 방식). */
let paveSeq = 0;
type PaveMark = { type_id: string; start_m?: number | null; end_m?: number | null };
/** 포장층 범위 표식(종류 · 시작 · 끝)이 그 노면공인가 — 겹친 노면공 가름. */
function isPaveLayerOf(data: DOMStringMap, mark: PaveMark): boolean {
const [low, high] = [mark.start_m ?? 0, mark.end_m ?? 0].sort((a, b) => a - b);
return (
data.paveType === mark.type_id &&
Math.abs(Number(data.paveStart) - low) < 0.01 &&
Math.abs(Number(data.paveEnd) - high) < 0.01
);
}
/**
* 고른 노면공의 포장층만 `is-active`(152-5) — 범위 표식(종류 · 시작 · 끝)이 맞는 층. 겹친 노면공은 하나만.
* `mark` 없음 = 모두 풂.
*/
export function highlightPavement(root: ParentNode, mark?: PaveMark): void {
for (const layer of root.querySelectorAll<SVGElement>(".b06-chart__pavement")) {
layer.classList.toggle("is-active", !!mark && isPaveLayerOf(layer.dataset, mark));
}
}
/** 누른 자리의 포장층 → 그 노면공(189-1 · 형상 누름 = 목록 고름) · 범위 표식 없는 층 · 밖 = undefined. */
export function pavementAt<T extends PaveMark>(
target: EventTarget | null,
marks: ReadonlyArray<T>,
): T | undefined {
const layer = (target as Element | null)?.closest?.<SVGElement>(".b06-chart__pavement");
if (!layer?.dataset.paveType) return undefined;
return marks.find((mark) => isPaveLayerOf(layer.dataset, mark));
}
/**
* 포장층 — 층 꼭짓점 = `pavementLayers`(두께 · 확폭 켬/끔 · 차례로 쌓음) · 여기는 무늬 · 범위 표식 ·
* 그리기. 없으면 카드 포장 켬일 때 표준 두께 채움만.
*/
export function appendPavementOverlay(
svg: SVGElement,
design: CrossDesign,
x: (offset: number) => number,
y: (elevation: number) => number,
pavement?: readonly PavementEntry[] | null,
): void {
for (const { entry, corners } of pavementLayers(design, pavement)) {
const points = corners.map((p) => `${x(p.offset_m)},${y(p.elevation_m)}`).join(" ");
const polygon = document.createElementNS(SVG_NS, "polygon");
polygon.setAttribute("points", points);
polygon.setAttribute("class", "b06-chart__pavement");
if (entry) {
// 범위 표식 — 고른 노면공의 포장층만 강조(152-5 · 겹친 노면공 가름)
polygon.dataset.paveType = entry.type_id;
polygon.dataset.paveStart = String(entry.start_m);
polygon.dataset.paveEnd = String(entry.end_m);
}
const spec = entry?.spec;
const mark = spec ? PAVE_MARKS[spec] : undefined;
if (spec && mark) {
// 무늬는 바탕 채움째 한 패턴 — 다각형 한 장(B07 도면 수확이 겹선을 안 담음)
const id = `b06-pave-hatch-${(paveSeq += 1)}`;
const defs = document.createElementNS(SVG_NS, "defs");
defs.innerHTML = `<pattern id="${id}" patternUnits="userSpaceOnUse" width="${mark.size}" height="${mark.size}"><rect width="${mark.size}" height="${mark.size}" class="b06-chart__pave-bg"/>${mark.marks}</pattern>`;
svg.append(defs);
polygon.style.fill = `url(#${id})`;
polygon.dataset.paveSpec = spec;
}
svg.append(polygon);
}
}