import type { CrossSection, SectionSample } from "./B06_Section_Api_Fetch"; import { CROSS_HEIGHT, CROSS_PAD, validElevation } from "./B06_Section_UI_Section_Common"; const AREA_OVERLAY_HEADROOM_PX = 58; interface CrossPlotMetrics { sourceSamples: SectionSample[]; minOffset: number; maxOffset: number; elevationMid: number; displaySpan: number; displayMax: number; pixelsPerMeter: number; heightPx: number; } export function crossPlotMetrics( section: CrossSection, verticalExaggeration: number, widthPx: number, crossHalfWidth?: number, designElevation?: number, forcedHeightPx?: number, ): CrossPlotMetrics | null { const sourceSamples = section.samples.filter( (sample) => crossHalfWidth === undefined || Math.abs(sample.offset_m ?? 0) <= crossHalfWidth + 1e-6, ); const valid = sourceSamples.filter(validElevation); if (!valid.length) return null; const offsets = sourceSamples.map((sample) => sample.offset_m ?? 0); const minOffset = Math.min(...offsets, -1); const maxOffset = Math.max(...offsets, 1); const elevations = valid.map((sample) => sample.elevation_m); if (designElevation !== undefined && Number.isFinite(designElevation)) elevations.push(designElevation); const rawMin = Math.min(...elevations); const rawMax = Math.max(...elevations); const elevationMid = (rawMin + rawMax) / 2; const padding = rawMax > rawMin ? (rawMax - rawMin) * 0.08 : 0.5; const pixelsPerMeter = (widthPx - CROSS_PAD.left - CROSS_PAD.right) / Math.max(maxOffset - minOffset, 1e-6); const rawSpan = Math.max( (rawMax - rawMin + padding * 2) * Math.max(verticalExaggeration, 0.1), 1e-6, ); const naturalHeight = rawSpan * pixelsPerMeter + CROSS_PAD.top + CROSS_PAD.bottom + AREA_OVERLAY_HEADROOM_PX; const heightPx = forcedHeightPx ?? Math.max(naturalHeight, CROSS_HEIGHT); const displaySpan = Math.max(heightPx - CROSS_PAD.top - CROSS_PAD.bottom, 1e-6) / pixelsPerMeter; const displayMax = elevationMid + displaySpan / 2 + AREA_OVERLAY_HEADROOM_PX / pixelsPerMeter / 2; return { sourceSamples, minOffset, maxOffset, elevationMid, displaySpan, displayMax, pixelsPerMeter, heightPx, }; } export function crossCardNaturalHeight( section: CrossSection, verticalExaggeration: number, widthPx: number, crossHalfWidth?: number, designElevation?: number, ): number { return ( crossPlotMetrics(section, verticalExaggeration, widthPx, crossHalfWidth, designElevation) ?.heightPx ?? CROSS_HEIGHT ); }