use std::collections::HashMap;
use std::ops::{Div, RangeToInclusive};
use crate::core::VerticalDistance;
use crate::measurements::{Mass, Speed};
use crate::nd::{RunwayConditionCode, RunwaySurface};
use super::Influences;
#[derive(Clone, PartialEq, Debug)]
pub enum FactorOfEffect<T>
where
T: Into<f32>,
T: Div<T, Output = f32>,
{
Range(Vec<(RangeToInclusive<T>, f32)>),
Rate { numerator: f32, denominator: T },
}
impl<T> FactorOfEffect<T>
where
T: Into<f32>,
T: Div<T, Output = f32>,
T: PartialOrd,
{
pub fn factor(self, effect: T) -> f32 {
match self {
Self::Range(ranges) => ranges
.iter()
.find(|range| range.0.contains(&effect))
.map(|range| range.1)
.expect("Range should be fully covered."),
Self::Rate {
numerator,
denominator,
} => effect / denominator * numerator,
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum AlteringFactor {
DecreaseHeadwind(FactorOfEffect<Speed>),
IncreaseTailwind(FactorOfEffect<Speed>),
IncreaseAltitude(FactorOfEffect<VerticalDistance>),
IncreaseRWYCC(HashMap<(Option<RunwayConditionCode>, Option<RunwaySurface>), f32>),
RunwaySlope(FactorOfEffect<f32>),
Mass(FactorOfEffect<Mass>),
}
impl AlteringFactor {
pub fn ground_roll_factor(&self, influences: &Influences) -> f32 {
match self {
Self::DecreaseHeadwind(f) => {
if influences.headwind() > &Speed::kt(0.0) {
1.0 - f.clone().factor(*influences.headwind())
} else {
1.0
}
}
Self::IncreaseTailwind(f) => {
if influences.headwind() < &Speed::kt(0.0) {
1.0 + f.clone().factor(*influences.headwind() * -1.0)
} else {
1.0
}
}
Self::IncreaseAltitude(f) => 1.0 + f.clone().factor(*influences.level()),
Self::IncreaseRWYCC(map) => {
let rwycc_and_surface = (Some(*influences.rwycc()), Some(*influences.surface()));
let rwycc_any_surface = (Some(*influences.rwycc()), None::<RunwaySurface>);
let any_rwycc_and_surface =
(None::<RunwayConditionCode>, Some(*influences.surface()));
let f = map
.get(&rwycc_and_surface)
.or_else(|| map.get(&rwycc_any_surface))
.or_else(|| map.get(&any_rwycc_and_surface))
.unwrap_or(&0.0);
1.0 + f
}
Self::RunwaySlope(f) => 1.0 + f.clone().factor(*influences.slope()),
Self::Mass(f) => 1.0 + f.clone().factor(*influences.mass()),
}
}
pub fn clear_obstacle_factor(&self, influences: &Influences) -> f32 {
match self {
Self::DecreaseHeadwind(_)
| Self::IncreaseTailwind(_)
| Self::Mass(_)
| Self::IncreaseAltitude(_) => self.ground_roll_factor(influences),
_ => 1.0, }
}
}
#[derive(Clone, PartialEq, Debug, Default)]
pub struct AlteringFactors {
factors: Vec<AlteringFactor>,
}
impl AlteringFactors {
pub fn new<I>(factors: I) -> Self
where
I: IntoIterator<Item = AlteringFactor>,
{
Self {
factors: factors.into_iter().collect(),
}
}
pub fn ground_roll_factor(&self, influences: &Influences) -> f32 {
self.factors
.iter()
.map(|factor| factor.ground_roll_factor(influences))
.reduce(|acc, factor| acc * factor)
.unwrap_or(1.0)
}
pub fn clear_obstacle_factor(&self, influences: &Influences) -> f32 {
self.factors
.iter()
.map(|factor| factor.clear_obstacle_factor(influences))
.reduce(|acc, factor| acc * factor)
.unwrap_or(1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn factor_for_range() {
let factor = FactorOfEffect::Range(vec![
(..=VerticalDistance::PressureAltitude(1000), 0.1),
(..=VerticalDistance::PressureAltitude(3000), 0.13),
(..=VerticalDistance::Unlimited, 0.18),
]);
assert!(0.1 - factor.clone().factor(VerticalDistance::Gnd) <= f32::EPSILON);
assert!(
0.13 - factor
.clone()
.factor(VerticalDistance::PressureAltitude(2000))
<= f32::EPSILON
);
assert!(
0.18 - factor
.clone()
.factor(VerticalDistance::PressureAltitude(4000))
<= f32::EPSILON
);
}
#[test]
fn factor_changes_with_rate() {
let factor = FactorOfEffect::Rate {
numerator: 0.1,
denominator: Speed::kt(9.0),
};
assert_eq!(0.2, factor.factor(Speed::kt(18.0)));
}
}