- B06_Section_Router.py(824) → Router_Design.py(139) 분리 - Cross_View → Cross_View_Metrics.ts(75) 분리 - Cross_Design → Cross_Design_Surface.ts(54) 분리 - Section_View → Section_View_Panel.ts(53) 분리 - Page → Page_Common.ts(15) 분리 - Style_Cross.css → Style_Cross_Controls.css(174) 분리 - B06_Section/ 전체 700줄 초과 0건 검증: tsc --noEmit 통과 · ruff check 통과(import 정렬·포맷 적용) · prettier 적용 · pytest tmp/tests/ 148 passed, 7 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
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
|
|
);
|
|
}
|