- 자리 칸 목록 한 벌 = TS `SEAT_KEYS`(Station_Swap · 파이썬 Router_Design 이 그 글을 읽음) — two_stage_slope · rock_boundary_offset_m · cut_slope_ratio_user · section_mode · ditch_side - 자리 판정 한 벌 = `sameSeat` ↔ 파이썬 `same_seat`(중심 0.01 m · 왼쪽 축 1e-3 · 모르면 같은 자리 = 옛 동작) - 끌기 세션 넘김 · Node stations.carry = `carryKeyedStates`(목록 측점 자리 · 값마다 쓴 때 자리 `@seat` — 되돌리기로 되살린 값도 제 자리로) - 미리보기 = `pipelineCrossSource` 가 저장분 · 세션 자리 칸에 자리째 실음 → `crossAtSeat` 가 새 자리면 뺌 - B06 갈아끼움(노선 초안) = 자리 바뀐 측점 설계의 자리 칸 걷고 기본값(`unseated` · 브레인 덧) - 서버 노선 저장 = `carry_cross_inputs` 자리 칸 같은 자리만 · 손 단면 유형 · 측구 방향 = `compute_default_designs` 입력 - 옛 단언 시험 고침(자리 모름 목록 = 같은 자리 = 손 상단측도 넘김 · 「@seat」 칸 거름) · 새 시험 test_475_3_seat_carry - 위키 rules 넘김 규칙 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
127 lines
6.1 KiB
TypeScript
127 lines
6.1 KiB
TypeScript
/* =============================================================================
|
|
* common_util_station_key.ts
|
|
* 측점 열쇠 한 규칙(PLAN 344-3) — 노선이 바뀌어도 같은 측점을 같은 이름으로 부름.
|
|
* 일반 = `grid:k`(누가거리 ÷ 측점 간격 반올림) · 관 = `pipe:<id>` · 구조물 = `structure:<id>` ·
|
|
* 시종점 = `end:BP` · `end:EP` · id 를 모르는 구조물 측점 = `at:<누가거리 0.01 m>`(옛 초안 열쇠와 같은 몫).
|
|
* 곡선 측점(MC · 252-2) = `curve:<누가거리 0.01 m>`.
|
|
* 구간형 구조물의 시작 · 끝 측점 = `structure:<id>:start` · `:end`(기준 측점은 `structure:<id>` 그대로).
|
|
* 한 목록에 같은 열쇠가 둘이면 둘째부터 `at:<누가거리>`(`dedupeStationKeys`).
|
|
* MC 가 움직여 곡선 열쇠가 바뀌면 넘길 때 가까운 새 곡선 측점으로 이음(`curveMoves` · 432-3).
|
|
* 쓰는 곳 = 서버 `B05_Profile_Route_Save_Parts` · 묶음 stations 단계 · B06 세션 횡단 초안(`stationKeysOf`).
|
|
*
|
|
* ⚠ 파이썬·TS 짝 — `common_util_station_key.py` `station_key` ↔ `stationKey`. 한쪽을 고치면 다른 쪽도
|
|
* 고치고 거울 시험 `resources/tester/test_344_3_station_key_mirror.py`.
|
|
* ========================================================================== */
|
|
|
|
/** 측점 종류 — 서버 측점 `kind`(bp · ep · regular · irregular · curve) · 묶음 측점 `kind`(grid · end · pipe · structure · curve). */
|
|
export type StationKeyKind =
|
|
"bp" | "ep" | "regular" | "irregular" | "grid" | "end" | "pipe" | "structure" | "curve";
|
|
|
|
/** 구간형 구조물 측점 자리 — 기준(anchor) · 시작 · 끝. */
|
|
export type StationKeyRole = "anchor" | "start" | "end";
|
|
|
|
/** 누가거리 0.01 m — 옛 초안 열쇠(`toFixed(2)`)와 같은 자릿수. */
|
|
export const chainageKey = (chainageM: number): string =>
|
|
(Math.round(chainageM * 100) / 100).toFixed(2);
|
|
|
|
/**
|
|
* 측점 열쇠. irregular 는 id 가 있으면 `pipe:` · `structure:`(종류는 `idKind`) · 없으면 `at:` · 구간형 시작 · 끝은
|
|
* `role` 로 `:start` · `:end` 덧붙임. `grid` · `end` 는 `id` 에 원래 격자 번호 · `BP`/`EP` 를 줄 수 있음(없으면
|
|
* 누가거리 ÷ 간격 반올림 · 0 이면 BP). 간격이 없거나 0 이하면 일반 측점도 `at:`.
|
|
*/
|
|
export function stationKey(
|
|
kind: StationKeyKind,
|
|
chainageM: number,
|
|
intervalM: number | null,
|
|
id: string | null = null,
|
|
idKind: "pipe" | "structure" = "structure",
|
|
role: StationKeyRole = "anchor",
|
|
): string {
|
|
const loose = `at:${chainageKey(chainageM)}`;
|
|
const suffix = role === "anchor" ? "" : `:${role}`;
|
|
if (kind === "bp") return "end:BP";
|
|
if (kind === "ep") return "end:EP";
|
|
if (kind === "end") return `end:${id ?? (chainageM <= 0 ? "BP" : "EP")}`;
|
|
if (kind === "curve") return `curve:${chainageKey(chainageM)}`;
|
|
if (kind === "pipe" || kind === "structure") return id ? `${kind}:${id}${suffix}` : loose;
|
|
if (kind === "irregular") return id ? `${idKind}:${id}${suffix}` : loose;
|
|
if (kind === "grid" && id !== null) return `grid:${id}`;
|
|
if (intervalM && intervalM > 0) return `grid:${Math.round(chainageM / intervalM)}`;
|
|
return loose;
|
|
}
|
|
|
|
/** 한 목록에 같은 열쇠가 둘이면 둘째부터 `at:<누가거리>` — 열쇠마다 측점 하나. */
|
|
export function dedupeStationKeys(
|
|
keys: ReadonlyArray<string>,
|
|
chainages: ReadonlyArray<number>,
|
|
): string[] {
|
|
const seen = new Set<string>();
|
|
return keys.map((key, index) => {
|
|
const unique = seen.has(key) ? `at:${chainageKey(chainages[index])}` : key;
|
|
seen.add(unique);
|
|
return unique;
|
|
});
|
|
}
|
|
|
|
/** 옛 초안 열쇠(누가거리 글 `"85.05"`) · `at:` 열쇠의 누가거리 — 측점 열쇠면 null. */
|
|
export function looseChainage(key: string): number | null {
|
|
const text = key.startsWith("at:") ? key.slice(3) : key;
|
|
if (!/^-?\d+(\.\d+)?$/.test(text)) return null;
|
|
return Number(text);
|
|
}
|
|
|
|
/** 옮긴 곡선 측점으로 보는 거리(m) — 옛 MC 자리에서 이 안의 새 MC. */
|
|
export const CURVE_NEAR_M = 10;
|
|
|
|
/**
|
|
* MC 가 움직여 `curve:` 열쇠가 바뀜 — 옛 목록에만 있는 곡선 열쇠 → 새 목록에만 있는 가장 가까운 곡선 열쇠
|
|
* (`CURVE_NEAR_M` 안 · 새 열쇠 하나에 옛 열쇠 하나 · 옛 누가거리 차례). 목록 = [열쇠, 누가거리].
|
|
*/
|
|
export function curveMoves(
|
|
before: ReadonlyArray<readonly [string, number]>,
|
|
after: ReadonlyArray<readonly [string, number]>,
|
|
): Map<string, string> {
|
|
const isCurve = (key: string) => key.startsWith("curve:");
|
|
const was = new Set(before.map(([key]) => key));
|
|
const kept = new Set(after.map(([key]) => key));
|
|
const free = after.filter(([key]) => isCurve(key) && !was.has(key));
|
|
const out = new Map<string, string>();
|
|
for (const [key, at] of [...before].sort((a, b) => a[1] - b[1])) {
|
|
if (!isCurve(key) || kept.has(key)) continue;
|
|
let best = -1;
|
|
free.forEach(([, to], i) => {
|
|
const gap = Math.abs(to - at);
|
|
if (gap <= CURVE_NEAR_M && (best < 0 || gap < Math.abs(free[best][1] - at))) best = i;
|
|
});
|
|
if (best >= 0) out.set(key, free.splice(best, 1)[0][0]);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** 측점 자리 — 중심 좌표 · 왼쪽 단위 벡터(횡단 축 · 없으면 좌표만 봄). */
|
|
export interface StationSeat {
|
|
x: number;
|
|
y: number;
|
|
left?: readonly [number, number] | null;
|
|
}
|
|
/** 같은 자리로 보는 중심 차(m) · 축 차 — 브라우저 틀 ↔ 서버 틀 차(467)를 넘김. */
|
|
export const SEAT_XY_M = 0.01;
|
|
export const SEAT_AXIS = 1e-3;
|
|
|
|
/**
|
|
* 노선이 바뀐 뒤 그 측점이 같은 자리인가(요구 275-3) — 자리 · 축이 같으면 지형이 같아 「자리 칸」(암 경계 · 2단
|
|
* 경사 · 손 상단측)을 들고 감 · 한쪽 자리를 모르면 같은 자리로 봄(옛 목록 · 옛 동작) · 짝 `same_seat` ·
|
|
* 거울 시험 `test_475_3_seat_carry.py`.
|
|
*/
|
|
export function sameSeat(
|
|
a: StationSeat | null | undefined,
|
|
b: StationSeat | null | undefined,
|
|
): boolean {
|
|
if (!a || !b) return true;
|
|
if (Math.hypot(a.x - b.x, a.y - b.y) > SEAT_XY_M) return false;
|
|
if (!a.left || !b.left) return true;
|
|
return (
|
|
Math.abs(a.left[0] - b.left[0]) <= SEAT_AXIS && Math.abs(a.left[1] - b.left[1]) <= SEAT_AXIS
|
|
);
|
|
}
|