diff --git a/B05_Profile/B05_Profile_UI_Profile_Panel.ts b/B05_Profile/B05_Profile_UI_Profile_Panel.ts index 6985a5ff..07489011 100644 --- a/B05_Profile/B05_Profile_UI_Profile_Panel.ts +++ b/B05_Profile/B05_Profile_UI_Profile_Panel.ts @@ -29,6 +29,7 @@ import { createProgressCircle } from "@ui/ui_template_progress"; import { showToast } from "@ui/ui_template_elements"; import { saveProfileAlignment } from "./B05_Profile_Api_Fetch"; import { previewCrossDesigns } from "../B06_Section/B06_Section_Api_Fetch"; +import { staleDesignChainages } from "../B06_Section/B06_Section_UI_Section_Common"; import { findMinCoverViolations, minCoverPoints, @@ -598,6 +599,10 @@ export function createRouteProfilePanel( ); draw(); requestAnimationFrame(draw); + // 저장된 횡단 설계가 옛 계획고로 굳어 있으면 진입 즉시 한 번 맞춘다 — + // 지금까지는 B06에 들어가야만 고쳐져, B05의 횡단 기준 유토곡선과 3D + // 예상형상이 옛 설계선을 그대로 썼다(2026-08-23 사용자 보고 · 실측 확인). + if (staleDesignChainages(nextDetail).length) scheduleCrossPreview(); }, /** 라이브 계획선 샘플(편집 반영분) — 3D 측점 바·코리도가 이걸 본다(2026-08-23). * 편집 전·base 없음이면 null — 호출부는 정본 design_profiles로 폴백한다. */ diff --git a/B06_Section/B06_Section_UI_Page.ts b/B06_Section/B06_Section_UI_Page.ts index c9fd8f79..e53e5b9f 100644 --- a/B06_Section/B06_Section_UI_Page.ts +++ b/B06_Section/B06_Section_UI_Page.ts @@ -41,7 +41,7 @@ import { import { computeMassHaul, massHaulPayload } from "@util/common_util_mass_haul"; import { computeHaulPlan } from "@util/common_util_mass_haul_balance"; import { balloonOffsetsPayload } from "@util/common_util_mass_haul_balance_view"; -import { designElevationAt } from "./B06_Section_UI_Section_Common"; +import { staleDesignChainages } from "./B06_Section_UI_Section_Common"; import { createStandardPanel, type StandardPanelController } from "./B06_Section_UI_Standard_Panel"; import "./B06_Section_UI_Style.css"; import "./B06_Section_UI_Style_Cross.css"; @@ -203,13 +203,14 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise { */ async function reconcileStaleDesigns(): Promise { if (!sectionDetail || !projectId || currentRouteId === null) return; - const profiles = sectionDetail.longitudinal.design_profiles; + // 계획고 어긋남은 B05와 **같은 규칙**으로 판정한다(공용 staleDesignChainages). + // 옛 암 2단계 필드 누락은 B06 전용 조건이라 여기서 더한다. + const staleByPlan = new Set(staleDesignChainages(sectionDetail)); const stale = sectionDetail.cross_sections.filter((section) => { const design = section.design; if (!design) return false; if (design.geometry_preset === "rock" && design.two_stage_slope === undefined) return true; - const planZ = designElevationAt(profiles, section.chainage_m); - return planZ !== undefined && Math.abs(planZ - design.design_elevation_m) > 1e-3; + return staleByPlan.has(section.chainage_m); }); if (!stale.length) return; showLoadingOverlay(); diff --git a/B06_Section/B06_Section_UI_Section_Common.ts b/B06_Section/B06_Section_UI_Section_Common.ts index 6b46112d..9cdca760 100644 --- a/B06_Section/B06_Section_UI_Section_Common.ts +++ b/B06_Section/B06_Section_UI_Section_Common.ts @@ -68,6 +68,35 @@ export function validElevation( * 측점 chainage 위치의 계획고를 계획선 샘플에서 선형보간한다. * 범위를 벗어나면 양 끝값으로 클램프하며, 계획선이 없으면 undefined를 반환해 지반고 폴백을 유도한다. */ +/** + * 저장된 횡단 설계가 **현재 계획선과 어긋난 측점**을 찾는다(B05·B06 공용 규칙). + * + * 횡단 설계는 계산 당시 계획고(`design.design_elevation_m`)를 기준으로 설계선 좌표를 + * 굳혀 둔다. 계획선이 그 뒤에 바뀌면(사용자 편집·확정, 배수 최소고 같은 백엔드 규칙 + * 도입) 설계선은 옛 계획고 자리에 남는데 화면의 계획고 십자선·3D는 최신 계획선을 쓴다 + * — 두 기준이 어긋나 횡단도·3D가 종단을 안 따라오는 것처럼 보인다(2026-08-23 실측: + * 한 노선에서 최대 2.21m 어긋남). 재계산 대상을 한 규칙으로 판정해 두 화면이 같은 + * 시점에 같은 조치를 하게 한다. + */ +export function staleDesignChainages( + detail: { + longitudinal: { design_profiles?: DesignProfile[] }; + cross_sections: Array<{ chainage_m: number; design?: { design_elevation_m: number } | null }>; + }, + toleranceM = 1e-3, +): number[] { + const profiles = detail.longitudinal.design_profiles; + if (!profiles?.length) return []; + return detail.cross_sections + .filter((section) => { + const design = section.design; + if (!design) return false; + const planned = designElevationAt(profiles, section.chainage_m); + return planned !== undefined && Math.abs(planned - design.design_elevation_m) > toleranceM; + }) + .map((section) => section.chainage_m); +} + export function designElevationAt( designProfiles: DesignProfile[] | undefined, chainageM: number,