From ebb8eb13020e066a05d307a6b630727745556e2a Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 8 Oct 2026 12:03:31 +0900 Subject: [PATCH] =?UTF-8?q?feat(B06):=20=EB=85=B8=EB=A9=B4=EA=B3=B5=20?= =?UTF-8?q?=ED=9A=A1=EB=8B=A8=20=ED=98=95=EC=83=81=20=E2=80=94=20=EB=91=90?= =?UTF-8?q?=EA=BB=98=20=C2=B7=20=ED=8F=AC=EC=9E=A5=EC=82=AC=EC=96=91=20?= =?UTF-8?q?=EB=AC=B4=EB=8A=AC=20=EC=85=8B=20=C2=B7=20=ED=99=95=ED=8F=AD=20?= =?UTF-8?q?=EB=81=94=20=EA=B7=9C=EA=B2=A9=20=ED=8F=AD=20=C2=B7=20B07=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=EC=98=B4(88-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt --- .../B06_Section_UI_Cross_Design_Surface.ts | 85 +++++++++++++++---- B06_Section/B06_Section_UI_Cross_View.ts | 3 +- .../B06_Section_UI_Style_Cross_Areas.css | 16 ++++ .../B07_DesignDetail_Cross_Overlay.ts | 2 +- 4 files changed, 89 insertions(+), 17 deletions(-) diff --git a/B06_Section/B06_Section_UI_Cross_Design_Surface.ts b/B06_Section/B06_Section_UI_Cross_Design_Surface.ts index b35b73912..dca4ed947 100644 --- a/B06_Section/B06_Section_UI_Cross_Design_Surface.ts +++ b/B06_Section/B06_Section_UI_Cross_Design_Surface.ts @@ -1,3 +1,4 @@ +import type { PavementEntry } from "@util/common_util_structure_pavement"; import type { CrossDesign } from "./B06_Section_Api_Fetch"; const SVG_NS = "http://www.w3.org/2000/svg"; @@ -29,26 +30,80 @@ export function appendRockBoundaryOverlay( } } +type Edge = { offset_m: number; elevation_m: number }; + +/** 포장사양 → 무늬(88) — 콘크리트 빗금 · 아스콘 점 · 쇄석 돌. 그 밖 = 채움만. */ +const PAVE_MARKS: Record = { + 콘크리트: { size: 6, marks: '' }, + 아스콘: { + size: 5, + marks: '', + }, + 쇄석: { + size: 9, + marks: + '' + + '', + }, +}; +/** 무늬 id 는 문서 안에서 유일 — 카드 · 다시 그리기마다 새 번호(물넘이 빗금과 같은 방식). */ +let paveSeq = 0; + +/** 확폭 끈 구간 — 규격 폭 끝(좌 = left − 확폭좌 · 우 = right + 확폭우 · 표고는 두 끝 사이 직선). */ +function standardEdges(edges: { left: Edge; right: Edge }, design: CrossDesign) { + const { left, right } = edges; + const span = right.offset_m - left.offset_m || 1; + const at = (offset: number): Edge => ({ + offset_m: offset, + elevation_m: + left.elevation_m + ((right.elevation_m - left.elevation_m) * (offset - left.offset_m)) / span, + }); + return { + left: at(left.offset_m - (design.widening_left_m ?? 0)), + right: at(right.offset_m + (design.widening_right_m ?? 0)), + }; +} + +/** + * 포장층 — 노면선(= 포장 윗면) 아래로 두께만큼. 노면공 범위(`section.pavement`)면 그 두께 · 무늬 · + * 확폭 켬/끔(여러 건은 아래로 차례로 쌓음) · 없으면 카드 포장 켬일 때 표준 두께 채움만. + */ export function appendPavementOverlay( svg: SVGElement, design: CrossDesign, x: (offset: number) => number, y: (elevation: number) => number, + pavement?: readonly PavementEntry[] | null, ): void { const edges = design.carriageway_edges ?? design.road_edges; - if (!design.paved || !edges) return; - const thickness = design.pavement_thickness_m ?? 0.2; - const { left, right } = edges; - const polygon = document.createElementNS(SVG_NS, "polygon"); - polygon.setAttribute( - "points", - [ - `${x(left.offset_m)},${y(left.elevation_m)}`, - `${x(right.offset_m)},${y(right.elevation_m)}`, - `${x(right.offset_m)},${y(right.elevation_m - thickness)}`, - `${x(left.offset_m)},${y(left.elevation_m - thickness)}`, - ].join(" "), - ); - polygon.setAttribute("class", "b06-chart__pavement"); - svg.append(polygon); + if (!edges) return; + const layers = pavement?.length ? pavement : design.paved ? [null] : []; + let depth = 0; + for (const entry of layers) { + const thickness = entry?.thickness_m ?? design.pavement_thickness_m ?? 0.2; + const { left, right } = entry && !entry.widening_on ? standardEdges(edges, design) : edges; + const points = [ + `${x(left.offset_m)},${y(left.elevation_m - depth)}`, + `${x(right.offset_m)},${y(right.elevation_m - depth)}`, + `${x(right.offset_m)},${y(right.elevation_m - depth - thickness)}`, + `${x(left.offset_m)},${y(left.elevation_m - depth - thickness)}`, + ].join(" "); + depth += thickness; + const polygon = document.createElementNS(SVG_NS, "polygon"); + polygon.setAttribute("points", points); + polygon.setAttribute("class", "b06-chart__pavement"); + svg.append(polygon); + const spec = entry?.spec; + const mark = spec ? PAVE_MARKS[spec] : undefined; + if (!spec || !mark) continue; + const id = `b06-pave-hatch-${(paveSeq += 1)}`; + const defs = document.createElementNS(SVG_NS, "defs"); + defs.innerHTML = `${mark.marks}`; + const hatch = document.createElementNS(SVG_NS, "polygon"); + hatch.setAttribute("points", points); + hatch.setAttribute("class", "b06-chart__pavement-hatch"); + hatch.setAttribute("fill", `url(#${id})`); + hatch.dataset.paveSpec = spec; + svg.append(defs, hatch); + } } diff --git a/B06_Section/B06_Section_UI_Cross_View.ts b/B06_Section/B06_Section_UI_Cross_View.ts index df688b16b..55bc589db 100644 --- a/B06_Section/B06_Section_UI_Cross_View.ts +++ b/B06_Section/B06_Section_UI_Cross_View.ts @@ -429,7 +429,8 @@ export function createCrossSectionCard( x, toDisplayY, ); - if (!fordPaved) appendPavementOverlay(plotLayer, section.design, x, toDisplayY); + if (!fordPaved) + appendPavementOverlay(plotLayer, section.design, x, toDisplayY, section.pavement); // 세월교 측점 — 계획고는 이미 월류 높이만큼 내려와 있다. "월류가 없었다면" // 노면을 점선으로 남겨 얼마나 내려앉았는지 읽히게 한다(2026-08-30 사용자). appendFordSurfaceDropPlan(plotLayer, section.design, x, toDisplayY, fordLayout); diff --git a/B06_Section/B06_Section_UI_Style_Cross_Areas.css b/B06_Section/B06_Section_UI_Style_Cross_Areas.css index d240917ba..390b8c475 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Areas.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Areas.css @@ -185,6 +185,22 @@ stroke-width: 1; } +/* 노면공 포장사양 무늬(88): 채움 박스 위에 무늬만 한 겹 — 콘크리트 빗금 · 아스콘 점 · 쇄석 돌 */ +.b06-chart__pavement-hatch { + stroke: none; +} + +.b06-chart__pave-mark { + fill: none; + stroke: color-mix(in srgb, var(--color-text-body) 70%, transparent); + stroke-width: 1; +} + +.b06-chart__pave-mark--dot { + fill: color-mix(in srgb, var(--color-text-body) 70%, transparent); + stroke: none; +} + /* 독립 기슭막이(2026-08-28): 성토면 끝에 서는 벽 — 배관 세트 기슭막이와 같은 색 계열 */ .b06-chart__revetment { fill: color-mix(in srgb, #9b6bdc 22%, transparent); diff --git a/B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts b/B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts index aa57fc4ea..ff43a5cff 100644 --- a/B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts +++ b/B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts @@ -415,7 +415,7 @@ function drawDesign( ): void { const { design, culvert, box, ford, own } = layouts; const paved = appendFordPavementOverlay(svg, section.ford_pavement, design, x, y); - if (!paved) appendPavementOverlay(svg, design, x, y); + if (!paved) appendPavementOverlay(svg, design, x, y, section.pavement); appendCrossDesignOverlay( svg, design,