Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_1
This commit is contained in:
@@ -63,8 +63,12 @@ import {
|
|||||||
svgText,
|
svgText,
|
||||||
validElevation,
|
validElevation,
|
||||||
} from "./B06_Section_UI_Section_Common";
|
} from "./B06_Section_UI_Section_Common";
|
||||||
import { crossPlotMetrics, effectiveCardHalfWidth } from "./B06_Section_UI_Cross_View_Metrics";
|
import {
|
||||||
export { crossCardNaturalHeight } from "./B06_Section_UI_Cross_View_Metrics";
|
type CrossPlotBase,
|
||||||
|
crossPlotFromBase,
|
||||||
|
crossPlotMetrics,
|
||||||
|
effectiveCardHalfWidth,
|
||||||
|
} from "./B06_Section_UI_Cross_View_Metrics";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 횡단 카드 요소. 선택 표시와 면적 강조를 **카드를 다시 만들지 않고** 갈아 끼우는 핸들을 단다
|
* 횡단 카드 요소. 선택 표시와 면적 강조를 **카드를 다시 만들지 않고** 갈아 끼우는 핸들을 단다
|
||||||
@@ -144,6 +148,8 @@ export function createCrossSectionCard(
|
|||||||
/** 세월교 측벽·BOX암거 구체 조작 제어(2026-08-25). */
|
/** 세월교 측벽·BOX암거 구체 조작 제어(2026-08-25). */
|
||||||
ford?: FordControl,
|
ford?: FordControl,
|
||||||
box?: BoxControl,
|
box?: BoxControl,
|
||||||
|
/** 행 높이를 재며 이미 만들어 둔 기하 — 있으면 다시 계산하지 않는다(2026-09-06). */
|
||||||
|
plotBase?: CrossPlotBase | null,
|
||||||
): CrossCardElement {
|
): CrossCardElement {
|
||||||
// 링크 카드 = 구조물이 옆 측점에 서 있고 그 연장이 여기까지 온 경우. 관만 숨기고
|
// 링크 카드 = 구조물이 옆 측점에 서 있고 그 연장이 여기까지 온 경우. 관만 숨기고
|
||||||
// 선택·조정은 연다 — 연동을 풀어 이 측점 위치를 따로 잡을 수 있어야 한다
|
// 선택·조정은 연다 — 연동을 풀어 이 측점 위치를 따로 잡을 수 있어야 한다
|
||||||
@@ -274,14 +280,18 @@ export function createCrossSectionCard(
|
|||||||
|
|
||||||
appendCardHeader(card, section, stationInterval, onDesignChange);
|
appendCardHeader(card, section, stationInterval, onDesignChange);
|
||||||
|
|
||||||
const metrics = crossPlotMetrics(
|
// 행 높이를 재며 만든 기하가 있으면 그대로 쓴다 — 같은 측점의 기하를 두 번 계산하던
|
||||||
section,
|
// 것이 B06 진입에서 `draw` 자기 시간의 대부분이었다(2026-09-06 CPU 프로파일).
|
||||||
verticalExaggeration,
|
const metrics = plotBase
|
||||||
widthPx,
|
? crossPlotFromBase(plotBase, forcedHeightPx)
|
||||||
effectiveHalfWidth,
|
: crossPlotMetrics(
|
||||||
designElevation,
|
section,
|
||||||
forcedHeightPx,
|
verticalExaggeration,
|
||||||
);
|
widthPx,
|
||||||
|
effectiveHalfWidth,
|
||||||
|
designElevation,
|
||||||
|
forcedHeightPx,
|
||||||
|
);
|
||||||
if (!metrics) {
|
if (!metrics) {
|
||||||
card.append(emptyView(L("B06_Profile_View_NoCross")));
|
card.append(emptyView(L("B06_Profile_View_NoCross")));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { toeFitHalfWidth } from "./B06_Section_UI_Cross_Fit";
|
|||||||
|
|
||||||
const AREA_OVERLAY_HEADROOM_PX = 58;
|
const AREA_OVERLAY_HEADROOM_PX = 58;
|
||||||
|
|
||||||
interface CrossPlotMetrics {
|
export interface CrossPlotMetrics {
|
||||||
sourceSamples: SectionSample[];
|
sourceSamples: SectionSample[];
|
||||||
minOffset: number;
|
minOffset: number;
|
||||||
maxOffset: number;
|
maxOffset: number;
|
||||||
@@ -16,14 +16,28 @@ interface CrossPlotMetrics {
|
|||||||
heightPx: number;
|
heightPx: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function crossPlotMetrics(
|
/**
|
||||||
|
* 강제 높이를 얹기 **전**까지의 값 — 카드 두 벌(행 높이 재기 / 실제 그리기)이 나눠 쓴다.
|
||||||
|
* 예전에는 같은 측점의 기하를 두 번 계산했고, 그 값이 B06 진입에서 `draw` 자기 시간
|
||||||
|
* 213ms 의 대부분이었다(2026-09-06 CPU 프로파일, 측점 67곳 기준 3.2ms/장).
|
||||||
|
*/
|
||||||
|
export interface CrossPlotBase {
|
||||||
|
sourceSamples: SectionSample[];
|
||||||
|
minOffset: number;
|
||||||
|
maxOffset: number;
|
||||||
|
elevationMid: number;
|
||||||
|
pixelsPerMeter: number;
|
||||||
|
/** 강제 높이가 없을 때 쓰는 높이(바닥 `CROSS_HEIGHT` 적용 전). */
|
||||||
|
naturalHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function crossPlotBase(
|
||||||
section: CrossSection,
|
section: CrossSection,
|
||||||
verticalExaggeration: number,
|
verticalExaggeration: number,
|
||||||
widthPx: number,
|
widthPx: number,
|
||||||
crossHalfWidth?: number,
|
crossHalfWidth?: number,
|
||||||
designElevation?: number,
|
designElevation?: number,
|
||||||
forcedHeightPx?: number,
|
): CrossPlotBase | null {
|
||||||
): CrossPlotMetrics | null {
|
|
||||||
const sourceSamples = section.samples.filter(
|
const sourceSamples = section.samples.filter(
|
||||||
(sample) =>
|
(sample) =>
|
||||||
crossHalfWidth === undefined || Math.abs(sample.offset_m ?? 0) <= crossHalfWidth + 1e-6,
|
crossHalfWidth === undefined || Math.abs(sample.offset_m ?? 0) <= crossHalfWidth + 1e-6,
|
||||||
@@ -48,32 +62,44 @@ export function crossPlotMetrics(
|
|||||||
);
|
);
|
||||||
const naturalHeight =
|
const naturalHeight =
|
||||||
rawSpan * pixelsPerMeter + CROSS_PAD.top + CROSS_PAD.bottom + AREA_OVERLAY_HEADROOM_PX;
|
rawSpan * pixelsPerMeter + CROSS_PAD.top + CROSS_PAD.bottom + AREA_OVERLAY_HEADROOM_PX;
|
||||||
const heightPx = forcedHeightPx ?? Math.max(naturalHeight, CROSS_HEIGHT);
|
return { sourceSamples, minOffset, maxOffset, elevationMid, pixelsPerMeter, naturalHeight };
|
||||||
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;
|
|
||||||
|
/** 재 둔 기하에 행 높이만 얹는다 — 다시 계산하지 않는다. */
|
||||||
|
export function crossPlotFromBase(base: CrossPlotBase, forcedHeightPx?: number): CrossPlotMetrics {
|
||||||
|
const heightPx = forcedHeightPx ?? Math.max(base.naturalHeight, CROSS_HEIGHT);
|
||||||
|
const displaySpan =
|
||||||
|
Math.max(heightPx - CROSS_PAD.top - CROSS_PAD.bottom, 1e-6) / base.pixelsPerMeter;
|
||||||
|
const displayMax =
|
||||||
|
base.elevationMid + displaySpan / 2 + AREA_OVERLAY_HEADROOM_PX / base.pixelsPerMeter / 2;
|
||||||
return {
|
return {
|
||||||
sourceSamples,
|
sourceSamples: base.sourceSamples,
|
||||||
minOffset,
|
minOffset: base.minOffset,
|
||||||
maxOffset,
|
maxOffset: base.maxOffset,
|
||||||
elevationMid,
|
elevationMid: base.elevationMid,
|
||||||
displaySpan,
|
displaySpan,
|
||||||
displayMax,
|
displayMax,
|
||||||
pixelsPerMeter,
|
pixelsPerMeter: base.pixelsPerMeter,
|
||||||
heightPx,
|
heightPx,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function crossCardNaturalHeight(
|
export function crossPlotMetrics(
|
||||||
section: CrossSection,
|
section: CrossSection,
|
||||||
verticalExaggeration: number,
|
verticalExaggeration: number,
|
||||||
widthPx: number,
|
widthPx: number,
|
||||||
crossHalfWidth?: number,
|
crossHalfWidth?: number,
|
||||||
designElevation?: number,
|
designElevation?: number,
|
||||||
): number {
|
forcedHeightPx?: number,
|
||||||
return (
|
): CrossPlotMetrics | null {
|
||||||
crossPlotMetrics(section, verticalExaggeration, widthPx, crossHalfWidth, designElevation)
|
const base = crossPlotBase(
|
||||||
?.heightPx ?? CROSS_HEIGHT
|
section,
|
||||||
|
verticalExaggeration,
|
||||||
|
widthPx,
|
||||||
|
crossHalfWidth,
|
||||||
|
designElevation,
|
||||||
);
|
);
|
||||||
|
return base ? crossPlotFromBase(base, forcedHeightPx) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,10 +35,15 @@ import {
|
|||||||
type RevetOffsetControl,
|
type RevetOffsetControl,
|
||||||
type RevetLinkControl,
|
type RevetLinkControl,
|
||||||
type StructureSpanControl,
|
type StructureSpanControl,
|
||||||
crossCardNaturalHeight,
|
|
||||||
type CrossCardElement,
|
type CrossCardElement,
|
||||||
type StationWidthControl,
|
type StationWidthControl,
|
||||||
} from "./B06_Section_UI_Cross_View";
|
} from "./B06_Section_UI_Cross_View";
|
||||||
|
import {
|
||||||
|
type CrossPlotBase,
|
||||||
|
crossPlotBase,
|
||||||
|
effectiveCardHalfWidth,
|
||||||
|
} from "./B06_Section_UI_Cross_View_Metrics";
|
||||||
|
import { CROSS_HEIGHT } from "./B06_Section_UI_Section_Common";
|
||||||
import { culvertLinkFor as culvertLink } from "./B06_Section_UI_Cross_Culvert_Wire";
|
import { culvertLinkFor as culvertLink } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||||
import type { CulvertLink } from "./B06_Section_UI_Cross_Culvert_Wire";
|
import type { CulvertLink } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||||
import { longitudinalMinimumWidth } from "./B06_Section_UI_Longitudinal";
|
import { longitudinalMinimumWidth } from "./B06_Section_UI_Longitudinal";
|
||||||
@@ -405,7 +410,11 @@ export function createSectionView(
|
|||||||
return planZ - (section.design?.surface_drop_m ?? 0);
|
return planZ - (section.design?.surface_drop_m ?? 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildCrossCard = (section: CrossSection, forcedHeightPx?: number): HTMLElement =>
|
const buildCrossCard = (
|
||||||
|
section: CrossSection,
|
||||||
|
forcedHeightPx?: number,
|
||||||
|
plotBase?: CrossPlotBase | null,
|
||||||
|
): HTMLElement =>
|
||||||
createCrossSectionCard(
|
createCrossSectionCard(
|
||||||
section,
|
section,
|
||||||
section.station_id === selectedStationId,
|
section.station_id === selectedStationId,
|
||||||
@@ -429,6 +438,7 @@ export function createSectionView(
|
|||||||
revetLink,
|
revetLink,
|
||||||
ford,
|
ford,
|
||||||
box,
|
box,
|
||||||
|
plotBase,
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -583,16 +593,22 @@ export function createSectionView(
|
|||||||
if (detail.cross_sections.length) {
|
if (detail.cross_sections.length) {
|
||||||
// 1차: 각 카드의 자연 높이(250px 바닥 적용) 측정 → 같은 행(columnCount 단위) 최댓값을 행 높이로.
|
// 1차: 각 카드의 자연 높이(250px 바닥 적용) 측정 → 같은 행(columnCount 단위) 최댓값을 행 높이로.
|
||||||
const sections = detail.cross_sections;
|
const sections = detail.cross_sections;
|
||||||
const naturalHeights = sections.map((section) =>
|
// 여기서 만든 기하를 2차에서 **그대로 넘긴다** — 예전에는 같은 측점을 두 번 계산했고
|
||||||
crossCardNaturalHeight(
|
// 그것이 진입에서 `draw` 자기 시간의 대부분이었다(2026-09-06 CPU 프로파일).
|
||||||
|
// 반폭도 카드가 실제로 쓰는 값(`effectiveCardHalfWidth`, 자동 줌아웃 포함)으로 맞춰
|
||||||
|
// 재 둔다 — 카드·행 높이가 어긋나지 않는다.
|
||||||
|
const plotBases = sections.map((section) =>
|
||||||
|
crossPlotBase(
|
||||||
section,
|
section,
|
||||||
currentExaggeration,
|
currentExaggeration,
|
||||||
cachedCardWidth,
|
cachedCardWidth,
|
||||||
// 개별 표시 반폭이 있으면 그 폭 기준으로 높이를 재야 카드·행 높이가 맞는다.
|
effectiveCardHalfWidth(section, stationWidth?.widthFor(section) ?? currentCrossHalfWidth),
|
||||||
stationWidth?.widthFor(section) ?? currentCrossHalfWidth,
|
|
||||||
designElevationAt(detail.longitudinal.design_profiles, section.chainage_m),
|
designElevationAt(detail.longitudinal.design_profiles, section.chainage_m),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const naturalHeights = plotBases.map((base) =>
|
||||||
|
Math.max(base?.naturalHeight ?? CROSS_HEIGHT, CROSS_HEIGHT),
|
||||||
|
);
|
||||||
// 2차: 행별 최댓값으로 강제 높이를 정해 같은 행 카드를 동일 높이로 렌더한다.
|
// 2차: 행별 최댓값으로 강제 높이를 정해 같은 행 카드를 동일 높이로 렌더한다.
|
||||||
for (let start = 0; start < sections.length; start += columnCount) {
|
for (let start = 0; start < sections.length; start += columnCount) {
|
||||||
const rowHeight = Math.max(...naturalHeights.slice(start, start + columnCount));
|
const rowHeight = Math.max(...naturalHeights.slice(start, start + columnCount));
|
||||||
@@ -602,7 +618,7 @@ export function createSectionView(
|
|||||||
index += 1
|
index += 1
|
||||||
) {
|
) {
|
||||||
cachedRowHeight.set(sections[index].station_id, rowHeight);
|
cachedRowHeight.set(sections[index].station_id, rowHeight);
|
||||||
grid.append(buildCrossCard(sections[index], rowHeight));
|
grid.append(buildCrossCard(sections[index], rowHeight, plotBases[index]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user