feat(측점열쇠): 346-2 덧 2 열쇠 한 벌 — 구간형 :start · :end · 묶음 측점 종류(grid · end) · 같은 열쇠 둘째 규칙 공용

- common_util_station_key(ts · py 짝): role 덧붙임(구간형 시작 · 끝 = structure:<id>:start · :end · 기준은 structure:<id> 그대로) · grid(원래 격자 번호) · end(BP/EP) 종류 · dedupeStationKeys(같은 열쇠 둘째부터 at:<누가거리>)
- B06 stationKeysOf 가 같은 규칙(측점 계획 · 구조물 목록의 시작 · 끝 자리 · 공용 dedupe)
- 거울 시험에 새 갈래(구간형 · grid · end · dedupe)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
This commit is contained in:
2026-10-16 12:20:06 +09:00
co-authored by Claude Opus 5.5
parent f9c70757b4
commit 69d84e5ca9
4 changed files with 119 additions and 28 deletions
+30 -4
View File
@@ -2,6 +2,8 @@
일반 = ``grid:k``(누가거리 ÷ 측점 간격 반올림) · 관 = ``pipe:<id>`` · 구조물 = ``structure:<id>`` ·
시종점 = ``end:BP`` · ``end:EP`` · id 를 모르는 구조물 측점 = ``at:<누가거리 0.01 m>``.
구간형 구조물 시작 · 끝 측점 = ``structure:<id>:start`` · ``:end``(기준 측점은 ``structure:<id>``).
한 목록에 같은 열쇠가 둘이면 둘째부터 ``at:<누가거리>``(``dedupe_station_keys``).
⚠ 파이썬·TS 짝 — ``common_util_station_key.ts`` ``stationKey`` ↔ ``station_key``.
한쪽을 고치면 다른 쪽도 고치고 거울 시험 ``resources/tester/test_344_3_station_key_mirror.py``.
@@ -31,19 +33,43 @@ def station_key(
interval_m: float | None,
station_id: str | None = None,
id_kind: str = "structure",
role: str = "anchor",
) -> str:
"""측점 열쇠 — irregular 는 id 가 있으면 pipe: · structure:(``id_kind``) · 없으면 at:."""
"""측점 열쇠 — irregular 는 id 가 있으면 pipe: · structure:(``id_kind``) · 없으면 at:.
구간형 시작 · 끝은 ``role`` 로 ``:start`` · ``:end``. ``grid`` · ``end`` 는 ``station_id`` 에
원래 격자 번호 · BP/EP(없으면 누가거리 ÷ 간격 반올림 · 0 이면 BP).
"""
loose = f"at:{chainage_key(chainage_m)}"
suffix = "" if role == "anchor" else f":{role}"
if kind == "bp":
return "end:BP"
if kind == "ep":
return "end:EP"
if kind == "end":
return (
f"end:{station_id if station_id is not None else ('BP' if chainage_m <= 0 else 'EP')}"
)
if kind in ("pipe", "structure"):
return f"{kind}:{station_id}" if station_id else f"at:{chainage_key(chainage_m)}"
return f"{kind}:{station_id}{suffix}" if station_id else loose
if kind == "irregular":
return f"{id_kind}:{station_id}" if station_id else f"at:{chainage_key(chainage_m)}"
return f"{id_kind}:{station_id}{suffix}" if station_id else loose
if kind == "grid" and station_id is not None:
return f"grid:{station_id}"
if interval_m and interval_m > 0:
return f"grid:{_half_up(chainage_m / interval_m)}"
return f"at:{chainage_key(chainage_m)}"
return loose
def dedupe_station_keys(keys: list[str], chainages: list[float]) -> list[str]:
"""한 목록에 같은 열쇠가 둘이면 둘째부터 ``at:<누가거리>`` — 열쇠마다 측점 하나."""
seen: set[str] = set()
out: list[str] = []
for key, chainage in zip(keys, chainages, strict=True):
unique = f"at:{chainage_key(chainage)}" if key in seen else key
seen.add(unique)
out.append(unique)
return out
def loose_chainage(key: str) -> float | None:
+33 -8
View File
@@ -3,21 +3,29 @@
* 측점 열쇠 한 규칙(PLAN 344-3) — 노선이 바뀌어도 같은 측점을 같은 이름으로 부름.
* 일반 = `grid:k`(누가거리 ÷ 측점 간격 반올림) · 관 = `pipe:<id>` · 구조물 = `structure:<id>` ·
* 시종점 = `end:BP` · `end:EP` · id 를 모르는 구조물 측점 = `at:<누가거리 0.01 m>`(옛 초안 열쇠와 같은 몫).
* 구간형 구조물의 시작 · 끝 측점 = `structure:<id>:start` · `:end`(기준 측점은 `structure:<id>` 그대로).
* 한 목록에 같은 열쇠가 둘이면 둘째부터 `at:<누가거리>`(`dedupeStationKeys`).
* 쓰는 곳 = 서버 `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) 그대로 받음. */
export type StationKeyKind = "bp" | "ep" | "regular" | "irregular" | "pipe" | "structure";
/** 측점 종류 — 서버 측점 `kind`(bp · ep · regular · irregular) · 묶음 측점 `kind`(grid · end · pipe · structure). */
export type StationKeyKind =
"bp" | "ep" | "regular" | "irregular" | "grid" | "end" | "pipe" | "structure";
/** 구간형 구조물 측점 자리 — 기준(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:`.
* 간격이 없거나 0 이하면 일반 측점도 `at:`.
* 측점 열쇠. irregular 는 id 가 있으면 `pipe:` · `structure:`(종류는 `idKind`) · 없으면 `at:` · 구간형 시작 · 끝은
* `role` 로 `:start` · `:end` 덧붙임. `grid` · `end` 는 `id` 에 원래 격자 번호 · `BP`/`EP` 를 줄 수 있음(없으면
* 누가거리 ÷ 간격 반올림 · 0 이면 BP). 간격이 없거나 0 이하면 일반 측점도 `at:`.
*/
export function stationKey(
kind: StationKeyKind,
@@ -25,14 +33,31 @@ export function stationKey(
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 === "pipe" || kind === "structure")
return id ? `${kind}:${id}` : `at:${chainageKey(chainageM)}`;
if (kind === "irregular") return id ? `${idKind}:${id}` : `at:${chainageKey(chainageM)}`;
if (kind === "end") return `end:${id ?? (chainageM <= 0 ? "BP" : "EP")}`;
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 `at:${chainageKey(chainageM)}`;
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. */