- 좌표 낡음 판정 — 좌표가 그 누가거리를 안 낳으면(마커 · 방향키 · 폼으로 누가거리만 옮김) 노선 위 자리로 다시 적음 · TS placeOnLine ↔ 파이썬 fill_structure_coordinates 짝(보낸 좌표가 누가거리를 낳으면 살림) - 따라감 단계 — 좌표 없는 관 초안은 앞 따라감 좌표 · site 간격 넘김 - 배수 편집기 — 노선 초안 follow 가 다시 매긴 관 자리를 id 짝으로 받음(시설 속성 · 고름 함께) · 초안 버림 = 저장분 다시 읽음 - 관 짝 차례 → id — 관 알림에 고정 id · followPipeReports · shownPipes(movePipeIds 한 번에 옮김) · followedMarks 같은 자리 먼저 - B06 측점 열쇠 — 서버 관 id 도 pipe: (계산 묶음과 같은 갈래) - 시험 test_410_follow_xy · test_411_b_pipe_id_pairing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
105 lines
5.2 KiB
TypeScript
105 lines
5.2 KiB
TypeScript
/* =============================================================================
|
|
* A00_Common/design/design_pipe_ids.ts
|
|
* 관 세션 id(323-5) — 관을 처음 볼 때 자리마다 붙이는 id · 옮겨도 그대로 · 저장 안 함(파일 모양 그대로).
|
|
*
|
|
* id = `pipe-s` + 차례(`pipe-s3`) — 머리 `pipe-` 로 관임을 가름(옛 자리 이름 `pipe-{누가거리}` 와 같은 머리) ·
|
|
* 누가거리는 id 에서 읽지 않음(`pipeChainageOf`). 장부 = 세션 `pipe-ids`(고름 `structure-pick` 과 같은 수명 ·
|
|
* 초안 비우기에 함께 비움). 자리 → id = 찾는 거리(`PIPE_MATCH_M`) 안 가장 가까운 것 · 없으면 새 차례 ·
|
|
* 옮김 = `movePipeId`(고름 따라가기 `followMovedPipe` 가 부름).
|
|
* 프로젝트 = 보기가 열 때 정함(`usePipeIds`) — 그리는 함수(카드 · 알약)가 프로젝트를 몰라도 같은 id.
|
|
* 프로젝트가 없으면(시험 · 도움 파일) 옛 자리 이름.
|
|
* ========================================================================== */
|
|
|
|
import { readState, writeState } from "../b_page_state";
|
|
import { isNear, nearestWithin, readSelection, selectDesign } from "./design_selection";
|
|
|
|
/** 관 찾는 거리 — 정의는 `design_selection` 한 곳(배수 패널이 관 옮김과 함께 가져감). */
|
|
export { PIPE_MATCH_M } from "./design_selection";
|
|
|
|
interface Book {
|
|
next: number;
|
|
ids: Array<{ id: string; at: number }>;
|
|
}
|
|
|
|
let active: string | null = null;
|
|
|
|
/** 지금 프로젝트 — 종단 · 횡단 보기가 열 때. */
|
|
export function usePipeIds(projectId: string | null): void {
|
|
active = projectId;
|
|
}
|
|
|
|
const bookOf = (projectId: string): Book =>
|
|
readState<Book>("pipe-ids", projectId) ?? { next: 1, ids: [] };
|
|
|
|
/** 그 자리 관의 id — 장부에 없으면 새 차례로 적음. */
|
|
export function pipeIdAt(chainageM: number, projectId: string | null = active): string {
|
|
if (!projectId) return `pipe-${chainageM.toFixed(2)}`;
|
|
const book = bookOf(projectId);
|
|
const hit = nearestWithin(book.ids, chainageM, (entry) => entry.at);
|
|
if (hit) return hit.id;
|
|
const id = `pipe-s${book.next}`;
|
|
writeState(
|
|
"pipe-ids",
|
|
{ next: book.next + 1, ids: [...book.ids, { id, at: chainageM }] },
|
|
projectId,
|
|
);
|
|
return id;
|
|
}
|
|
|
|
/** id 의 관 자리 — 장부 · 옛 자리 이름 순 · 관 id 가 아니면 null. */
|
|
export function pipeChainageOf(id: string, projectId: string | null = active): number | null {
|
|
if (!id.startsWith("pipe-")) return null;
|
|
const hit = projectId ? bookOf(projectId).ids.find((entry) => entry.id === id) : undefined;
|
|
if (hit) return hit.at;
|
|
const legacy = Number(id.slice("pipe-".length));
|
|
return Number.isFinite(legacy) ? legacy : null;
|
|
}
|
|
|
|
/** 관을 옮김 — 옛 자리 id 가 새 자리로(새 자리에 남은 옛 기록은 뺌) · 옮긴 관의 id. */
|
|
export function movePipeId(fromM: number, toM: number, projectId: string | null = active): string {
|
|
if (!projectId) return `pipe-${toM.toFixed(2)}`;
|
|
return movePipeIds([[fromM, toM]], projectId)[0] ?? pipeIdAt(toM, projectId);
|
|
}
|
|
|
|
/** 관 여럿을 한 번에 옮김(노선 따라감 · 411-B) — 옮길 기록을 먼저 다 찾고 옮김(앞 관이 뒤 관 옛 자리로 가도
|
|
* 뒤 관 기록을 안 지움) · 새 자리에 남은 옮기지 않는 옛 기록은 뺌 · 옮긴 관마다 id(못 찾으면 null). */
|
|
export function movePipeIds(
|
|
moves: ReadonlyArray<readonly [number, number]>,
|
|
projectId: string | null = active,
|
|
): Array<string | null> {
|
|
if (!projectId) return moves.map(() => null);
|
|
const book = bookOf(projectId);
|
|
const moving = moves.map(([fromM]) => nearestWithin(book.ids, fromM, (entry) => entry.at));
|
|
const to = new Map(moving.flatMap((entry, i) => (entry ? [[entry, moves[i][1]]] : [])));
|
|
const still = book.ids.filter((entry) => !to.has(entry));
|
|
const stale = new Set([...to.values()].map((at) => nearestWithin(still, at, (e) => e.at)));
|
|
const ids = book.ids
|
|
.filter((entry) => !stale.has(entry))
|
|
.map((entry) => (to.has(entry) ? { ...entry, at: to.get(entry)! } : entry));
|
|
if (to.size) writeState("pipe-ids", { next: book.next, ids }, projectId);
|
|
return moving.map((entry) => entry?.id ?? null);
|
|
}
|
|
|
|
/** 관 목록에 세션 id 를 얹은 사본 — 측점 계획의 덮는 구조물 id(종단 측점 id · 알약 짝)가 세션 id. */
|
|
export function withPipeIds<T extends { chainage_m: number }>(
|
|
pipes: ReadonlyArray<T>,
|
|
projectId: string | null = active,
|
|
): Array<T & { id: string }> {
|
|
return pipes.map((pipe) => ({ ...pipe, id: pipeIdAt(pipe.chainage_m, projectId) }));
|
|
}
|
|
|
|
/** 관을 옮김 — 관 세션 id 가 새 자리로(323-5 · id 그대로) · 그 관을 골라 둔 고름이면 자리만 새로(부재키
|
|
* 그대로 · 옛 자리 이름 id 면 세션 id 로). 다른 것을 골랐으면 그대로. */
|
|
export function followMovedPipe(projectId: string | null, fromM: number, toM: number): void {
|
|
const id = movePipeId(fromM, toM, projectId);
|
|
const picked = readSelection(projectId);
|
|
if (!picked || picked.chainageM === null || !isNear(picked.chainageM, fromM)) return;
|
|
const sid = picked.structureId;
|
|
if (sid !== null && !sid.startsWith("pipe-")) return;
|
|
selectDesign(
|
|
projectId,
|
|
{ chainageM: toM, structureId: sid === null ? null : id },
|
|
{ partKey: picked.partKey },
|
|
);
|
|
}
|