/* ============================================================================= * 계획선 1m 재표본 (B04 2D 지도 공용) * * 백엔드는 도로 위 값을 전부 **누가거리 1m 구간**으로 준다(흐름 강도 곡선, 관 매설 지점, * 유입 집중점). 화면에서 그 값을 계획선 위에 얹으려면 같은 규칙으로 다시 찍은 점 목록이 * 있어야 한다 — 오버레이마다 따로 찍으면 한 칸씩 밀려 색과 마커가 어긋난다. * ========================================================================== */ export type RoutePoint = { x: number; y: number }; /** 계획선을 1m 간격으로 다시 찍는다. 배열 인덱스 = 누가거리(m). */ export function resampleRoute(points: ReadonlyArray): RoutePoint[] { const samples: RoutePoint[] = []; if (points.length < 2) return samples; let carried = 0; samples.push({ x: points[0].x, y: points[0].y }); for (let i = 1; i < points.length; i += 1) { const from = points[i - 1]; const to = points[i]; const dx = to.x - from.x; const dy = to.y - from.y; const length = Math.hypot(dx, dy); if (length <= 0) continue; let travelled = 1 - carried; while (travelled <= length) { samples.push({ x: from.x + (dx * travelled) / length, y: from.y + (dy * travelled) / length, }); travelled += 1; } carried = (carried + length) % 1; } return samples; } /** 누가거리(m) 위치의 계획선 좌표. 범위를 벗어나면 양 끝으로 자른다. */ export function pointAtChainage( samples: ReadonlyArray, chainage: number, ): RoutePoint | null { if (samples.length === 0) return null; const index = Math.min(samples.length - 1, Math.max(0, Math.round(chainage))); return samples[index]; } /** 화면 좌표에서 가장 가까운 계획선 위치를 찾는다. (누가거리 m, 화면 거리 px). * 관을 우클릭으로 추가하거나 끌어 옮길 때 "계획선 위"로 스냅하는 근거다. 1m 표본을 전부 * 훑되 화면 변환은 넘겨받은 함수에 맡긴다 — 배경지도 메타를 여기서 알 필요가 없다. */ export function nearestChainage( samples: ReadonlyArray, toScreen: (point: RoutePoint) => [number, number], screenX: number, screenY: number, ): { chainage: number; distance: number } | null { if (samples.length === 0) return null; let bestIndex = -1; let bestDistance = Number.POSITIVE_INFINITY; for (let index = 0; index < samples.length; index += 1) { const [x, y] = toScreen(samples[index]); const distance = Math.hypot(x - screenX, y - screenY); if (distance < bestDistance) { bestDistance = distance; bestIndex = index; } } return bestIndex < 0 ? null : { chainage: bestIndex, distance: bestDistance }; }