- 판정 셈을 logic/B05_Profile_Profile_CrossPick.ts 한 함수(crossPickAt)로 뺌 — 누름 · 표시가 같은 판정 - 원지반선 위아래 8 px · 추가 측점 선 = 그 측점 · 측점선 · 단추 · 도구 중 · 끌기 · 스크롤 = 안 그림 - 포인터 이동 소수 px 를 누름과 같이 내려 맞춤 — 0.5 m 경계 1 m 어긋남 막음 - 시험 test_490_1_cross_pick · test_368_3 연결 자리 갱신 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54 lines
2.4 KiB
TypeScript
54 lines
2.4 KiB
TypeScript
/* =============================================================================
|
|
* logic/B05_Profile_Profile_CrossPick.ts
|
|
* 종단 그래프 한 점 → 임시 횡단을 열 누가거리(정수 m) · 아니면 null(368-3 · 490-1).
|
|
*
|
|
* 누름(작은 창 열기)과 가까이 표시가 같은 판정을 씀 — 추가 측점 선 = 그 측점 · 다른 측점선 · 단추 =
|
|
* 그 몫(null) · 빈 곳 = 원지반선 위아래 `GROUND_SLACK_PX` 안.
|
|
* ========================================================================== */
|
|
|
|
/** 원지반선을 눌렀다고 볼 위아래 여유(px). */
|
|
export const GROUND_SLACK_PX = 8;
|
|
|
|
/** 포인터 아래 = 추가 측점 선(그 누가거리) · 다른 측점선 · 단추("other") · 빈 곳(null). */
|
|
export type CrossPickHit = { plusM: number } | "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 (input.hit) return Math.round(input.hit.plusM);
|
|
const ground = groundElevationAt(input.samples, input.chainageM);
|
|
if (ground === null) return null;
|
|
return Math.abs(input.pointerY - input.groundY(ground)) <= GROUND_SLACK_PX
|
|
? Math.round(input.chainageM)
|
|
: null;
|
|
}
|