B06_계획노선 반영

This commit is contained in:
2026-07-20 19:58:55 +09:00
parent 6781b405d8
commit 7a50540dba
2 changed files with 51 additions and 5 deletions
@@ -48,12 +48,42 @@ function validElevation(sample: SectionSample): sample is SectionSample & { elev
);
}
/**
* 측점 chainage 위치의 계획고를 계획선 샘플에서 선형보간한다.
* 범위를 벗어나면 양 끝값으로 클램프하며, 계획선이 없으면 undefined를 반환해 지반고 폴백을 유도한다.
*/
function designElevationAt(
designProfiles: DesignProfile[] | undefined,
chainageM: number,
): number | undefined {
const samples = designProfiles?.[0]?.samples?.filter((sample) =>
Number.isFinite(sample.elevation_m),
);
if (!samples?.length) return undefined;
if (chainageM <= samples[0].chainage_m) return samples[0].elevation_m;
const last = samples[samples.length - 1];
if (chainageM >= last.chainage_m) return last.elevation_m;
for (let index = 1; index < samples.length; index += 1) {
const previous = samples[index - 1];
const current = samples[index];
if (chainageM > current.chainage_m) continue;
const span = current.chainage_m - previous.chainage_m;
if (span <= 0) return current.elevation_m;
const ratio = (chainageM - previous.chainage_m) / span;
return previous.elevation_m + (current.elevation_m - previous.elevation_m) * ratio;
}
return last.elevation_m;
}
function calculateYScale(detail: SectionDetailResponse): YScaleOptions | undefined {
const elevations = [
...detail.longitudinal.samples.map((sample) => sample.elevation_m),
...detail.cross_sections.flatMap((section) =>
section.samples.map((sample) => sample.elevation_m),
),
...(detail.longitudinal.design_profiles ?? []).flatMap((profile) =>
profile.samples.map((sample) => sample.elevation_m),
),
].filter((value): value is number => typeof value === "number" && Number.isFinite(value));
if (!elevations.length) return undefined;
const globalMinElevation = Math.min(...elevations);
@@ -348,6 +378,7 @@ export function createCrossSectionCard(
crossHalfWidth?: number,
widthPx = CROSS_WIDTH,
heightPx = CROSS_HEIGHT,
designElevation?: number,
): HTMLElement {
const card = document.createElement("article");
card.id = `cross-${section.station_id}`;
@@ -386,7 +417,9 @@ export function createCrossSectionCard(
const offsets = sourceSamples.map((sample) => sample.offset_m ?? 0);
const minOffset = Math.min(...offsets, -1);
const maxOffset = Math.max(...offsets, 1);
const hasDesign = designElevation !== undefined && Number.isFinite(designElevation);
const elevations = valid.map((sample) => sample.elevation_m);
if (hasDesign) elevations.push(designElevation as number);
const rawMin = Math.min(...elevations);
const rawMax = Math.max(...elevations);
const elevationMid = (rawMin + rawMax) / 2;
@@ -478,9 +511,15 @@ export function createCrossSectionCard(
return nearest;
}, null);
const centerX = x(0);
const centerY = centerSample
? y(elevationMid + (centerSample.elevation_m - elevationMid) * exaggeration)
: heightPx / 2;
// 십자선은 계획 노선(계획고) 위치를 가리킨다. 계획선이 없는 구 데이터는 지반고로 폴백한다.
const centerElevation = hasDesign
? (designElevation as number)
: (centerSample?.elevation_m ?? null);
const centerY =
centerElevation !== null
? y(elevationMid + (centerElevation - elevationMid) * exaggeration)
: heightPx / 2;
const centerMarkerClass = `b06-chart__center-marker${hasDesign ? " b06-chart__center-marker--design" : ""}`;
svg.append(
svgElement("line", {
x1: CROSS_PAD.left,
@@ -501,14 +540,14 @@ export function createCrossSectionCard(
y1: centerY - 18,
x2: centerX,
y2: centerY + 18,
class: "b06-chart__center-marker",
class: centerMarkerClass,
}),
svgElement("line", {
x1: centerX - 18,
y1: centerY,
x2: centerX + 18,
y2: centerY,
class: "b06-chart__center-marker",
class: centerMarkerClass,
}),
svgText(L("B06_Profile_View_CrossXAxis"), {
x: widthPx / 2,
@@ -599,6 +638,7 @@ export function createSectionView(): SectionViewController {
Math.max(renderWidth, longitudinalMinWidth),
LONG_HEIGHT,
longitudinalMinWidth,
detail.longitudinal.design_profiles ?? [],
),
);
@@ -629,6 +669,7 @@ export function createSectionView(): SectionViewController {
currentCrossHalfWidth,
cardWidth,
CROSS_HEIGHT,
designElevationAt(detail.longitudinal.design_profiles, section.chainage_m),
),
),
);
@@ -356,6 +356,11 @@
stroke-width: 2.5;
}
/* 십자선이 계획 노선(계획고) 위치를 가리킬 때: 종단 계획선과 동일 색으로 대응시킨다 */
.b06-chart__center-marker--design {
stroke: var(--color-royal-amethyst);
}
.b06-chart__station--selected .b06-chart__station-label {
fill: var(--color-danger);
font-weight: var(--font-weight-bold);