ifc_lite_landxml/alignment/
inspection.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//! Station-indexed source inspection without inventing transition values.
6
7use super::{
8    LandXmlAlignment, LandXmlCantStation, LandXmlNumericDiagnostic, LandXmlStationMapping,
9    LandXmlSuperelevation,
10};
11
12/// The bracketing authored cant values at one physical alignment distance.
13///
14/// LandXML preserves producer-specific transition semantics. This type exposes
15/// the exact neighboring records instead of silently interpolating one.
16#[derive(Clone, Debug, PartialEq, serde::Serialize)]
17pub struct LandXmlCantProbe {
18    pub internal_station: f64,
19    pub station: LandXmlStationMapping,
20    pub previous: Option<LandXmlCantStation>,
21    pub next: Option<LandXmlCantStation>,
22}
23
24impl LandXmlAlignment {
25    /// Inspects the exact authored CantStation records bracketing a physical
26    /// distance. Displayed station equations do not alter Cant's internal
27    /// station axis.
28    pub fn cant_at_distance(
29        &self,
30        distance: f64,
31    ) -> Result<Option<LandXmlCantProbe>, LandXmlNumericDiagnostic> {
32        let station = self.station_at_distance(distance)?;
33        let Some(cant) = &self.cant else {
34            return Ok(None);
35        };
36        let internal_station = self.sta_start + distance;
37        if cant
38            .stations
39            .iter()
40            .any(|candidate| !candidate.station.is_finite())
41            || cant
42                .stations
43                .windows(2)
44                .any(|pair| pair[1].station <= pair[0].station)
45        {
46            return Err(super::numeric::diagnostic(
47                &cant.source_id,
48                "LXMLA207",
49                "CantStation values must be finite and strictly increasing",
50            ));
51        }
52        let split = cant
53            .stations
54            .partition_point(|candidate| candidate.station <= internal_station);
55        Ok(Some(LandXmlCantProbe {
56            internal_station,
57            station,
58            previous: split
59                .checked_sub(1)
60                .and_then(|index| cant.stations.get(index))
61                .cloned(),
62            next: cant.stations.get(split).cloned(),
63        }))
64    }
65
66    /// Returns all Superelevation blocks whose declared station range includes
67    /// the physical distance. Missing bounds are open bounds, never guessed
68    /// from a sibling event's textual value.
69    pub fn superelevations_at_distance(
70        &self,
71        distance: f64,
72    ) -> Result<Vec<&LandXmlSuperelevation>, LandXmlNumericDiagnostic> {
73        self.station_at_distance(distance)?;
74        let internal_station = self.sta_start + distance;
75        Ok(self
76            .superelevations
77            .iter()
78            .filter(|value| {
79                value
80                    .sta_start
81                    .is_none_or(|start| internal_station >= start)
82                    && value.sta_end.is_none_or(|end| internal_station <= end)
83            })
84            .collect())
85    }
86}