/* ============================================================================= * B05_Profile_UI_RouteEdit_Lock.ts * 계획노선 편집 창의 **고정 한 벌** — 시점 · 종점과 교각점 자리(계획서 22-4 · 22-5). * * 모달은 노드마다 한 칸씩 `apexLock` 을 쥔다 — 0번 = 시점, 끝 칸 = 종점, 사이 = 교각점. * 시점 · 종점은 **기본 고정**이다. 켜진 자리는 고르기만 되고 안 끌린다(`_Pointer`). * * 창을 닫아도([취소] · [확인] 포함) 고정은 남는다 — 화면 상태 보관소의 pref 통(프로젝트 칸)에 * 바로 적고, 다시 열 때 서버 노드에 **좌표로** 다시 짚는다(노드 번호는 편집마다 밀린다). * draft 통에 두지 않는다 — [저장] 대상이 아닌데 미저장 표시가 켜지고 [저장] 뒤 지워진다. * 초기노선 계산(POST /route/initial)에 넘길 때도 `readRouteLocks` 한 곳만 읽는다. * DOM 을 안 만진다 — 시험이 Node 로 돈다. * ========================================================================== */ import { readState, writeState } from "../A00_Common/b_page_state"; type Vertex = [number, number]; /** 고정 한 벌. `apex` 는 고정한 교각점 좌표(사업지 좌표계 m). */ export interface RouteLocks { start: boolean; end: boolean; apex: Vertex[]; } /** 같은 교각점으로 볼 거리(m) — 서버가 되돌려 준 좌표의 끝자리 어긋남만 흡수한다. */ const SAME_POINT_M = 0.05; const isVertex = (value: unknown): value is Vertex => Array.isArray(value) && value.length >= 2 && value.every((n) => Number.isFinite(n)); /** 저장된 고정 — 없거나 깨졌으면 시점 · 종점만 고정. */ export function readRouteLocks(projectId: string): RouteLocks { const saved = readState>("routeedit-locks", projectId); return { start: saved?.start !== false, end: saved?.end !== false, apex: Array.isArray(saved?.apex) ? saved.apex.filter(isVertex) : [], }; } /** 노드 칸 → 고정 한 벌. */ export function routeLocksOf( planned: ReadonlyArray, locked: ReadonlyArray, ): RouteLocks { const last = planned.length - 1; return { start: locked[0] === true, end: locked[last] === true, apex: planned .filter((_, index) => index > 0 && index < last && locked[index] === true) .map(([x, y]) => [x, y]), }; } export function writeRouteLocks( projectId: string, planned: ReadonlyArray, locked: ReadonlyArray, ): void { if (planned.length < 2) return; // 노선을 못 읽은 채 닫으면 적어 둔 고정을 지우지 않는다. writeState("routeedit-locks", routeLocksOf(planned, locked), projectId); } /** 고정 한 벌 → 노드 칸. 교각점은 좌표가 가까운 자리만 켠다. */ export function lockColumn(planned: ReadonlyArray, locks: RouteLocks): boolean[] { const last = planned.length - 1; return planned.map(([x, y], index) => index === 0 ? locks.start : index === last ? locks.end : locks.apex.some(([ax, ay]) => Math.hypot(ax - x, ay - y) <= SAME_POINT_M), ); } /** 곧은 선(노드 `index` → `index + 1`)은 **양 끝이 모두** 고정일 때 고정. */ export const straightLocked = (locked: ReadonlyArray, index: number): boolean => locked[index] === true && locked[index + 1] === true; /** 호(교각점 `index`)는 **그 교각점과 앞뒤 점이 모두** 고정일 때 고정. */ export const arcLocked = (locked: ReadonlyArray, index: number): boolean => locked[index - 1] === true && locked[index] === true && locked[index + 1] === true;