ifc_lite_landxml/
profile.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Durable LandXML alignment, profile, cross-section, and roadway records.
6//!
7//! These source records deliberately retain authored stations and offsets.
8//! They do not imply a sampled mesh, a corridor solid, or a generated road.
9
10use serde::{Deserialize, Serialize};
11use std::fmt;
12
13use crate::LandXmlSourceId;
14
15/// A horizontal alignment that owns profiles and cross-sections by source id.
16#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
17pub struct LandXmlAlignment {
18    pub source_id: LandXmlSourceId,
19    pub ordinal: usize,
20    pub name: String,
21    pub length: f64,
22    /// Exact LandXML `staStart`, named to match the alignment source model.
23    pub sta_start: f64,
24    pub profile_source_ids: Vec<LandXmlSourceId>,
25    pub cross_section_source_ids: Vec<LandXmlSourceId>,
26}
27
28/// LandXML distinguishes proposed vertical geometry from sampled surface data.
29#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
30#[serde(rename_all = "snake_case")]
31pub enum LandXmlProfileKind {
32    Design,
33    Sampled,
34}
35
36/// A `ProfAlign` or `ProfSurf` record under an alignment.
37#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
38pub struct LandXmlProfile {
39    pub source_id: LandXmlSourceId,
40    pub parent_alignment_source_id: LandXmlSourceId,
41    pub ordinal: usize,
42    pub name: String,
43    pub kind: LandXmlProfileKind,
44    pub pvis: Vec<LandXmlProfilePoint>,
45    pub vertical_curves: Vec<LandXmlVerticalCurve>,
46    /// A `ProfSurf` may contain multiple `PntList2D` segments; each stays
47    /// separate so a source discontinuity is never interpolated away.
48    pub grade_lines: Vec<LandXmlGradeLine>,
49}
50
51/// One station/elevation pair authored by `PVI` or a sampled grade line.
52#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
53pub struct LandXmlProfilePoint {
54    pub source_id: LandXmlSourceId,
55    pub station: f64,
56    /// Missing source elevations are preserved as an explicit capability gap.
57    pub elevation: Option<f64>,
58}
59
60/// A continuous `PntList2D` segment of a sampled profile.
61#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
62pub struct LandXmlGradeLine {
63    pub source_id: LandXmlSourceId,
64    pub parent_profile_source_id: LandXmlSourceId,
65    pub ordinal: usize,
66    pub points: Vec<LandXmlProfilePoint>,
67}
68
69/// The exact LandXML vertical-curve declaration.
70#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
71#[serde(rename_all = "snake_case")]
72pub enum LandXmlVerticalCurveKind {
73    Parabolic,
74    UnsymmetricalParabolic,
75    Circular,
76}
77
78/// Why a design profile cannot be evaluated at a requested station.
79///
80/// The source records are deliberately preserved even when an evaluator cannot
81/// prove the required tangent data.  In particular, this is preferable to
82/// drawing a plausible-looking grade from a lone PVI.
83#[derive(Clone, Debug, Eq, PartialEq)]
84pub enum LandXmlProfileEvaluationError {
85    MissingTangentPvi,
86    InvalidCurveDeclaration,
87    InconsistentCircularCurve,
88    NonFiniteEvaluation,
89}
90
91impl fmt::Display for LandXmlProfileEvaluationError {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        f.write_str(match self {
94            Self::MissingTangentPvi => "vertical curve requires finite PVIs on both tangents",
95            Self::InvalidCurveDeclaration => "vertical curve has an invalid length or radius",
96            Self::InconsistentCircularCurve => {
97                "circular curve radius, length, and tangent grades disagree"
98            }
99            Self::NonFiniteEvaluation => "vertical alignment produces a non-finite elevation",
100        })
101    }
102}
103
104impl std::error::Error for LandXmlProfileEvaluationError {}
105
106/// A vertical curve anchored at its authored point of vertical intersection.
107#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
108pub struct LandXmlVerticalCurve {
109    pub source_id: LandXmlSourceId,
110    pub parent_profile_source_id: LandXmlSourceId,
111    pub kind: LandXmlVerticalCurveKind,
112    pub station: f64,
113    pub elevation: Option<f64>,
114    pub length: Option<f64>,
115    pub length_in: Option<f64>,
116    pub length_out: Option<f64>,
117    pub radius: Option<f64>,
118}
119
120/// A sampled cross-section at one station on its parent alignment.
121#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
122pub struct LandXmlCrossSection {
123    pub source_id: LandXmlSourceId,
124    pub parent_alignment_source_id: LandXmlSourceId,
125    pub ordinal: usize,
126    pub station: f64,
127    pub surface_source_ids: Vec<LandXmlSourceId>,
128}
129
130/// Whether cross-section values are existing/sampled or planned/design values.
131#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
132#[serde(rename_all = "snake_case")]
133pub enum LandXmlCrossSectionSurfaceKind {
134    Sampled,
135    Design,
136}
137
138/// One source cross-section surface, never a generated corridor surface.
139#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
140pub struct LandXmlCrossSectionSurface {
141    pub source_id: LandXmlSourceId,
142    pub parent_cross_section_source_id: LandXmlSourceId,
143    pub kind: LandXmlCrossSectionSurfaceKind,
144    pub name: Option<String>,
145    pub segments: Vec<LandXmlCrossSectionSegment>,
146    pub points: Vec<LandXmlCrossSectionPoint>,
147}
148
149/// A continuous sampled `CrossSectSurf/PntList2D` sequence.
150#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
151pub struct LandXmlCrossSectionSegment {
152    pub source_id: LandXmlSourceId,
153    pub parent_surface_source_id: LandXmlSourceId,
154    pub ordinal: usize,
155    pub points: Vec<LandXmlCrossSectionPoint>,
156}
157
158/// The coordinate convention authored by a `CrossSectPnt`.
159#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
160#[serde(rename_all = "snake_case")]
161pub enum LandXmlCrossSectionPointDataFormat {
162    OffsetElevation,
163    SlopeDistance,
164}
165
166/// A cross-section point as authored, including its optional source references.
167///
168/// LandXML `PointType` permits a `pntRef` with no coordinates.  When both are
169/// present, the coordinates are authoritative (per the schema documentation)
170/// and the reference is retained as provenance rather than substituted.
171#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
172pub struct LandXmlCrossSectionPoint {
173    pub source_id: LandXmlSourceId,
174    pub data_format: LandXmlCrossSectionPointDataFormat,
175    pub offset: Option<f64>,
176    /// Missing source elevations stay observable instead of becoming zero.
177    pub elevation: Option<f64>,
178    pub slope: Option<f64>,
179    pub distance: Option<f64>,
180    pub pnt_ref: Option<String>,
181    pub alignment_ref: Option<String>,
182    pub align_ref_station: Option<f64>,
183    pub alignment_source_id: Option<LandXmlSourceId>,
184    pub plan_feature_ref: Option<String>,
185    pub plan_feature_ref_station: Option<f64>,
186    pub parcel_ref: Option<String>,
187    pub parcel_ref_station: Option<f64>,
188}
189
190/// A roadway's named source associations; it does not claim corridor geometry.
191#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
192pub struct LandXmlRoadway {
193    pub source_id: LandXmlSourceId,
194    pub ordinal: usize,
195    pub name: String,
196    pub alignment_refs: Vec<String>,
197    pub alignment_source_ids: Vec<LandXmlSourceId>,
198    pub surface_refs: Vec<String>,
199    pub surface_source_ids: Vec<LandXmlSourceId>,
200    pub grade_model_refs: Vec<String>,
201}
202
203/// A parser capability gap retained with its source identity and path.
204#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
205pub struct LandXmlCapabilityDiagnostic {
206    pub code: LandXmlCapabilityDiagnosticCode,
207    pub source_id: Option<LandXmlSourceId>,
208    pub source_path: String,
209    pub message: String,
210}
211
212/// Stable capability diagnostics for non-rendering LandXML road semantics.
213#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
214#[serde(rename_all = "snake_case")]
215pub enum LandXmlCapabilityDiagnosticCode {
216    SchemaVersionMismatch,
217    MissingElevation,
218    MissingReference,
219    AmbiguousReference,
220    UnsupportedGradeModelReference,
221    UnresolvedPointReference,
222    UnsupportedPlanFeatureReference,
223    UnsupportedParcelReference,
224    UnsupportedSlopeDistance,
225    SectionDiscontinuity,
226    UnsupportedCorridorExtension,
227    UnsupportedStringLineExtension,
228}
229
230/// A bounded preserved-only corridor or stringline extension root.
231#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
232pub struct LandXmlPreservedOnlyExtension {
233    pub source_id: LandXmlSourceId,
234    pub parent_source_id: Option<LandXmlSourceId>,
235    pub local_name: String,
236    pub source_path: String,
237    pub kind: LandXmlPreservedOnlyExtensionKind,
238}
239
240/// The source area containing an unsupported preserved-only subtree.
241#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
242#[serde(rename_all = "snake_case")]
243pub enum LandXmlPreservedOnlyExtensionKind {
244    Corridor,
245    StringLine,
246}