/* ============================================================================= * common_util/common_util_structure_station.ts * 구조물 · 관 기준 측점 = 정수 m · 일반 측점 1 m 안이면 그 측점에 붙음(234-1). * * 파이썬 짝 = `common_util/common_util_structure_station.py`(서버 자동 배치 · 노선 재투영). * 거울 시험 = `resources/tester/test_234_1_structure_station.py`. * 일반 측점 = 격자 · 시종점(구조물 측점 kind "irregular" 은 뺌) — 상세를 읽을 때 한 번 둔다. * ========================================================================== */ /** 일반 측점에 붙는 폭(m) — 이 안이면 그 측점 값(끝 측점처럼 정수가 아니어도). */ export const GENERAL_SNAP_M = 1; const EPS = 1e-6; let general: number[] = []; /** 일반 측점 목록을 둔다 — 횡단 상세를 읽을 때마다(B05 · B06 한 노선). */ export function setGeneralStations( stations: ReadonlyArray<{ chainage_m: number; kind?: string }> | null | undefined, ): void { general = (stations ?? []) .filter((station) => station.kind !== "irregular") .map((station) => station.chainage_m); } export const generalStations = (): readonly number[] => general; function nearGeneral(value: number, stations: readonly number[]): number | null { let best: number | null = null; for (const station of stations) { const gap = Math.abs(station - value); if (gap < GENERAL_SNAP_M - EPS && (best === null || gap < Math.abs(best - value))) best = station; } return best; } /** 기준 측점 맞춤 — 일반 측점 1 m 안이면 그 측점 · 아니면 정수 m(.5 는 올림) · 정수가 일반 측점 * 1 m 안이면 그 측점(끝 119.8 옆 119 → 119.8). 두 번 맞춰도 같다. */ export function snapStructureStation( chainageM: number, stations: readonly number[] = general, ): number { if (!Number.isFinite(chainageM)) return chainageM; const direct = nearGeneral(chainageM, stations); if (direct !== null) return direct; const whole = Math.floor(chainageM + 0.5); return nearGeneral(whole, stations) ?? whole; } /** 방향키 한 걸음(1 m) — 붙기가 제자리로 되돌리면 한 걸음 더(끝 119.8 → 118). 노선 밖이면 제자리. */ export function stepStructureStation( fromM: number, direction: 1 | -1, stations: readonly number[] = general, ): number { const end = stations.length ? Math.max(...stations) : Infinity; for (let steps = 1; steps <= 3; steps += 1) { const next = snapStructureStation(fromM + direction * steps, stations); if (next < 0 || next > end + EPS) return fromM; if ((next - fromM) * direction > EPS) return next; } return fromM; } /** 이미 맞춘 값인가(정수 맞춤 단추가 옮길 것을 가름). */ export const isSnappedStation = ( chainageM: number, stations: readonly number[] = general, ): boolean => Math.abs(snapStructureStation(chainageM, stations) - chainageM) < EPS;