92 lines
3.6 KiB
TypeScript
92 lines
3.6 KiB
TypeScript
/* =============================================================================
|
|
* design_calc_profile.ts
|
|
* 계산 자리 「서버」 종단 길(PLAN 511-B B-3) — 판(`design_calc_sites.ts`)이 profile 묶음을 server 로 둔 때
|
|
* 묶음 planline 의 새 지반 첫 계획선 꼭짓점(P1 · `profile.base`)과 profile 단계 계획선 짓기(P2 ~ P4 · P8 ·
|
|
* `profile.alignment`)를 서버 Node 가 같은 TS 로 셈(창구 `POST /projects/{id}/calc`). both 면 브라우저 그대로.
|
|
* 서버 실패(끊김 밖) = 경고 한 줄 · 브라우저 값.
|
|
* ========================================================================== */
|
|
|
|
import { calcOnServerNow } from "./design_calc_sites";
|
|
import { serverCalc } from "./design_calc_server";
|
|
import type { StageFn } from "./logic/design_pipeline_types";
|
|
import {
|
|
groundFollowingBasePvi,
|
|
groundProfile,
|
|
type GroundFollowingInput,
|
|
} from "../../B05_Profile/logic/B05_Profile_Grade_Ground";
|
|
import { profileStage } from "../../B05_Profile/logic/B05_Profile_Pipeline_Grade";
|
|
import {
|
|
emptyEdits,
|
|
type AlignmentNode,
|
|
type ProfileAlignment,
|
|
} from "../../B05_Profile/logic/B05_Profile_UI_Profile_Alignment";
|
|
|
|
const KEY = "profile";
|
|
const r6 = (value: number): number => Number(value.toFixed(6));
|
|
|
|
/** 서버 값 — 끊김은 그대로 던짐(묶음이 버림) · 그 밖 실패는 브라우저 값. */
|
|
async function orLocal<T>(signal: AbortSignal, remote: () => Promise<T>, local: () => T) {
|
|
try {
|
|
return await remote();
|
|
} catch (error) {
|
|
if (signal.aborted) throw error;
|
|
console.warn("[calc-server] profile 실패 — 브라우저 값", error);
|
|
return local();
|
|
}
|
|
}
|
|
|
|
/** planline `groundPvi` — 서버면 첫 계획선 꼭짓점 약속(0.000001 m · `groundFollowingBasePvi` 꼴) · 아니면 null
|
|
* (브라우저 셈 · 단계 동기). 판은 앞 ground 단계가 이미 받음(`calcOnServerNow`). */
|
|
export function groundPviAt(
|
|
input: GroundFollowingInput,
|
|
signal: AbortSignal,
|
|
): Promise<AlignmentNode[] | null> | null {
|
|
if (!calcOnServerNow(KEY)) return null;
|
|
const local = () => groundFollowingBasePvi(input);
|
|
const profile = groundProfile(input.samples);
|
|
if (!profile) return null;
|
|
return orLocal(
|
|
signal,
|
|
async () => {
|
|
const follow = {
|
|
stations: input.stations,
|
|
start_offset_m: input.startOffsetM ?? 0,
|
|
end_offset_m: input.endOffsetM ?? 0,
|
|
};
|
|
const inputs = { profile: { ...profile, policy: input.policy, ground_following: follow } };
|
|
const results = await serverCalc(KEY, ["profile.base"], inputs, signal);
|
|
const nodes = results["profile.base"] as AlignmentNode[] | null;
|
|
return (
|
|
nodes?.map((node) => ({
|
|
chainage_m: r6(node.chainage_m),
|
|
elevation_m: r6(node.elevation_m),
|
|
})) ?? null
|
|
);
|
|
},
|
|
local,
|
|
);
|
|
}
|
|
|
|
/** profile 단계 — 첫 계획선 + 계획고 편집 → 종단(서버면 `profile.alignment` · 아니면 동기 그대로). */
|
|
export const profileAt: StageFn<"profile"> = (ctx) => {
|
|
const base = ctx.out.planline;
|
|
if (!base || !calcOnServerNow(KEY)) return profileStage(ctx);
|
|
return orLocal(
|
|
ctx.signal,
|
|
async () => {
|
|
const profile = {
|
|
chainage: base.chainage,
|
|
ground: base.ground,
|
|
policy: base.policy,
|
|
stations: base.stations,
|
|
base_pvi: base.basePvi,
|
|
edits: ctx.inputs.alignment ?? emptyEdits(),
|
|
only_explicit_curves: base.onlyExplicitCurves,
|
|
};
|
|
const results = await serverCalc(KEY, ["profile.alignment"], { profile }, ctx.signal);
|
|
return results["profile.alignment"] as ProfileAlignment;
|
|
},
|
|
() => profileStage(ctx) as ProfileAlignment,
|
|
);
|
|
};
|