- 그림 신원표 = 새 common_util_pick_tag(tagShape · pipeTagId) — 관 id 는 화면이 열 때(usePipeIds) 세션 장부를 꽂음 · 안 꽂으면 옛 자리 이름(장부가 프로젝트 모를 때와 같은 값) - 구조물 그림 다섯(Box · Culvert · Ford · Ford_Pavement · Revetment)이 design_pipe_ids · design_pick_match 대신 이것을 씀 - Culvert_Wire 한계 토스트 = 알림 창구(setCulvertNotice) — 화면 Cross_View 가 토스트를 꽂음 · Wire 가 ui_template · Section_Common 안 끔 - 472-3 알려진 끌림 다섯 걷음(b_page_state · pick_match · pipe_ids · selection · Section_Common) · 남은 줄 = 헬퍼 정규식 헛잡음 - 헬퍼 286_1 흉내 → 진짜 pick_tag · 폴더 지침 끌림 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
6.5 KiB
TypeScript
132 lines
6.5 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 { setPipeTagSource } from "../../common_util/common_util_pick_tag";
|
|
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;
|
|
// 그림 신원표(common_util_pick_tag)가 이 장부를 씀 — 프로젝트 없으면 옛 자리 이름(pipeIdAt 와 같음)
|
|
setPipeTagSource(projectId ? (chainageM) => pipeIdAt(chainageM) : null);
|
|
}
|
|
|
|
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 — 장부에 있을 때만(안 지음 · 411-E). */
|
|
export function pipeIdNear(chainageM: number, projectId: string | null = active): string | null {
|
|
if (!projectId) return null;
|
|
return nearestWithin(bookOf(projectId).ids, chainageM, (entry) => entry.at)?.id ?? null;
|
|
}
|
|
|
|
/** 지금 관 자리(`keep`) 근처에 없는 기록을 뺌 — 옮긴 사이 옛 측점이 지은 헛 id 정리(411-E). */
|
|
export function prunePipeIds(keep: ReadonlyArray<number>, projectId: string | null = active): void {
|
|
if (!projectId || !keep.length) return; // 빈 목록 = 아직 못 받음 · 다 지움 — 장부 그대로
|
|
const book = bookOf(projectId);
|
|
const ids = book.ids.filter((entry) => keep.some((at) => isNear(entry.at, at)));
|
|
if (ids.length !== book.ids.length) writeState("pipe-ids", { next: book.next, ids }, projectId);
|
|
}
|
|
|
|
/** 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)}`;
|
|
const book = bookOf(projectId);
|
|
const moving = nearestWithin(book.ids, fromM, (entry) => entry.at);
|
|
if (!moving) return pipeIdAt(toM, projectId);
|
|
const stale = nearestWithin(
|
|
book.ids.filter((entry) => entry !== moving),
|
|
toM,
|
|
(entry) => entry.at,
|
|
);
|
|
const ids = book.ids
|
|
.filter((entry) => entry !== stale)
|
|
.map((entry) => (entry === moving ? { ...entry, at: toM } : entry));
|
|
writeState("pipe-ids", { next: book.next, ids }, projectId);
|
|
return moving.id;
|
|
}
|
|
|
|
/** 관 세션 id 를 그 자리로(화면 목록 맞춤 · 411-B) — 짝 = 관 고정 id 가 쥔 세션 id(노선 따라감 · 끌기 · 되돌리기
|
|
* 어느 길로 옮겨도 그 id) · 그 자리 근처의 다른 기록(옛 측점 열쇠가 지은 헛 id)은 뺌 · 장부에 없는 id 는 적음. */
|
|
export function placePipeIds(
|
|
places: ReadonlyArray<readonly [string, number]>,
|
|
projectId: string | null = active,
|
|
): void {
|
|
if (!projectId || !places.length) return;
|
|
const book = bookOf(projectId);
|
|
const to = new Map(places);
|
|
const ids = book.ids
|
|
.filter((entry) => to.has(entry.id) || !places.some(([, at]) => isNear(entry.at, at)))
|
|
.map((entry) => (to.has(entry.id) ? { ...entry, at: to.get(entry.id)! } : entry));
|
|
for (const [id, at] of places) if (!ids.some((entry) => entry.id === id)) ids.push({ id, at });
|
|
if (JSON.stringify(ids) !== JSON.stringify(book.ids))
|
|
writeState("pipe-ids", { next: book.next, ids }, projectId);
|
|
}
|
|
|
|
/** 관 목록에 세션 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 },
|
|
);
|
|
}
|