Files
Aislo/B05_Profile/logic/B05_Profile_Sections_Generate.ts
T

108 lines
4.8 KiB
TypeScript

/* =============================================================================
* B05_Profile/logic/B05_Profile_Sections_Generate.ts
* 측점 생성 한 벌(PLAN 511-B B-2) — 노선 + 관 · 구조물 · 추가 측점 + DTM 격자 → 서버 `generate_sections`
* 꼴(종단 표본 · 측점 · 횡단 표본). 서버 측점 생성 길(`B05_Profile_Engine_Sections_Node.py` → Node
* `stations.sections`)이 부름 — 화면 묶음과 같은 측점(`buildStations`) · 같은 지반(`sectionsGround`).
*
* 측점 = `buildStations`(열쇠 · 종류 · 반경 · 확폭) · 지반 · 틀 · 상단측 = `sectionsGround` · 이름 =
* `plantedStations`(카드 이름) · 칸 차례 · 자릿수 = 파이썬 `generate_sections` 그대로 + 측점 열쇠 `key`.
* 머리(schema_version · source · coordinate_reference · cad_exchange · options)는 파이썬이 붙임.
* 순수 — 화면 · fetch · 세션 없음.
* ========================================================================== */
import type { DtmGrid } from "../../common_util/common_util_surface_sampler";
import { stationsAlong } from "./B05_Profile_UI_RouteEdit_Frame";
import { formatStation } from "../../B06_Section/logic/B06_Section_Station_Swap";
import { stationIdOf } from "./B05_Profile_Pipeline_Grade";
import {
buildStations,
lineLength,
plantedStations,
SECTION_INCLUDE_ENDPOINT,
SECTION_STATION_INTERVAL_M,
type StationsOptions,
} from "./B05_Profile_Sections_Stations";
import { sectionsGround, type GroundOptions } from "./B05_Profile_Sections_Ground";
import type { PlanPipe, PlanStructure } from "../../common_util/common_util_structure_station";
import type { DesignCurve, Vertex } from "../../A00_Common/design/logic/design_pipeline_types";
export type GenerateOptions = Omit<StationsOptions, "registry"> & GroundOptions;
const KIND: Record<string, string> = {
grid: "regular",
curve: "curve",
pipe: "irregular",
structure: "irregular",
};
/** 서버 `generate_sections` 꼴 몸통 — 종단(길이 · 표본 · 측점) · 횡단 · 요약 · 도면 기준고. */
export function generateSections(
line: ReadonlyArray<Vertex>,
curves: ReadonlyArray<DesignCurve>,
pipes: ReadonlyArray<PlanPipe>,
structures: ReadonlyArray<PlanStructure>,
grid: DtmGrid,
options: GenerateOptions,
) {
const stations = buildStations(line, curves, pipes, structures, options);
if (stations.length < 2) throw new Error("경로는 최소 2개의 (x, y, z) 좌표로 구성되어야 합니다.");
const gridM = stationsAlong(
line,
options.intervalM ?? SECTION_STATION_INTERVAL_M,
options.includeEndpoint ?? SECTION_INCLUDE_ENDPOINT,
);
const planted = plantedStations(gridM, lineLength(line), pipes, structures, options);
const chainages = stations.map((station) => station.chainageM);
const ground = sectionsGround(line, chainages, grid, options);
const crossSections = stations.map((station, index) => {
const at = ground.stations[index];
const mark = [...planted].find(([m]) => Math.abs(m - station.chainageM) < 1e-6)?.[1];
const kind = index === 0 ? "bp" : station.kind === "end" ? "ep" : KIND[station.kind];
const head: Record<string, unknown> = {
station_id: stationIdOf(station.chainageM),
chainage_m: at.chainage_m,
label: formatStation(station.chainageM) + (kind === "curve" ? " (MC)" : ""),
kind,
center_x: at.center_x,
center_y: at.center_y,
center_z: at.center_z,
azimuth_deg: station.azimuthDeg,
plan_radius_m: station.planRadiusM,
curve_outer_side: station.curveOuterSide,
curve_widening_m: station.curveWideningM,
uphill_side: at.uphill_side,
frame: {
origin: { x: at.center_x, y: at.center_y, z: at.center_z },
tangent_xy: at.tangent_xy,
left_xy: at.left_xy,
up_xyz: [0.0, 0.0, 1.0],
},
};
if (mark) {
head.structure = mark.title;
if (gridM.some((m) => Math.abs(m - station.chainageM) < 1e-6)) head.grid = true;
}
head.key = station.key;
return { head, samples: at.samples };
});
const z = ground.samples.flatMap((sample) => (sample.z === null ? [] : [sample.z]));
return {
longitudinal: {
length_m: ground.length_m,
samples: ground.samples,
stations: crossSections.map(({ head }) => head),
},
cross_sections: crossSections.map(({ head, samples }) => ({ ...head, samples })),
summary: {
station_count: stations.length,
cross_sample_count: crossSections.reduce((sum, { samples }) => sum + samples.length, 0),
invalid_longitudinal_samples: ground.samples.filter((sample) => !sample.valid).length,
invalid_cross_samples: crossSections.reduce(
(sum, { samples }) => sum + samples.filter((sample) => !sample.valid).length,
0,
),
},
datum: z.length ? Math.floor(Math.min(...z) / 10) * 10 : null,
};
}