55 lines
2.6 KiB
TypeScript
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;
|
|
}
|