Files
Aislo/B05_Profile/logic/B05_Profile_Profile_CrossPick.ts
T
eomsangdonandClaude Opus 5.5 6414fb83d8 feat(490-1 임시 횡단 표시): B05 종단 그래프 — 누르면 임시 횡단이 열리는 자리 가까이 원 · 세로 점선 · 측점 글 · 손가락 커서
- 판정 셈을 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>
2026-10-19 22:43:19 +09:00

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;
}