/* ============================================================================= * logic/B05_Profile_Profile_CrossPick.ts * 종단 그래프 한 점 → 임시 횡단을 열 누가거리(정수 m) · 아니면 null(368-3 · 490-1). * * 누름(작은 창 열기)과 가까이 표시가 같은 판정을 씀 — 추가 측점 선 = 그 측점 · 단추 = 그 몫(null) · * 다른 측점선 · 빈 곳 = 원지반선 위아래 `GROUND_SLACK_PX` 안(측점선은 띠 밖이면 측점 고름 몫 · 500-2). * ========================================================================== */ /** 원지반선을 눌렀다고 볼 위아래 여유(px). */ export const GROUND_SLACK_PX = 12; /** 포인터 아래 = 추가 측점 선(그 누가거리) · 다른 측점선("station") · 단추("other") · 빈 곳(null). */ export type CrossPickHit = { plusM: number } | "station" | "other" | null; export type GroundSample = { chainage_m?: number; elevation_m?: number | null }; export interface CrossPickInput { hit: CrossPickHit; /** 포인터 x 의 누가거리. */ chainageM: number; /** 포인터 y(그래프 px). */ pointerY: number; samples: readonly GroundSample[]; /** 표고 → 그래프 y px(세로 창 없으면 NaN). */ groundY: (elevationM: number) => number; } /** 누가거리의 원지반 표고(앞뒤 표본 직선 보간) · 첫 표본 앞 · 끝 뒤 · 빈 표본 = null. */ export function groundElevationAt( samples: readonly GroundSample[], chainageM: number, ): number | null { const valid = samples.filter( (s): s is { chainage_m: number; elevation_m: number } => Number.isFinite(s.chainage_m ?? NaN) && Number.isFinite(s.elevation_m ?? NaN), ); const after = valid.findIndex((s) => s.chainage_m >= chainageM); if (!Number.isFinite(chainageM) || after <= 0) return null; const [a, b] = [valid[after - 1], valid[after]]; const t = (chainageM - a.chainage_m) / (b.chainage_m - a.chainage_m || 1); return a.elevation_m + (b.elevation_m - a.elevation_m) * t; } /** 임시 횡단을 열 누가거리(정수 m) · 아니면 null. */ export function crossPickAt(input: CrossPickInput): number | null { if (input.hit === "other") return null; if (typeof input.hit === "object" && input.hit) return Math.round(input.hit.plusM); const ground = groundElevationAt(input.samples, input.chainageM); if (ground === null) return null; // "station"·null 모두 띠 안이면 임시 횡단 — 측점선은 띠 밖에서만 측점 고름 몫. return Math.abs(input.pointerY - input.groundY(ground)) <= GROUND_SLACK_PX ? Math.round(input.chainageM) : null; }