import type { PierModel } from '../core/geometry'; import type { PierInput } from '../core/types'; import { SUPPORT_STRATUM } from '../core/ground'; /** Layer fills from the top down (시안 2026-10-26: 지층 1 light, deeper layers darker). */ const LAYER_FILLS = ['#EADFC8', '#DCCBA8', '#C9B38D', '#B9A37E', '#A8926F', '#97815F']; const WIDTH = 400; const PLOT_H = 300; const TOP_PAD = 24; /** * 지반·지층 단면 (삽도). Drawn from the inputs and the generated footings and piles, on one vertical EL scale. * Horizontal positions are schematic (no dimensions): piles are spread across the footing. */ export function groundSectionSvg(input: PierInput, model: PierModel): string { const rows = input.strataRows ?? []; const ground = input.groundEl; const footTop = Math.max(...model.footings.map((f) => f.top)); const footBottom = Math.min(...model.footings.map((f) => f.bottom)); const pileTips = model.piles.map((p) => p.bottom); const lowestRow = rows.length ? rows[rows.length - 1].bottomEl : ground - 1; // 하상: the 설계홍수위 (solid) and the 세굴선 replace the groundwater (dashed, 승인 2026-10-26). const position = input.groundPosition ?? '육상'; const river = position === '하상'; const sea = position === '해상'; const level = river ? (Number.isFinite(input.floodLevelEl) ? input.floodLevelEl : undefined) : sea ? (Number.isFinite(input.highWaterEl) ? input.highWaterEl : undefined) : (input.groundWaterOn && Number.isFinite(input.groundWaterEl) ? input.groundWaterEl : undefined); // 해상: the 저조위 is drawn too (dashed), on the MSL datum. const lowLevel = sea && Number.isFinite(input.lowWaterEl) ? input.lowWaterEl : undefined; const groundLabel = sea ? '해저면 EL' : '지반고'; const scour = river && Number.isFinite(input.scourDepthM) && (input.scourDepthM as number) >= 0 ? (input.scourDepthM as number) : undefined; const scourEl = scour !== undefined ? ground - scour : undefined; const yMin = Math.min(footBottom, lowestRow, ...pileTips, level ?? ground, lowLevel ?? ground, scourEl ?? ground) - 1; const yMax = Math.max(ground, footTop, input.copingTopEl) + 0.5; const scale = PLOT_H / (yMax - yMin); const y = (el: number) => TOP_PAD + (yMax - el) * scale; const H = TOP_PAD + PLOT_H + 16; const parts: string[] = []; // Layers: each band runs from the upper boundary (ground for the first) down to its 하단 EL. let upper = ground; rows.forEach((row, i) => { const top = y(upper); const bottom = y(row.bottomEl); parts.push(``); parts.push(`${escapeText(row.name)}`); if (row.name === SUPPORT_STRATUM) { parts.push(``); // Caption just under the line, so it does not collide with the label of the band above. parts.push(`연암층 상면 EL ${upper.toFixed(3)}`); } upper = row.bottomEl; }); if (rows.length === 0 || upper > yMin) { parts.push(``); } // Ground line and label. parts.push(``); parts.push(`${groundLabel} ${ground.toFixed(3)}`); // Groundwater (육상, when on) as a dashed line; the 설계홍수위 (하상) as a solid line. if (level !== undefined) { const dash = river ? '' : ' stroke-dasharray="6 4"'; const label = river ? '설계홍수위 DHWL' : sea ? '고조위 HWL' : '지하수위'; parts.push(``); parts.push(`${label} ${level.toFixed(3)}`); } // 해상: 저조위 (dashed, MSL datum). if (lowLevel !== undefined) { parts.push(``); parts.push(`저조위 LWL ${lowLevel.toFixed(3)}`); } // 세굴선 (하상·해상): the bed after scour. if (scourEl !== undefined) { parts.push(``); parts.push(`세굴선 ${scourEl.toFixed(3)}`); } // Footing, column stub, and piles. const cx = WIDTH / 2; const colW = 30; parts.push(``); const footW = 140; parts.push(``); const sValues = [...new Set(model.piles.map((p) => p.s))].sort((a, b) => a - b); const spread = sValues.length > 1 ? (footW - 20) / (sValues.length - 1) : 0; sValues.forEach((s) => { const tips = model.piles.filter((p) => p.s === s); const x = sValues.length > 1 ? cx - footW / 2 + 10 + sValues.indexOf(s) * spread : cx; const tip = Math.min(...tips.map((p) => p.bottom)); parts.push(``); }); return `${parts.join('')}`; } function escapeText(s: string): string { return s.replace(/&/g, '&').replace(//g, '>'); }