Files
Aislo/B05_Profile/B05_Profile_UI_Structures_Snap.ts
T
eomsangdonandClaude Opus 5.5 3d8c161efa fix(구조물표): 322-3 고침 — B06 을 바로 열어도 옛 소수 관 자리 = 정수 m 초안(B05 snapOldStations 와 같은 셈 · 요구 167) · 계획고 편집 · 고름 따라감 · 알림 한 줄 같은 글 · 투영 이월 끝 = 선 길이(관이 전부 0 m 로 가던 흠) · design_view_cross 685줄 안(322-3 넘침)
- B06 구조물 목록 load: 관 초안 없고 저장분에 소수 자리 = `snapPlan` 으로 옮긴 초안(persistPipes) → 카드 · 측점 계획이 B05 를 먼저 연 것과 같음 · 정본은 [저장]에서(B08 사본 이름도 PUT 길)
- 서버 관 읽기는 그대로(파일 = 측점) · `project_pipe_points` 끝 = 선 길이
- 시험: test_322_3_flow 견본(85.05 … 982.64) → 85 · 173 · 258 · 352 · 440 · 534 · 620 · 720 · 804 · 900 · 983 · 정수 저장분 = 새 미리 뜬 카드 0 · 소수 저장분 = 정수 자리로 · test_322_3_saved_snap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-14 17:02:22 +09:00

141 lines
6.6 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_Structures_Snap.ts
* 구조물 기준 측점 정수 맞춤(234-1 · 272) — 끌기 · 방향키로 옮기는 구조물 한 건의 모양과
* 옛 소수 자료를 정수 m 로 옮기는 자동 맞춤(종단 보기를 열 때 한 번 · 단추 없음 · 요구 167).
*
* 맞춤 규칙은 공용 `common_util_structure_station`(파이썬 짝) 한 벌. 구조물은 한 번에, 관은 하나씩
* 폼 수정과 같은 길(`onPipeUpdate`)로 옮긴다 — 계획고 편집 따라가기(213-2) · 시설 속성 이어받기가
* 손으로 옮길 때와 같다. 정본은 [저장]에서 나간다(알림 한 줄).
* ========================================================================== */
import { showToast } from "@ui/ui_template_elements";
import {
isSnappedStation,
snapStructureStation,
} from "../common_util/common_util_structure_station";
import { PIPE_MATCH_M } from "../A00_Common/design/design_selection";
export { snapStructureStation };
import { structureAnchorM, type StructureInstance } from "./B05_Profile_Api_Structures";
import type { FacilityAttributes } from "./B05_Profile_UI_Drainage_Facility";
import type { PipeFacilityItem } from "./B05_Profile_UI_Structures_Panel_Types";
/** 구조물을 그 측점으로 옮긴 모양 — 구간은 기준과의 거리 · 길이를 지키되 시작 · 끝도 맞춤. */
export function movedStructure(target: StructureInstance, toChainageM: number): StructureInstance {
const anchor = snapStructureStation(toChainageM);
if (target.placement !== "interval") return { ...target, chainage_m: anchor };
const length = (target.end_m ?? 0) - (target.start_m ?? 0);
const start = snapStructureStation(anchor - (structureAnchorM(target) - (target.start_m ?? 0)));
const end = Math.max(snapStructureStation(start + length), start + 1); // 끝도 정수 · 1 m 이상(167)
return {
...target,
chainage_m: Math.min(Math.max(anchor, start), end),
start_m: start,
end_m: end,
};
}
/** 사라진 측점의 계획고 편집이 따라갈 새 측점 — 맞춘 자리(0.006m 안 · 213-2 와 같은 폭)의 것. */
export function followSnapped<T extends { chainage_m: number }>(
station: T,
born: ReadonlyArray<T>,
): T | null {
const to = snapStructureStation(station.chainage_m);
return born.find((next) => Math.abs(next.chainage_m - to) < 0.006) ?? null;
}
const offGrid = (value: number | null | undefined): boolean =>
typeof value === "number" && !isSnappedStation(value);
/** 기준 · 시작 · 끝 중 하나라도 맞춤 밖(소수)인 구조물인가. */
export const structureOffGrid = (structure: StructureInstance): boolean =>
offGrid(structure.chainage_m) || offGrid(structure.start_m) || offGrid(structure.end_m);
export interface SnapPlan {
/** 목록 자리 · 옮긴 모양. */
structures: Array<{ index: number; next: StructureInstance }>;
/** 옛 자리 → 새 자리 · 시작 · 끝도 맞춘 속성(독립 기슭막이 구간). */
pipes: Array<{ from: number; to: number; attributes: FacilityAttributes }>;
/** 맞춘 자리에 다른 관이 있어 그대로 둔 관 수. */
skipped: number;
}
/** 옮길 것 셈 — 구조물은 옮긴 모양, 관은 (옛 자리 → 새 자리) · 자리 겹친 관은 건너뜀. */
export function snapPlan(
structures: ReadonlyArray<StructureInstance>,
pipes: ReadonlyArray<PipeFacilityItem>,
): SnapPlan {
const moved = structures.flatMap((structure, index) =>
structureOffGrid(structure)
? [{ index, next: movedStructure(structure, structureAnchorM(structure)) }]
: [],
);
const taken = pipes.filter((pipe) => !offGrid(pipe.chainage_m)).map((pipe) => pipe.chainage_m);
const plan: SnapPlan = { structures: moved, pipes: [], skipped: 0 };
[...pipes]
.filter((pipe) => offGrid(pipe.chainage_m) || offGrid(pipe.start_m) || offGrid(pipe.end_m))
.sort((left, right) => left.chainage_m - right.chainage_m)
.forEach((pipe) => {
const to = snapStructureStation(pipe.chainage_m);
if (to !== pipe.chainage_m && taken.some((other) => Math.abs(other - to) < PIPE_MATCH_M)) {
plan.skipped += 1;
return;
}
taken.push(to);
const attributes = pipeAttributes(pipe);
if (attributes.start_m !== undefined)
attributes.start_m = snapStructureStation(attributes.start_m);
if (attributes.end_m !== undefined) attributes.end_m = snapStructureStation(attributes.end_m);
plan.pipes.push({ from: pipe.chainage_m, to, attributes });
});
return plan;
}
/** 관 한 건의 지금 속성 그대로 — 폼 [수정]과 같은 꼴(비운 칸 = 지움 규칙에 옛 값이 다 실림). */
export const pipeAttributes = (pipe: PipeFacilityItem): FacilityAttributes => ({
facility: pipe.facility,
...(pipe.start_m !== undefined ? { start_m: pipe.start_m } : {}),
...(pipe.end_m !== undefined ? { end_m: pipe.end_m } : {}),
options: { ...(pipe.options ?? {}) },
summary_item: pipe.summary_item ?? null,
group_items: pipe.group_items ?? null,
});
export interface SnapContext {
/** 패널이 쥔 구조물 배열(참조) — 옮긴 것을 제자리에 갈아 끼움. */
structures: StructureInstance[];
pipes: () => ReadonlyArray<PipeFacilityItem>;
/** 목록이 바뀜 알림(한 번). */
emit: () => void;
/** 관 한 건 옮김 — 폼 [수정]과 같은 길. */
movePipe: (from: number, to: number, attributes: FacilityAttributes) => void;
/** 열린 폼을 닫음 — 옛 측점을 쥔 폼이 다음 [수정]에 되돌리지 않게. */
closeForm: () => void;
}
/** 옛 소수 측점(관 · 구조물)을 정수 m 로 옮긴다(요구 167 · 확인 창 없이) — 옮긴 수. */
export function snapOldStations(ctx: SnapContext): number {
const plan = snapPlan(ctx.structures, ctx.pipes());
const count = plan.structures.length + plan.pipes.length;
if (count) {
ctx.closeForm();
if (plan.structures.length) {
plan.structures.forEach(({ index, next }) => (ctx.structures[index] = next));
ctx.emit();
}
plan.pipes.forEach(({ from, to, attributes }) => ctx.movePipe(from, to, attributes));
}
snapNotice(count, plan.skipped);
return count;
}
/** 옛 소수 맞춤 알림 한 줄 — B05 종단 보기 · B06 구조물 목록(관)이 같은 글. */
export function snapNotice(count: number, skipped: number): void {
if (count || skipped)
showToast(
(count ? `옛 소수 측점 ${count}건을 정수 m 로 옮겼습니다 — [저장]하면 남습니다.` : "") +
(skipped ? ` 맞출 자리에 다른 관이 있는 관 ${skipped}건은 그대로 둡니다.` : ""),
skipped ? "info" : "success",
);
}