77 lines
3.0 KiB
TypeScript
77 lines
3.0 KiB
TypeScript
/* =============================================================================
|
||
* 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),
|
||
};
|
||
}
|