use core::f32;
use std::cmp::{Ord, Ordering, PartialOrd};
use std::fmt;
use std::ops::Div;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::error::Error;
use crate::measurements::{Altitude, Length, LengthUnit, Pressure};
mod constants {
pub const METER_IN_FEET: f32 = 3.28084;
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub enum VerticalDistance {
Agl(u16),
Altitude(u16),
PressureAltitude(i16),
Fl(u16),
Gnd,
Msl(u16),
Unlimited,
}
impl VerticalDistance {
pub fn to_msl(&self, qnh: Pressure, elevation: Length) -> Option<Altitude> {
let qnh_correction_ft = (qnh - Pressure::STD).to_si() / 100.0 * 27.0;
let ground_ft = *elevation.convert_to(LengthUnit::Feet).value();
Some(Altitude::ft(match self {
Self::Gnd => ground_ft,
Self::Agl(n) => ground_ft + *n as f32,
Self::Msl(n) => *n as f32,
Self::Altitude(n) => *n as f32,
Self::Fl(n) => *n as f32 * 100.0 + qnh_correction_ft,
Self::PressureAltitude(n) => *n as f32 + qnh_correction_ft,
Self::Unlimited => return None,
}))
}
pub fn pa(elevation: i16, qnh: Pressure) -> Result<Self, Error> {
let (pa, overflowed) = elevation.overflowing_add(
(145366.45 * (1.0 - (qnh / Pressure::STD).powf(0.190284))).round() as i16,
);
if overflowed {
Err(Error::ImplausibleValue)
} else {
Ok(Self::PressureAltitude(pa))
}
}
}
impl FromStr for VerticalDistance {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
macro_rules! value {
($s:expr, $index:expr) => {
$s.get($index)
.and_then(|s| s.parse::<u16>().ok())
.ok_or(Error::UnexpectedString)
};
}
match s.get(0..1).unwrap_or_default() {
"F" => Ok(Self::Fl(value!(s, 1..4)?)),
"S" => Ok(Self::Fl(
(value!(s, 1..5)? as f32 * constants::METER_IN_FEET / 10.0).round() as u16,
)),
"A" => Ok(Self::Altitude(value!(s, 1..4)? * 100)), "M" => Ok(Self::Altitude(
(value!(s, 1..5)? as f32 * constants::METER_IN_FEET).round() as u16,
)),
_ => Err(Error::UnexpectedString),
}
}
}
impl fmt::Display for VerticalDistance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VerticalDistance::Gnd => write!(f, "GND"),
VerticalDistance::Fl(value) => write!(f, "FL{value}"),
VerticalDistance::Agl(value) => write!(f, "{value} AGL"),
VerticalDistance::Msl(value) => write!(f, "{value} MSL"),
VerticalDistance::Altitude(value) => write!(f, "{value} ALT"),
VerticalDistance::PressureAltitude(value) => write!(f, "PA {value}"),
VerticalDistance::Unlimited => write!(f, "unlimited"),
}
}
}
impl Ord for VerticalDistance {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::Gnd, Self::Gnd) => Ordering::Equal,
(Self::Gnd, _) => Ordering::Less,
(_, Self::Gnd) => Ordering::Greater,
(Self::Unlimited, Self::Unlimited) => Ordering::Equal,
(Self::Unlimited, _) => Ordering::Greater,
(_, Self::Unlimited) => Ordering::Less,
(Self::Agl(v), Self::Agl(o)) => v.cmp(o),
(Self::PressureAltitude(v), Self::PressureAltitude(o)) => v.cmp(o),
_ => {
fn to_msl(vd: &VerticalDistance) -> u16 {
match vd {
VerticalDistance::Fl(v) => v * 100,
VerticalDistance::Msl(v) => *v,
VerticalDistance::Altitude(v) => *v,
_ => panic!(
"We can't compare {vd} here, since it doesn't reference to common datum."
),
}
}
to_msl(self).cmp(&to_msl(other))
}
}
}
}
impl PartialOrd for VerticalDistance {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Div for VerticalDistance {
type Output = f32;
fn div(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::Gnd, Self::Gnd) => 1.0,
(Self::Fl(a), Self::Fl(b)) => (a / b).into(),
(Self::Agl(a), Self::Agl(b)) => (a / b).into(),
(Self::Msl(a), Self::Msl(b)) => (a / b).into(),
(Self::Altitude(a), Self::Altitude(b)) => (a / b).into(),
(Self::PressureAltitude(a), Self::PressureAltitude(b)) => (a / b).into(),
(Self::Unlimited, Self::Unlimited) => 1.0,
_ => unimplemented!(
"Division of vertical distances of different types is not yet supported!"
),
}
}
}
impl From<VerticalDistance> for f32 {
fn from(value: VerticalDistance) -> Self {
match value {
VerticalDistance::Gnd => 0.0,
VerticalDistance::Fl(value) => value.into(),
VerticalDistance::Agl(value) => value.into(),
VerticalDistance::Msl(value) => value.into(),
VerticalDistance::Altitude(value) => value.into(),
VerticalDistance::PressureAltitude(value) => value.into(),
VerticalDistance::Unlimited => f32::INFINITY,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vertical_distance_from_str() {
assert_eq!(
"F085".parse::<VerticalDistance>(),
Ok(VerticalDistance::Fl(85))
);
assert_eq!(
"S1130".parse::<VerticalDistance>(),
Ok(VerticalDistance::Fl(371))
);
assert_eq!(
"A025".parse::<VerticalDistance>(),
Ok(VerticalDistance::Altitude(2500))
);
assert_eq!(
"M0762".parse::<VerticalDistance>(),
Ok(VerticalDistance::Altitude(2500))
);
assert_eq!(
"F08".parse::<VerticalDistance>(),
Err(Error::UnexpectedString)
);
}
#[test]
fn gnd_is_least() {
assert!(VerticalDistance::Gnd < VerticalDistance::Agl(1000));
assert!(VerticalDistance::Gnd < VerticalDistance::Altitude(1000));
assert!(VerticalDistance::Gnd < VerticalDistance::Fl(10));
assert!(VerticalDistance::Gnd == VerticalDistance::Gnd);
assert!(VerticalDistance::Gnd < VerticalDistance::Msl(100));
assert!(VerticalDistance::Gnd < VerticalDistance::Unlimited);
}
#[test]
fn unlimited_is_greatest() {
assert!(VerticalDistance::Unlimited > VerticalDistance::Agl(1000));
assert!(VerticalDistance::Unlimited > VerticalDistance::Altitude(1000));
assert!(VerticalDistance::Unlimited > VerticalDistance::Fl(10));
assert!(VerticalDistance::Unlimited > VerticalDistance::Gnd);
assert!(VerticalDistance::Unlimited > VerticalDistance::Msl(100));
assert!(VerticalDistance::Unlimited == VerticalDistance::Unlimited);
}
#[test]
fn cmp_vertical_distances() {
assert!(VerticalDistance::Agl(1000) < VerticalDistance::Agl(2000));
assert!(VerticalDistance::Altitude(1000) < VerticalDistance::Altitude(2000));
assert!(VerticalDistance::Msl(1000) < VerticalDistance::Fl(100));
}
#[test]
fn to_msl_at_standard_pressure() {
let std_qnh = Pressure::STD;
let zero_elev = Length::m(0.0);
let alt = VerticalDistance::Fl(100)
.to_msl(std_qnh, zero_elev)
.unwrap();
assert!((alt.to_si() - Length::ft(10_000.0).to_si()).abs() < 1.0);
let alt = VerticalDistance::Msl(5_000)
.to_msl(std_qnh, zero_elev)
.unwrap();
assert!((alt.to_si() - Length::ft(5_000.0).to_si()).abs() < 1.0);
assert!(VerticalDistance::Unlimited
.to_msl(std_qnh, zero_elev)
.is_none());
}
#[test]
fn to_msl_qnh_correction() {
let high_qnh = Pressure::STD + Pressure::h_pa(20.0);
let expected_correction = 20.0 * 27.0; let alt = VerticalDistance::Fl(100)
.to_msl(high_qnh, Length::m(0.0))
.unwrap();
let expected_ft = 10_000.0 + expected_correction;
assert!((alt.to_si() - Length::ft(expected_ft).to_si()).abs() < 2.0);
}
#[test]
fn to_msl_agl_adds_ground_elevation() {
let std_qnh = Pressure::STD;
let ground = Length::ft(500.0);
let alt = VerticalDistance::Agl(1_000)
.to_msl(std_qnh, ground)
.unwrap();
assert!((alt.to_si() - Length::ft(1_500.0).to_si()).abs() < 1.0);
let alt = VerticalDistance::Gnd.to_msl(std_qnh, ground).unwrap();
assert!((alt.to_si() - Length::ft(500.0).to_si()).abs() < 1.0);
}
}