Files
Aislo/A00_Common/design/design_calc_profile.ts
T

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,
);
};