Files
Aislo/B05_Profile/logic/B05_Profile_Profile_CrossPick.ts
T

55 lines
2.6 KiB
TypeScript

/* =============================================================================
* 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;
}