use axiolid_curve::ElevationLaw;
use crate::error::{AlignmentError, AlignmentResult};
use crate::vertical::{VerticalSegment, VerticalSegmentType};
pub fn elevation_law(segment: &VerticalSegment) -> AlignmentResult<ElevationLaw> {
match segment.predefined_type {
VerticalSegmentType::ConstantGradient => constant_gradient(segment),
VerticalSegmentType::ParabolicArc => parabolic_arc(segment),
ref kind => Err(AlignmentError::Unsupported {
entity: segment.entity,
type_name: kind.source_name().to_owned(),
detail: "no exact elevation law: only constant gradient and parabolic arc are polynomial in plan distance",
}),
}
}
fn constant_gradient(segment: &VerticalSegment) -> AlignmentResult<ElevationLaw> {
if segment.radius_of_curvature.is_some() || segment.start_gradient != segment.end_gradient {
return Err(AlignmentError::InvalidSegment {
entity: segment.entity,
detail: "CONSTANTGRADIENT requires equal gradients and no curvature radius",
});
}
Ok(ElevationLaw::constant_grade(
segment.start_height,
segment.start_gradient,
))
}
fn parabolic_arc(segment: &VerticalSegment) -> AlignmentResult<ElevationLaw> {
if segment.horizontal_length <= 0.0 {
return Err(AlignmentError::InvalidSegment {
entity: segment.entity,
detail: "PARABOLICARC requires a positive horizontal length",
});
}
let law = ElevationLaw::parabolic(
segment.start_height,
segment.start_gradient,
segment.end_gradient,
segment.horizontal_length,
);
if !law.is_well_formed() {
return Err(AlignmentError::InvalidSegment {
entity: segment.entity,
detail: "PARABOLICARC parameters do not form a finite elevation law",
});
}
Ok(law)
}
pub fn profile_law(segments: &[VerticalSegment]) -> AlignmentResult<ElevationLaw> {
let Some(first) = segments.first() else {
return Err(AlignmentError::SemanticViolation {
entity: None,
rule: "a vertical profile must have at least one segment",
});
};
if segments.len() == 1 {
return elevation_law(first);
}
let mut laws = Vec::with_capacity(segments.len());
let mut breaks = Vec::with_capacity(segments.len() - 1);
let start = first.start_dist_along;
for (index, segment) in segments.iter().enumerate() {
laws.push(elevation_law(segment)?);
if index > 0 {
let previous = &segments[index - 1];
let expected = previous.start_dist_along + previous.horizontal_length;
if !approximately(segment.start_dist_along, expected) {
return Err(AlignmentError::InvalidSegment {
entity: segment.entity,
detail: "vertical segments must be contiguous and ascending in StartDistAlong",
});
}
breaks.push(segment.start_dist_along - start);
}
}
Ok(ElevationLaw::Piecewise { breaks, laws })
}
fn approximately(left: f64, right: f64) -> bool {
let scale = left.abs().max(right.abs()).max(1.0);
(left - right).abs() <= 1e-9 * scale
}