B06_계획노선 반영
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user