fix(B05): 종단표 구조물 열 상시 표기·직선화 자리 곡선값 제거
- 구조물 측점 판정을 문자열 키에서 누가거리 허용 오차 0.1m 비교로 바꿈 — 구조물 목록(85.59)과 계획선 측점(85.595)이 셋째 자리에서 어긋나 구조물 열을 못 알아보고 값을 그대로 펼쳐 이웃 값과 겹쳐 보였음. 이제 고른 때만 값 열 오버레이로 보임 - 대수차 1e-4% 미만이면 직선으로 보고 곡선 L·R 을 빈 칸으로 둠 — 직선화하면 대수차가 0 이 되어 R = L ÷ |대수차| 가 수억 m 로 발산했음 (표·값 열 오버레이 공통) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,33 @@ const CELL_PADDING_PX = 2;
|
||||
export const TABLE_TARGET_FONT_PX = 12;
|
||||
/** 곡선 셀이 이웃과 겹칠 때 세로(회전) 표기로 줄이는 셀 폭(px). */
|
||||
const CURVE_ROTATED_WIDTH_PX = 16;
|
||||
/**
|
||||
* 같은 측점으로 볼 누가거리 차이(m).
|
||||
*
|
||||
* 구조물 목록의 누가거리(`85.59`)와 계획선 측점의 누가거리(`85.595`)는 소수 셋째 자리에서
|
||||
* 어긋난다 — 정본을 만든 경로가 달라 최대 0.05m 벌어진다(`_Profile_Edit` 의 같은 상수 참조).
|
||||
* 문자열 키로 맞추면 구조물 열을 못 알아봐 값이 그대로 펼쳐졌다(2026-09-04 실측).
|
||||
*/
|
||||
const SAME_STATION_TOLERANCE_M = 0.1;
|
||||
|
||||
/**
|
||||
* 대수차가 이 값보다 작으면 **직선**으로 본다(%).
|
||||
*
|
||||
* 직선화하면 그 자리의 좌·우 기울기가 같아져 대수차가 0 이 되고, `L = R × |대수차|` 를
|
||||
* 뒤집은 R 은 수억 m 로 튄다. 직선 구간에는 반경이 없으므로 표에는 빈 칸으로 둔다
|
||||
* (2026-09-04 사용자 지시). 진짜 완만한 변화점(대수차 0.9% 수준)은 그대로 남는다.
|
||||
*/
|
||||
const STRAIGHT_DELTA_PCT = 1e-4;
|
||||
|
||||
/** 직선화로 대수차가 사라진 자리인가 — 표기 대상에서 뺀다. */
|
||||
function isStraightCurve(curve: AlignmentCurve): boolean {
|
||||
return Math.abs(curve.delta_pct) < STRAIGHT_DELTA_PCT;
|
||||
}
|
||||
|
||||
/** 구조물(비정규) 측점 누가거리 목록에 이 측점이 들어 있는가 — 허용 오차로 본다. */
|
||||
function isNearChainage(chainageM: number, list: ReadonlyArray<number>): boolean {
|
||||
return list.some((entry) => Math.abs(entry - chainageM) < SAME_STATION_TOLERANCE_M);
|
||||
}
|
||||
|
||||
/** 주어진 글자 크기로 가장 긴 값을 자르지 않고 담는 데 필요한 셀 폭. */
|
||||
export function tableCellWidthFor(fontPx: number): number {
|
||||
@@ -301,10 +328,10 @@ function curveTitle(curve: AlignmentCurve): string {
|
||||
function buildCurveRows(
|
||||
options: ProfileTableOptions,
|
||||
centers: number[],
|
||||
/** 구조물 측점 chainage 키(소수 3자리) — 이 열의 곡선 입력은 기본 표기하지 않는다. */
|
||||
structureStationKeys: ReadonlySet<string>,
|
||||
/** 배관 구조물 chainage 키 — R이 필수라 곡선 L·R은 **항상** 표기한다(2026-08-04 지시). */
|
||||
pipeStationKeys: ReadonlySet<string>,
|
||||
/** 구조물 측점 누가거리 — 이 열의 곡선 입력은 기본 표기하지 않는다. */
|
||||
structureChainageList: ReadonlyArray<number>,
|
||||
/** 배관 구조물 누가거리 — R이 필수라 곡선 L·R은 **항상** 표기한다(2026-08-04 지시). */
|
||||
pipeChainageList: ReadonlyArray<number>,
|
||||
/** 기본 글자 크기(px)와 행 높이(px) — 회전 셀의 글자 축소 산식에 쓴다. */
|
||||
fontPx: number,
|
||||
rowHeightPx: number,
|
||||
@@ -330,10 +357,15 @@ function buildCurveRows(
|
||||
// 그대로 서고, 글자 크기는 행 높이에 맞춰 줄인다(구배 행 is-rotated와 같은 방식).
|
||||
const curveEntries = alignment.stations.map((station, index) => {
|
||||
const key = station.chainage_m.toFixed(3);
|
||||
const isPipe = pipeStationKeys.has(key);
|
||||
const isPipe = isNearChainage(station.chainage_m, pipeChainageList);
|
||||
// 구조물 측점 열은 곡선 입력도 기본 표기하지 않는다(빈 칸으로 격자만 유지).
|
||||
// 예외: 배관 측점은 R이 필수 입력이라 곡선 L·R을 항상 보인다.
|
||||
const curve = structureStationKeys.has(key) && !isPipe ? undefined : curveByChainage.get(key);
|
||||
const found =
|
||||
isNearChainage(station.chainage_m, structureChainageList) && !isPipe
|
||||
? undefined
|
||||
: curveByChainage.get(key);
|
||||
// 직선화된 자리는 반경이 없다 — 빈 칸으로 둔다.
|
||||
const curve = found && !isStraightCurve(found) ? found : undefined;
|
||||
return { center: centers[index], curve, rotated: false };
|
||||
});
|
||||
const withCurve = curveEntries.filter((entry) => entry.curve);
|
||||
@@ -471,7 +503,9 @@ function buildSelectedColumn(
|
||||
const right = rightSeg ? pick(rightSeg).toFixed(digits) : "";
|
||||
return left && right ? { left, right } : { text: left || right || "" };
|
||||
};
|
||||
const curve = alignment.curves.find((entry) => Math.abs(entry.chainage_m - chainage) < 0.01);
|
||||
const curveAt = alignment.curves.find((entry) => Math.abs(entry.chainage_m - chainage) < 0.01);
|
||||
// 값 열 오버레이도 같은 규칙 — 직선화된 자리는 곡선 L·R 을 비운다.
|
||||
const curve = curveAt && !isStraightCurve(curveAt) ? curveAt : undefined;
|
||||
// 거리: 바로 앞 측점(계획선 측점)까지의 간격.
|
||||
const previous = alignment.stations
|
||||
.filter((row) => row.chainage_m < chainage - 1e-6)
|
||||
@@ -567,15 +601,11 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement {
|
||||
// 확정 시 종단 정본에 병합된 구조물 측점은 alignment.stations에 규칙 측점처럼 끼어 있다.
|
||||
// 그 열의 기본값은 표기하지 않는다(2026-08-04 사용자 지시) — 사이드바 구조물 목록의
|
||||
// chainage와 일치하는 측점이 대상이고, 값은 선택 시 값 열 오버레이(하이라이트)가 보여준다.
|
||||
const structureStationKeys = new Set(
|
||||
(options.irregularStations ?? []).map((entry) => entry.chainage_m.toFixed(3)),
|
||||
);
|
||||
const structureChainageList = (options.irregularStations ?? []).map((entry) => entry.chainage_m);
|
||||
// 배관 구조물은 R이 필수라 곡선 행만은 기본 표기 예외다(2026-08-04 사용자 지시).
|
||||
const pipeStationKeys = new Set(
|
||||
(options.irregularStations ?? [])
|
||||
.filter((entry) => isPipeStation(entry))
|
||||
.map((entry) => entry.chainage_m.toFixed(3)),
|
||||
);
|
||||
const pipeChainageList = (options.irregularStations ?? [])
|
||||
.filter((entry) => isPipeStation(entry))
|
||||
.map((entry) => entry.chainage_m);
|
||||
const selectedIrregularEntry = options.irregularStations?.find(
|
||||
(entry) => irregularStationId(entry.id) === options.selectedStationId,
|
||||
);
|
||||
@@ -596,7 +626,7 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement {
|
||||
// 값이 없는 측점(절토고/성토고 중 한쪽)도 빈 칸을 만들어야 세로 구분선이 끊기지 않는다.
|
||||
alignment.stations.forEach((station, stationIndex) => {
|
||||
// 구조물 측점 열은 기본값을 비운다 — 격자(빈 셀)만 남기고 값은 선택 오버레이가 맡는다.
|
||||
const structural = structureStationKeys.has(station.chainage_m.toFixed(3));
|
||||
const structural = isNearChainage(station.chainage_m, structureChainageList);
|
||||
const cell = element(
|
||||
"span",
|
||||
`b05-profile-table__cell${structural ? " is-structure" : ""}`,
|
||||
@@ -607,7 +637,14 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement {
|
||||
table.append(row);
|
||||
});
|
||||
table.append(
|
||||
...buildCurveRows(options, centers, structureStationKeys, pipeStationKeys, fontSize, rowHeight),
|
||||
...buildCurveRows(
|
||||
options,
|
||||
centers,
|
||||
structureChainageList,
|
||||
pipeChainageList,
|
||||
fontSize,
|
||||
rowHeight,
|
||||
),
|
||||
);
|
||||
|
||||
// 선택된 측점(규칙·비정규 공용)을 **값 열 오버레이**로 강조한다.
|
||||
|
||||
Reference in New Issue
Block a user