/* ============================================================================= * B05_Profile/logic/B05_Profile_RouteEdit_StructureMarks.ts * 평면 구조물 측점 마커(399 · 요구 250-2) — 노선 따라 정수 m 자리 · 방향키 부호 · 고른 마커. * * · 끌기 · 방향키 = 노선 위 정수 m(시점 0 ~ 종점 내림 · 밖은 끝에 붙음) · 방향키 한 번 1 m(Shift 없음). * · 방향키 부호 = 누른 화면 방향이 노선 앞쪽과 같은 쪽이면 + · 거의 직각이면 → · ↑ = +. * · 고른 마커 = 공용 고름 구조물 id 와 같은 것 · id 없으면(관 고름) 그 누가거리 가까운 것. * 파이썬 짝 아님. 순수 계산 — 화면 · fetch · sessionStorage 없음. 부르는 쪽은 `_UI_RouteEdit_StructureMarks`. * ========================================================================== */ import type { ArrowKey } from "./B05_Profile_RouteEdit_KeyNudge"; import type { Vertex } from "./B05_Profile_UI_RouteEdit_Curve"; /** 노선 위 정수 m 자리 — 0 ~ 종점(내림) 밖은 끝에 붙임. */ export function markChainage(rawM: number, totalM: number): number { return Math.min(Math.max(Math.round(rawM), 0), Math.max(0, Math.floor(totalM))); } /** 방향키 한 번 — 지금 자리(정수로) ± 1 m. */ export const markStep = (chainageM: number, sign: 1 | -1, totalM: number): number => markChainage(Math.round(chainageM) + sign, totalM); /** 방향키 부호 — `keyDir`(누른 화면 방향의 도면 벡터) · `tangent`(노선 앞쪽) 같은 쪽이면 +1. */ export function arrowSign(key: ArrowKey, keyDir: Vertex, tangent: Vertex): 1 | -1 { const dot = keyDir[0] * tangent[0] + keyDir[1] * tangent[1]; const size = Math.hypot(keyDir[0], keyDir[1]) * Math.hypot(tangent[0], tangent[1]); if (Math.abs(dot) > size * 0.2) return dot > 0 ? 1 : -1; return key === "ArrowRight" || key === "ArrowUp" ? 1 : -1; } /** 고른 마커 id — 고름 구조물 id 와 같은 것 · id 없으면 그 누가거리 `tolM` 안 가장 가까운 것 · 없으면 null. */ export function pickedMarkId( marks: ReadonlyArray<{ id: string; chainageM: number }>, selection: { chainageM: number | null; structureId: string | null } | null, tolM: number, ): string | null { if (!selection || selection.chainageM === null) return null; if (selection.structureId) return marks.some((mark) => mark.id === selection.structureId) ? selection.structureId : null; let best: string | null = null; let bestGap = tolM; for (const mark of marks) { const gap = Math.abs(mark.chainageM - selection.chainageM); if (gap < bestGap) [best, bestGap] = [mark.id, gap]; } return best; }