feat(계산묶음): 346-1 묶음을 B05 페이지 · 노선 편집에 붙임 — 노선 초안 캐시 칸 · 노선 고칠 때마다 종단 따라옴 · ▲▼ = grade · 단계 붙임
- design_pipeline_page.ts 새 — 프로젝트마다 묶음 하나(종단도 dispose 때 버림) · route · ground(sub8) · follow · planline · profile · stations · footprint(sub6) 붙임 · cross · masshaul · haul · drainage 는 붙으면 한 줄 · 첫 계산은 처음 고칠 때 · 가게 stations · profile · footprint · generation - B05_Profile_UI_RouteEdit_Draft.ts 새 — 사진첩 감싸기(쌓기 · 되돌리기 · 다시하기 · 열 때로 = 초안 넘김 · 같으면 null) · 저장 안 한 초안 되살림 · RouteEdit.ts 는 줄 셋(700줄 안) - logic/B05_Profile_Pipeline_Grade.ts 새 — follow(관 · 구조물 좌표 · 계획고 옮김) · planline(저장 기준 · 초안이면 바뀐 구간만 새 지반 첫 계획선) · profile(buildAlignment) · routeRange · gradeRange — sub1 짝 부르기만 - design_profile — listed 를 design_profile_base.ts 로 나눔 · rebuild = pipelineGrade · render · 구조물 측점 = 다시 셈 · adoptPipeline(초안 동안 편집은 메모리만 · [저장]이 안 보냄) · pipelineSource - design_pipeline — input() · dispose() - 시험 test_346_1_pipeline_page 12 건(실제 단계 · 노선 1,078 m 점 옮김 23 ms · 늦은 응답 버림 · 초안 캐시 칸 살아남음) · popout 시험 문맥에 pipelineGrade 자리 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
This commit is contained in:
@@ -83,11 +83,15 @@ export interface DesignPipeline {
|
||||
): Promise<PipelineRunRecord> | null;
|
||||
subscribe(fn: (event: PipelineEvent) => void): () => void;
|
||||
output<K extends StageName>(name: K): PipelineOutputs[K] | null;
|
||||
/** 지금 입력(follow 가 고친 뒤 값 포함 · 읽기만). */
|
||||
input<K extends InputKey>(key: K): PipelineInputs[K];
|
||||
readonly generation: number;
|
||||
/** 단계 · 알림 안에서 들어온 입력 쓰기(고리)를 막은 수. */
|
||||
readonly blockedWrites: number;
|
||||
/** 떠 있는 뒤 단계가 다 끝날 때까지. */
|
||||
settled(): Promise<void>;
|
||||
/** 페이지를 떠남 — 떠 있는 계산 · 요청을 버림. */
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
type Outs = { [S in StageName]: PipelineOutputs[S] | null };
|
||||
@@ -327,6 +331,9 @@ export function createDesignPipeline(options: { stages: PipelineStages }): Desig
|
||||
output(name) {
|
||||
return out[name] as never;
|
||||
},
|
||||
input(key) {
|
||||
return inputs[key];
|
||||
},
|
||||
get generation() {
|
||||
return generation;
|
||||
},
|
||||
@@ -336,6 +343,11 @@ export function createDesignPipeline(options: { stages: PipelineStages }): Desig
|
||||
async settled() {
|
||||
while (inFlight.size) await Promise.allSettled([...inFlight]);
|
||||
},
|
||||
dispose() {
|
||||
generation += 1;
|
||||
controller.abort();
|
||||
listeners.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/* =============================================================================
|
||||
* A00_Common/design/design_pipeline_page.ts
|
||||
* B05 · B06 한 페이지의 계산 묶음 하나(PLAN 346-1) — 프로젝트마다 하나 · 페이지를 떠나면 버림
|
||||
* (종단도 `dispose`). 단계 붙임 · 입력 모음 · 가게 잇기만 — 셈은 단계 파일 몫.
|
||||
*
|
||||
* route · ground `B05_Profile/logic/B05_Profile_Pipeline_RouteGround.ts`(sub8)
|
||||
* follow · planline · profile `B05_Profile/logic/B05_Profile_Pipeline_Grade.ts`
|
||||
* stations `design_pipeline_stations.ts` · footprint `B05_Profile_Footprint_Stage.ts`(sub6)
|
||||
* cross · masshaul · haul(sub2) · drainage(sub3) · corridor(sub5) — 붙으면 `stages` 한 줄씩
|
||||
* (없으면 건너뜀 · 계약 규칙).
|
||||
*
|
||||
* 입력 — 노선 = 편집기 다리(`RouteDraftBridge` · 캐시 칸 route-draft) · 계획고 = 종단도 편집
|
||||
* (`pipelineGrade`) · 관 · 구조물 = 가게 초안(없으면 구조물은 저장분). 출력 = 가게 stations ·
|
||||
* profile · footprint · generation + 노선 초안이 있으면 종단도가 새 첫 계획선 · 옮긴 계획고를 그림.
|
||||
* 첫 계산은 처음 고칠 때(또는 저장 안 한 노선 초안이 있을 때) — 열기만 하면 안 셈.
|
||||
* [노선 반영] · [저장]이 서버 정본 — 여기 값은 미리보기(346-3 전까지).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createDesignPipeline, publishToStore, type DesignPipeline } from "./design_pipeline";
|
||||
import { createStationsStage } from "./design_pipeline_stations";
|
||||
import type { PipelineInputs, RouteDraft, Vertex } from "./design_pipeline_types";
|
||||
import { useDesignStore } from "./design_store";
|
||||
import {
|
||||
emptyEdits,
|
||||
type AlignmentBase,
|
||||
type AlignmentEdits,
|
||||
} from "../../B05_Profile/B05_Profile_UI_Profile_Alignment";
|
||||
import {
|
||||
analysisTerrain,
|
||||
fetchRoutePlan,
|
||||
type RoutePlanResponse,
|
||||
} from "../../B05_Profile/B05_Profile_Api_Replan";
|
||||
import { fetchStructures, fetchStructureTypes } from "../../B05_Profile/B05_Profile_Api_Structures";
|
||||
import {
|
||||
designTerrainGridNow,
|
||||
groundStageDeps,
|
||||
} from "../../B05_Profile/B05_Profile_Api_TerrainGrid";
|
||||
import {
|
||||
createGroundStage,
|
||||
createRouteStage,
|
||||
draftLine,
|
||||
} from "../../B05_Profile/logic/B05_Profile_Pipeline_RouteGround";
|
||||
import {
|
||||
createPlanlineStage,
|
||||
followStage,
|
||||
gradeRange,
|
||||
profileStage,
|
||||
routeRange,
|
||||
} from "../../B05_Profile/logic/B05_Profile_Pipeline_Grade";
|
||||
import { footprintStage } from "../../B05_Profile/logic/B05_Profile_Footprint_Stage";
|
||||
import type { PlanType } from "../../common_util/common_util_structure_station";
|
||||
import type {
|
||||
RouteDraftBridge,
|
||||
RouteDraftEdit,
|
||||
} from "../../B05_Profile/B05_Profile_UI_RouteEdit_Draft";
|
||||
|
||||
/** 종단도(`design_profile`)가 묶음에 내주는 것. */
|
||||
export interface ProfileLink {
|
||||
/** 저장 노선의 종단 기준(측점 한 벌) · 지금 계획고 편집. */
|
||||
pipelineSource(): { base: AlignmentBase | null; edits: AlignmentEdits };
|
||||
/** 노선 초안으로 셈한 첫 계획선 · 옮긴 계획고를 그림. */
|
||||
adoptPipeline(base: AlignmentBase, edits: AlignmentEdits, draft: boolean): void;
|
||||
}
|
||||
|
||||
interface PagePipeline {
|
||||
pipe: DesignPipeline;
|
||||
bridge: RouteDraftBridge;
|
||||
grade(edits: AlignmentEdits): void;
|
||||
restart(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
const pages = new Map<string, PagePipeline>();
|
||||
const same = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);
|
||||
|
||||
/** 편집기에 넘길 다리 — 그 프로젝트 묶음을 처음 부를 때 세움. */
|
||||
export function routeDraftBridge(
|
||||
projectId: string,
|
||||
link: ProfileLink,
|
||||
options: { stationIntervalM?: number } = {},
|
||||
): RouteDraftBridge {
|
||||
let page = pages.get(projectId);
|
||||
if (!page) pages.set(projectId, (page = createPagePipeline(projectId, link, options)));
|
||||
return page.bridge;
|
||||
}
|
||||
|
||||
/** 종단도 계획고 편집(▲▼ · 끌기 · 되돌리기) — 묶음이 없으면(편집기 안 뜸) 아무 일 없음. */
|
||||
export const pipelineGrade = (projectId: string, edits: AlignmentEdits): void =>
|
||||
pages.get(projectId)?.grade(edits);
|
||||
|
||||
/** 저장 종단 기준이 바뀜(새 상세 · 구조물 측점) — 셈 중이면 처음부터 다시. */
|
||||
export const restartPagePipeline = (projectId: string): void => pages.get(projectId)?.restart();
|
||||
|
||||
/** 지금 묶음(개발 · 시험) — 없으면 null. */
|
||||
export const pagePipeline = (projectId: string): DesignPipeline | null =>
|
||||
pages.get(projectId)?.pipe ?? null;
|
||||
|
||||
export function disposePagePipeline(projectId: string): void {
|
||||
pages.get(projectId)?.dispose();
|
||||
pages.delete(projectId);
|
||||
}
|
||||
|
||||
function createPagePipeline(
|
||||
projectId: string,
|
||||
link: ProfileLink,
|
||||
options: { stationIntervalM?: number },
|
||||
): PagePipeline {
|
||||
const store = useDesignStore(projectId, null);
|
||||
let plan: RoutePlanResponse | null = null;
|
||||
let types: PlanType[] = [];
|
||||
let savedStructures: unknown[] = [];
|
||||
let ready = false;
|
||||
let started = false;
|
||||
/** 앞 종단이 노선 초안으로 셈한 것인가 — 초안을 버리면 저장 기준으로 다시 그림. */
|
||||
let drawnDraft = false;
|
||||
let disposed = false;
|
||||
|
||||
const savedLine = (): Vertex[] | null =>
|
||||
plan ? plan.planned.map(([x, y]): Vertex => [x, y]) : null;
|
||||
const pipe = createDesignPipeline({
|
||||
stages: {
|
||||
route: createRouteStage({
|
||||
minRadiusM: () => plan?.min_radius_m ?? 0,
|
||||
savedLine: () =>
|
||||
plan && {
|
||||
line: savedLine()!,
|
||||
curves: plan.curves.map(({ start, apex, end, radius_m }) => ({
|
||||
start,
|
||||
apex,
|
||||
end,
|
||||
radius_m,
|
||||
})),
|
||||
},
|
||||
}),
|
||||
follow: followStage,
|
||||
stations: createStationsStage({ intervalM: options.stationIntervalM, types: () => types }),
|
||||
ground: createGroundStage(groundStageDeps(projectId)),
|
||||
planline: createPlanlineStage({ savedBase: () => link.pipelineSource().base, savedLine }),
|
||||
profile: profileStage,
|
||||
footprint: footprintStage,
|
||||
},
|
||||
});
|
||||
pipe.subscribe(publishToStore(store));
|
||||
pipe.subscribe((event) => {
|
||||
if (event.kind !== "stage" || event.stage !== "profile") return;
|
||||
const draft = pipe.input("route") !== null;
|
||||
const base = pipe.output("planline");
|
||||
const edits = (pipe.input("alignment") as AlignmentEdits | null) ?? emptyEdits();
|
||||
if ((draft || drawnDraft) && base) link.adoptPipeline(base, edits, draft);
|
||||
drawnDraft = draft;
|
||||
});
|
||||
|
||||
const inputsNow = (): PipelineInputs => ({
|
||||
route: store.get("routeDraft"),
|
||||
alignment: link.pipelineSource().edits,
|
||||
cross: null,
|
||||
// ponytail: 저장 관 목록(좌표)은 아직 안 받음 — 관 초안만. 346-3 에서 관 정본을 가게로.
|
||||
pipes: store.get("pipes") ?? [],
|
||||
structures: store.get("structures") ?? savedStructures,
|
||||
});
|
||||
const start = (): void => {
|
||||
if (disposed || !ready || !link.pipelineSource().base) return;
|
||||
started = true;
|
||||
void pipe.start(inputsNow());
|
||||
};
|
||||
|
||||
/** 저장 노선(그린 선 · 곡선표 · 기본 반경) — [노선 반영] 뒤 다시 받음(`restart`). */
|
||||
const loadPlan = (): Promise<void> =>
|
||||
fetchRoutePlan(projectId)
|
||||
.then((routePlan) => void (plan = routePlan))
|
||||
.catch(() => undefined);
|
||||
|
||||
designTerrainGridNow(projectId); // 지형 격자 미리 받기(받는 동안은 서버 길)
|
||||
void Promise.all([
|
||||
loadPlan(),
|
||||
fetchStructureTypes().catch(() => []),
|
||||
fetchStructures(projectId).catch(() => null),
|
||||
])
|
||||
.then(([, structureTypes, list]) => {
|
||||
types = structureTypes as PlanType[];
|
||||
savedStructures = list?.structures ?? [];
|
||||
ready = plan !== null;
|
||||
if (store.get("routeDraft")) start(); // 저장 안 한 노선 초안 — 열 때부터 그 노선으로
|
||||
})
|
||||
.catch(() => undefined);
|
||||
|
||||
const unsubscribe = [
|
||||
store.subscribe("pipes", (pipes) => started && void pipe.update("pipe", pipes ?? [])),
|
||||
store.subscribe(
|
||||
"structures",
|
||||
(list) => started && void pipe.update("structure", list ?? savedStructures),
|
||||
),
|
||||
];
|
||||
|
||||
const bridge: RouteDraftBridge = {
|
||||
saved: () => store.get("routeDraft"),
|
||||
edited(edit: RouteDraftEdit | null) {
|
||||
const stored = store.get("routeDraft");
|
||||
let draft: RouteDraft | null = null;
|
||||
if (edit) {
|
||||
draft = { ...edit, rev: (stored?.rev ?? 0) + 1, terrain: analysisTerrain(projectId) };
|
||||
if (stored && same({ ...stored, rev: 0 }, { ...draft, rev: 0 })) draft = stored;
|
||||
}
|
||||
store.set("routeDraft", draft);
|
||||
if (!started) return start();
|
||||
const before = pipe.output("route")?.line ?? null;
|
||||
const after = draft ? draftLine(draft, plan?.min_radius_m ?? 0).line : savedLine();
|
||||
void pipe.update("route", draft, routeRange(before, after));
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
pipe,
|
||||
bridge,
|
||||
grade(edits) {
|
||||
if (!started) return start();
|
||||
if (same(edits, pipe.input("alignment"))) return;
|
||||
const nodes = pipe.output("planline")?.basePvi.map((node) => node.chainage_m) ?? [];
|
||||
const before = pipe.input("alignment") as AlignmentEdits | null;
|
||||
void pipe.update("grade", edits, gradeRange(before, edits, nodes));
|
||||
},
|
||||
restart() {
|
||||
if (started) void loadPlan().then(start);
|
||||
},
|
||||
dispose() {
|
||||
disposed = true;
|
||||
unsubscribe.forEach((stop) => stop());
|
||||
pipe.dispose();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -70,11 +70,8 @@ import {
|
||||
createProfileDrag,
|
||||
} from "../../B05_Profile/B05_Profile_UI_Profile_Drag";
|
||||
import { planCurveWarning, sampleAt } from "../../B05_Profile/B05_Profile_UI_Profile_Guard";
|
||||
import {
|
||||
irregularGraphStations,
|
||||
maxChainageOf,
|
||||
} from "../../B05_Profile/B05_Profile_UI_Profile_Layout";
|
||||
import { followMovedNodes, oneStationList } from "../../common_util/common_util_structure_station";
|
||||
import { listedBase } from "./design_profile_base";
|
||||
import { disposePagePipeline, pipelineGrade, restartPagePipeline } from "./design_pipeline_page";
|
||||
import type { RouteProfilePanelCallbacks } from "../../B05_Profile/B05_Profile_UI_Profile_Panel_Types";
|
||||
import type { ElevationWindowResult } from "@util/common_util_chart_ywindow";
|
||||
import { frameOf } from "@util/common_util_view_window";
|
||||
@@ -223,6 +220,8 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
let storedBase: AlignmentBase | null = null;
|
||||
let alignment: ProfileAlignment | null = null;
|
||||
let store = createProfileEditStore(null, emptyEdits(), () => rebuild());
|
||||
/** 계산 묶음이 노선 초안으로 셈한 종단을 그리는 동안의 편집 한 벌 — 메모리만(346-1). */
|
||||
let preview: typeof store | null = null;
|
||||
/** 서버 저장분 — 되돌리기 복원이 초안을 다시 읽을 때 기준으로 쓴다. */
|
||||
let savedEdits: AlignmentEdits = emptyEdits();
|
||||
let redrawPending = false;
|
||||
@@ -342,31 +341,8 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
history.record();
|
||||
}
|
||||
|
||||
/** 계획선 측점 줄 = 측점 한 벌(서버 일반 + 지금 구조물 측점 · 272) — 표 · ▲▼ · 그래프 · 3D · 횡단 다시 셈(342-1)이
|
||||
* 같은 목록 · 사라진 구조물 측점의 꼭짓점은 서버와 같게 옮기거나 지움(`followMovedNodes`) · 늘 저장분에서 셈. */
|
||||
function listed(): AlignmentBase | null {
|
||||
if (!storedBase || !detail) return storedBase;
|
||||
const server = detail.longitudinal.stations;
|
||||
const planted = irregularGraphStations(irregularStations, maxChainageOf(detail.longitudinal));
|
||||
const list = oneStationList(server, planted);
|
||||
const after = new Set(list.map((station) => station.chainage_m.toFixed(3)));
|
||||
const before = new Set(server.map((station) => station.chainage_m.toFixed(3)));
|
||||
const gone = server
|
||||
.filter((station) => station.kind === "irregular" && !station.grid)
|
||||
.map((station) => station.chainage_m)
|
||||
.filter((chainage) => !after.has(chainage.toFixed(3)));
|
||||
const born = list
|
||||
.map((station) => station.chainage_m)
|
||||
.filter((chainage) => !before.has(chainage.toFixed(3)));
|
||||
return {
|
||||
...storedBase,
|
||||
basePvi: followMovedNodes(storedBase.basePvi, gone, born),
|
||||
stations: list.map((station) => ({
|
||||
station_id: station.station_id,
|
||||
chainage_m: station.chainage_m,
|
||||
})),
|
||||
};
|
||||
}
|
||||
/** 계획선 측점 줄 = 측점 한 벌 — 셈은 `design_profile_base` 몫(계산 묶음 planline 도 같은 기준). */
|
||||
const listed = (): AlignmentBase | null => listedBase(storedBase, detail, irregularStations);
|
||||
|
||||
/**
|
||||
* 편집 후 다시 그린다. 길게 누르기(초당 10회)로 연속 호출되므로 한 프레임에 한 번만
|
||||
@@ -374,6 +350,7 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
*/
|
||||
function rebuild(): void {
|
||||
if (base) alignment = buildAlignment(base, store.edits());
|
||||
pipelineGrade(projectId, store.edits());
|
||||
// 계획고가 바뀌면 측점별 횡단 단면적도 함께 바뀐다 — 전 측점을 다시 계산해 공유
|
||||
// 캐시에 얹는다. 그래야 **횡단 기준** 유토곡선과 횡단 카드가 따라 움직인다
|
||||
// (2026-08-03 사용자 보고: B05에서 계획선을 끌어도 횡단 곡선이 그대로였음).
|
||||
@@ -525,6 +502,7 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
// 이월분은 base 설정 후 미저장 초안으로 커밋한다(확정 시 전송·재탐색 후 새로고침에도 유지).
|
||||
if (carried) store.replace(carried);
|
||||
alignment = base ? buildAlignment(base, store.edits()) : null;
|
||||
restartPagePipeline(projectId);
|
||||
// 유토곡선 balloon 위치 캐시 — 두 보기가 **같은 scope**를 써서 같은 자리를 공유한다.
|
||||
// 영구저장소 값(detail.balloon_offsets)이 있으면 그것이 이긴다.
|
||||
configureBalloonOffsets(
|
||||
@@ -601,6 +579,7 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
irregularStations = stations;
|
||||
base = listed();
|
||||
if (base) alignment = buildAlignment(base, store.edits());
|
||||
restartPagePipeline(projectId);
|
||||
draw();
|
||||
},
|
||||
/** 구조물 타입 레지스트리를 받아 마크 색·약호에 쓴다(최초 1회 · 우클릭 목록은 사이드 패널 몫). */
|
||||
@@ -645,9 +624,24 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
if (key !== undefined) store.resetStation(Number(key), moveTo);
|
||||
},
|
||||
isDirty: () => store.dirty(),
|
||||
/** 계산 묶음(346-1) — 저장 노선의 종단 기준 · 지금 계획고 편집. */
|
||||
pipelineSource: () => ({ base: listed(), edits: store.edits() }),
|
||||
/** 노선 초안으로 셈한 첫 계획선 · 옮긴 계획고를 그림 — 초안 동안 편집은 메모리만(저장 노선에
|
||||
* 안 씀 · [노선 반영] · [저장]이 서버 정본) · 초안을 버리면 저장 기준 · 세션 편집으로 돌아옴. */
|
||||
adoptPipeline(nextBase: AlignmentBase, edits: AlignmentEdits, draft: boolean) {
|
||||
const sameEdits = JSON.stringify(edits) === JSON.stringify(store.edits());
|
||||
if (draft && store === preview && base === nextBase && sameEdits) return; // 이미 그림
|
||||
if (draft && (store !== preview || !sameEdits))
|
||||
store = preview = createProfileEditStore(null, edits, () => rebuild());
|
||||
if (!draft && store === preview)
|
||||
store = createProfileEditStore(routeId, savedEdits, () => rebuild());
|
||||
if (!draft) preview = null;
|
||||
base = draft ? nextBase : listed();
|
||||
rebuild();
|
||||
},
|
||||
/** [확정] 직전에 호출한다. 편집이 없으면 아무 것도 하지 않는다. */
|
||||
async save(): Promise<void> {
|
||||
if (!routeId || !store.dirty()) return;
|
||||
if (!routeId || !store.dirty() || store === preview) return; // 노선 초안 미리보기 편집은 안 보냄
|
||||
const saved = await saveProfileAlignment(projectId, routeId, store.edits());
|
||||
noticeDroppedEdits(saved);
|
||||
const next = saved.profile_alignment as ProfileAlignment | undefined;
|
||||
@@ -688,6 +682,7 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
drainagePanel?.load(projectId);
|
||||
},
|
||||
dispose() {
|
||||
disposePagePipeline(projectId);
|
||||
setListening(false);
|
||||
drag.dispose();
|
||||
crossPreview.dispose();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/* =============================================================================
|
||||
* A00_Common/design/design_profile_base.ts
|
||||
* 종단도 계획선 기준 = 측점 한 벌(서버 일반 + 지금 구조물 측점 · 272) — `design_profile` 에서 나눔(700줄).
|
||||
* 계산 묶음 planline 단계(`design_pipeline_page`)도 같은 기준을 받음.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { SectionDetailResponse } from "../../B06_Section/B06_Section_Api_Fetch";
|
||||
import type { AlignmentBase } from "../../B05_Profile/B05_Profile_UI_Profile_Alignment";
|
||||
import type { IrregularStation } from "../../B05_Profile/B05_Profile_UI_IrregularStations";
|
||||
import {
|
||||
irregularGraphStations,
|
||||
maxChainageOf,
|
||||
} from "../../B05_Profile/B05_Profile_UI_Profile_Layout";
|
||||
import { followMovedNodes, oneStationList } from "../../common_util/common_util_structure_station";
|
||||
|
||||
/** 계획선 측점 줄 = 측점 한 벌(서버 일반 + 지금 구조물 측점 · 272) — 표 · ▲▼ · 그래프 · 3D · 횡단 다시 셈(342-1)이
|
||||
* 같은 목록 · 사라진 구조물 측점의 꼭짓점은 서버와 같게 옮기거나 지움(`followMovedNodes`) · 늘 저장분에서 셈. */
|
||||
export function listedBase(
|
||||
storedBase: AlignmentBase | null,
|
||||
detail: SectionDetailResponse | null,
|
||||
irregularStations: IrregularStation[],
|
||||
): AlignmentBase | null {
|
||||
if (!storedBase || !detail) return storedBase;
|
||||
const server = detail.longitudinal.stations;
|
||||
const planted = irregularGraphStations(irregularStations, maxChainageOf(detail.longitudinal));
|
||||
const list = oneStationList(server, planted);
|
||||
const after = new Set(list.map((station) => station.chainage_m.toFixed(3)));
|
||||
const before = new Set(server.map((station) => station.chainage_m.toFixed(3)));
|
||||
const gone = server
|
||||
.filter((station) => station.kind === "irregular" && !station.grid)
|
||||
.map((station) => station.chainage_m)
|
||||
.filter((chainage) => !after.has(chainage.toFixed(3)));
|
||||
const born = list
|
||||
.map((station) => station.chainage_m)
|
||||
.filter((chainage) => !before.has(chainage.toFixed(3)));
|
||||
return {
|
||||
...storedBase,
|
||||
basePvi: followMovedNodes(storedBase.basePvi, gone, born),
|
||||
stations: list.map((station) => ({
|
||||
station_id: station.station_id,
|
||||
chainage_m: station.chainage_m,
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { createRouteProfilePanel } from "../../B05_Profile/B05_Profile_UI_Profil
|
||||
import { leaveForDashboard } from "../b_missing_data_guard";
|
||||
import { currentRoute, remountRoute } from "../router";
|
||||
import { mountRouteEditor } from "../../B05_Profile/B05_Profile_UI_RouteEdit";
|
||||
import { routeDraftBridge } from "./design_pipeline_page";
|
||||
import { createSelectionSync, graphTargetAt } from "../../B05_Profile/B05_Profile_UI_Selection";
|
||||
import {
|
||||
readStructurePick,
|
||||
@@ -488,6 +489,10 @@ export async function mountProfileView(
|
||||
smooth: latestResponse.surface_params.smooth,
|
||||
tools: [view3d.modal.button],
|
||||
recommend: panel.recommend,
|
||||
// 계산 묶음(346-1) — 노선을 고칠 때마다 종단이 따라옴(미리보기 · [노선 반영]이 정본).
|
||||
draft: routeDraftBridge(activeProjectId, profilePanel, {
|
||||
stationIntervalM: panel.values().stationInterval ?? undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
// ③ 종단면·횡단 자료 — 하단 패널을 3D보다 먼저 채운다.
|
||||
|
||||
Reference in New Issue
Block a user