/* ============================================================================= * D03_PlanAnalysis_PointEdit.ts * D03 지점 칸 손 고침 계산(PLAN 597-2 · 요구 463 7 · 8) — 화면 · fetch 없음(logic/). * 이름 = 20 자 안 · 파일 이름에 못 쓰는 글자(`/ \ : * ? " < > |`) 막음 · 빈 값 = 「지점 N」 보임. * 좌표 = 경위도 손 고침 → 작업 좌표(TM) · 그 자리 노선 노드도 같이(`moveNodesAt`). * ⚠ 이름 한도 · 글자 = `Plan.ts` 의 `NAME_MAX` · `nameError`(594-1)와 같은 규칙 — 그쪽이 들어오면 거기서 가져옴. * ========================================================================== */ import { moveNodesAt, type PlanNetwork, type Vertex } from "./D03_PlanAnalysis_Plan"; import { lonLatToTm, type TmParams } from "./D03_PlanAnalysis_Tm"; /** 지점 상한 — 서버 `MAX_POINTS` 와 같음. */ export const MAX_POINTS = 40; export const POINT_NAME_MAX = 20; const BAD_CHARS = /[\\/:*?"<>|]/; /** 이름 까닭 글 — 쓸 수 있으면 null. */ export function pointNameError(name: string): string | null { if (name.length > POINT_NAME_MAX) return `이름은 ${POINT_NAME_MAX} 자까지`; if (BAD_CHARS.test(name)) return '이름에 / \\ : * ? " < > | 는 못 씁니다'; return null; } /** 보이는 이름 — 빈 값 = 「지점 N」. */ export function pointLabel(names: string[], index: number): string { return names[index]?.trim() || `지점 ${index + 1}`; } /** 경위도 글 → 값 · 못 읽거나 범위 밖이면 까닭 글. */ export function parseLonLat(lonText: string, latText: string): [number, number] | string { const lon = Number(lonText.trim()); const lat = Number(latText.trim()); if (!lonText.trim() || !Number.isFinite(lon) || Math.abs(lon) > 180) return "경도는 −180 ~ 180"; if (!latText.trim() || !Number.isFinite(lat) || Math.abs(lat) > 90) return "위도는 −90 ~ 90"; return [lon, lat]; } export interface PointsState { points: Vertex[]; names: string[]; network: PlanNetwork; } /** 지점 더함 — 이름 비움. 상한이면 그대로. */ export function addPoint(state: PointsState, at: Vertex): PointsState { if (state.points.length >= MAX_POINTS) return state; return { ...state, points: [...state.points, [at[0], at[1]]], names: [...state.names, ""], }; } /** 이름 바꿈 — 빈 값 허용. */ export function renamePoint(state: PointsState, index: number, name: string): PointsState { const names = state.points.map((_, k) => state.names[k] ?? ""); names[index] = name.trim(); return { ...state, names }; } /** 경위도로 옮김 — 그 자리 노선 노드도 같이. */ export function movePointTo( state: PointsState, index: number, tm: TmParams, lon: number, lat: number, ): PointsState { const to = lonLatToTm(tm, lon, lat); const from = state.points[index]; return { ...state, points: state.points.map((p, k): Vertex => (k === index ? to : p)), network: moveNodesAt(state.network, from, to), }; }