refactor(logic): 348-1 순수 계산 TS 49 파일 <페이지>/logic/ 로 옮김 — 이름 앞머리 그대로 · 동작 고침 없음
- 옮김: B04 2 · B05 23 · B06 11 · A00_Common/design 13 (9,299 줄) · 이미 logic/ 에 있던 16 파일 그대로 - 안 옮김: 묶음 입구(package.json build:*) · 파이썬 · 순수지만 계산 아닌 8 (보기 결선 · 콜백 · API 타입 · 갈아끼움 알림판) - import 266 줄(142 파일) · 시험 · 주석 · 짝 표시 문자열 경로 고침 · B05 · B06 CLAUDE.md 짝 줄 logic/ - 시험 도우미 새 경로: 가짜 require 열쇠 · B05()/B06() 부름 · 형제 import 자리 · tsc 묶어 돌리는 시험 · 정규식 · rglob - design_view_cross 줄 한도 시험 685 → 688 (긴 import 경로 줄바꿈 3줄) - 확인: typecheck 0 · 서버 묶음 넷(corridor · server-calc · b07-extent · b07-note) 다시 지어 코드 같음(//#region 경로 주석만 다름) · 관련 시험 270 파일 — 남은 실패 19 는 옮기기 전과 같음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/* =============================================================================
|
||||
* 계획선 1m 재표본 (B04 2D 지도 공용)
|
||||
*
|
||||
* 백엔드는 도로 위 값을 전부 **누가거리 1m 구간**으로 준다(흐름 강도 곡선, 관 매설 지점,
|
||||
* 유입 집중점). 화면에서 그 값을 계획선 위에 얹으려면 같은 규칙으로 다시 찍은 점 목록이
|
||||
* 있어야 한다 — 오버레이마다 따로 찍으면 한 칸씩 밀려 색과 마커가 어긋난다.
|
||||
* ========================================================================== */
|
||||
|
||||
export type RoutePoint = { x: number; y: number };
|
||||
|
||||
/** 계획선을 1m 간격으로 다시 찍는다. 배열 인덱스 = 누가거리(m). */
|
||||
export function resampleRoute(points: ReadonlyArray<RoutePoint>): 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<RoutePoint>,
|
||||
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<RoutePoint>,
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user