diff --git a/B05_Profile/B05_Profile_UI_Page.ts b/B05_Profile/B05_Profile_UI_Page.ts index 96597bf0..e324fe6b 100644 --- a/B05_Profile/B05_Profile_UI_Page.ts +++ b/B05_Profile/B05_Profile_UI_Page.ts @@ -1,3 +1,4 @@ +import { dropStationsNear } from "./B05_Profile_Util_Station"; import { CURRENT_PROJECT_ID_KEY, ROUTES } from "@config/config_frontend"; import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale"; import { showToast } from "@ui/ui_template_elements"; @@ -449,7 +450,9 @@ export async function renderB05Route(root: HTMLElement): Promise { return best.elevation_m; }; // 측점 바 양 끝 램프용 상단측: 사용자 변경분 → solve 자동 판정 순으로 적용. - const withUphill = [...regular, ...injected].map((station) => { + // 구조물 측점과 0.1m 안에서 겹치는 규칙 측점은 지운다 — 3D 측점 띠도 종단 그래프와 + // 같은 규칙을 써야 두 화면의 측점이 어긋나지 않는다(2026-09-06). + const withUphill = [...dropStationsNear(regular, injected), ...injected].map((station) => { const design = designAt(station.chainage_m); return { ...station, diff --git a/B05_Profile/B05_Profile_UI_Profile_Render.ts b/B05_Profile/B05_Profile_UI_Profile_Render.ts index cc84fe5d..8b01e971 100644 --- a/B05_Profile/B05_Profile_UI_Profile_Render.ts +++ b/B05_Profile/B05_Profile_UI_Profile_Render.ts @@ -8,6 +8,7 @@ * 거친다. 그래프·테이블·유토곡선이 같은 X 매핑을 쓰는 규칙은 그대로다. * ========================================================================== */ +import { dropStationsNear } from "./B05_Profile_Util_Station"; import { createLongitudinalProfile, longitudinalMinimumWidth, @@ -237,9 +238,13 @@ export function renderProfile(ctx: ProfileRenderContext): void { // 그대로 남는다(2026-08-02 사용자 보고). const regular = graphData.stations.filter((station) => station.kind !== "irregular"); const injected = irregularGraphStations(irregularStations, maxChainageOf(longitudinal)); + // 구조물 측점과 0.1m 안에서 겹치는 규칙 측점은 지운다 — 세로선·라벨이 두 겹으로 + // 겹쳐 읽히지 않는다(2026-09-06). 남는 쪽은 구조물 측점이다. const graphLongitudinal = { ...graphData, - stations: [...regular, ...injected].sort((a, b) => a.chainage_m - b.chainage_m), + stations: [...dropStationsNear(regular, injected), ...injected].sort( + (a, b) => a.chainage_m - b.chainage_m, + ), }; // 세로 자동 맞춤 — 지금 화면에 보이는 누가거리 구간만 보고 Y 창을 잡는다(2026-09-04 // 사용자 확정). 가로 스크롤 위치(`scrollLeft`)와 본문 폭이 곧 보이는 구간이다. diff --git a/B05_Profile/B05_Profile_Util_Station.ts b/B05_Profile/B05_Profile_Util_Station.ts index da5ee30a..b6859a0f 100644 --- a/B05_Profile/B05_Profile_Util_Station.ts +++ b/B05_Profile/B05_Profile_Util_Station.ts @@ -52,3 +52,24 @@ export function parseStationText(text: string, intervalM: number): number | null const direct = Number(trimmed); return Number.isFinite(direct) && direct >= 0 ? direct : null; } + +/** 구조물 측점과 겹쳤다고 볼 거리(m) — 이보다 가까우면 규칙 측점을 지운다. */ +export const STATION_MERGE_TOLERANCE_M = 0.1; + +/** + * 구조물 측점과 **겹치는 규칙 측점을 지운다**(2026-09-06). + * + * 구조물은 보조말뚝을 박아 그 자리가 정본 측점이 된다. 규칙 격자(20m)와 0.1m 안에서 + * 만나면 세로선·라벨이 두 겹으로 겹쳐 읽히지 않으므로 구조물 쪽만 남긴다. + */ +export function dropStationsNear( + stations: readonly T[], + anchors: ReadonlyArray<{ chainage_m: number }>, + toleranceM: number = STATION_MERGE_TOLERANCE_M, +): T[] { + if (anchors.length === 0) return [...stations]; + return stations.filter( + (station) => + !anchors.some((anchor) => Math.abs(anchor.chainage_m - station.chainage_m) <= toleranceM), + ); +}