- 규칙: 구조물 · 관 기준 측점 = 정수 m 사사오입(일반 측점 1 m 붙기 뺌) · 측점 심는 자리 TS 짝(plantedChainages ↔ resolve_extra_stations) · 같은 m 면 구조물 측점 하나(격자 자리 grid) · 떠나면 일반 측점(oneStationList ↔ _merge_irregular_into_longitudinal) - 서버: B05 [저장] · B06 저장 · [측점 만들기]가 목록 전체 다시 맞춤(빈 목록도 · 옛 글 · 횡단 파일 글) · 계획선 측점 줄 다시 셈 · 정본에 없는 횡단 행은 0.5 m 안 새 측점으로 옮기고 나머지 지움 · 옛 소수 측점 꼭짓점 · 편집은 정수 자리로 · 맞춤 실패는 저장을 안 막음 - 화면: 종단 그래프 · 3D · 표 · ▲▼ 한 목록(초안 반영) · C군 구간형 · 독립 기슭막이 시작 · 끝 측점 · B06 카드는 초안에 없는 옛 구조물 측점 숨김 · 고름은 같은 m 구조물 측점 먼저 · 폼 거리 칸 정수 표기 · C군 계산 구간 · 포장 나눔 경계 정수 - 옛 자료: [정수 맞춤] 단추 없앰 · 종단 보기 열 때 한 번 자동 정수 · 알림 한 줄 · [저장]하면 남음 - 시험: test_272_1 · test_272_2 새로 · 234_1 · 238_1 · b05_extra_stations_supplier · b06_patch_station · b05_station_roundtrip · b05_b06_save_roundtrip 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
136 lines
6.4 KiB
TypeScript
136 lines
6.4 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));
|
|
}
|
|
if (count || plan.skipped)
|
|
showToast(
|
|
(count ? `옛 소수 측점 ${count}건을 정수 m 로 옮겼습니다 — [저장]하면 남습니다.` : "") +
|
|
(plan.skipped ? ` 맞출 자리에 다른 관이 있는 관 ${plan.skipped}건은 그대로 둡니다.` : ""),
|
|
plan.skipped ? "info" : "success",
|
|
);
|
|
return count;
|
|
}
|