#[derive(Debug, PartialEq, Clone, Copy)]
pub enum IsaError {
InputOutOfRange,
ComputationError,
}
pub struct InternationalStandardAtmosphere {
}
impl InternationalStandardAtmosphere {
pub fn list_base_altitudes () -> Vec<uom::si::f64::Length> {
let out: Vec<uom::si::f64::Length> = vec![
uom::si::f64::Length::new::<uom::si::length::kilometer>(-5.0),
uom::si::f64::Length::new::<uom::si::length::kilometer>(0.0),
uom::si::f64::Length::new::<uom::si::length::kilometer>(11.0),
uom::si::f64::Length::new::<uom::si::length::kilometer>(20.0),
uom::si::f64::Length::new::<uom::si::length::kilometer>(32.0),
uom::si::f64::Length::new::<uom::si::length::kilometer>(47.0),
uom::si::f64::Length::new::<uom::si::length::kilometer>(51.0),
uom::si::f64::Length::new::<uom::si::length::kilometer>(71.0),
uom::si::f64::Length::new::<uom::si::length::kilometer>(80.0),
];
out
}
pub fn altitude_to_temperature (geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::ThermodynamicTemperature, IsaError> {
let h = geopotential_altitude.get::<uom::si::length::kilometer>();
if h < -5.0 || 80.0 < h {
return Err(IsaError::InputOutOfRange);
}
let delta_h:f64;
let lapse_rate:f64;
let t0:f64;
match geopotential_altitude.get::<uom::si::length::kilometer>() {
h if -5.0 <= h && h <= 11.0 => {
t0 = 288.15; lapse_rate = -6.5; delta_h = h - 0.0; },
h if 11.0 < h && h <= 20.0 => {
t0 = 216.65; lapse_rate = 0.0; delta_h = h - 11.0; },
h if 20.0 < h && h <= 32.0 => {
t0 = 216.65; lapse_rate = 1.0; delta_h = h - 20.0; },
h if 32.0 < h && h <= 47.0 => {
t0 = 228.65; lapse_rate = 2.8; delta_h = h - 32.0;
},
h if 47.0 < h && h <= 51.0 => {
t0 = 270.65; lapse_rate = 0.0; delta_h = h - 47.0; },
h if 51.0 < h && h <= 71.0 => {
t0 = 270.65; lapse_rate = -2.8; delta_h = h - 51.0; },
h if 71.0 < h && h <= 80.0 => {
t0 = 214.65; lapse_rate = -2.0; delta_h = h - 71.0; },
_ => {
return Err(IsaError::InputOutOfRange);
}
}
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(t0 + lapse_rate * delta_h))
}
pub fn altitude_to_temperature_ratio(geopotential_altitude: uom::si::f64::Length) -> Result<f64, IsaError> {
let temperature = Self::altitude_to_temperature(geopotential_altitude)?;
Ok(temperature.get::<uom::si::thermodynamic_temperature::kelvin>() / 288.15)
}
pub fn altitude_to_base_temperature(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::ThermodynamicTemperature, IsaError> {
let h = geopotential_altitude.get::<uom::si::length::kilometer>();
if h < -5.0 || 80.0 < h {
return Err(IsaError::InputOutOfRange);
}
match geopotential_altitude.get::<uom::si::length::kilometer>() {
h if -5.0 <= h && h <= 0.0 => {
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(320.65))
},
h if 0.0 < h && h <= 11.0 => {
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(288.15))
},
h if 11.0 < h && h <= 20.0 => {
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(216.65))
},
h if 20.0 < h && h <= 32.0 => {
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(216.65))
},
h if 32.0 < h && h <= 47.0 => {
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(228.65))
},
h if 47.0 < h && h <= 51.0 => {
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(270.65))
},
h if 51.0 < h && h <= 71.0 => {
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(270.65))
},
h if 71.0 < h && h <= 80.0 => {
Ok(uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(214.65))
},
_ => {
Err(IsaError::InputOutOfRange)
}
}
}
pub fn altitude_to_base_geopotential_altitude(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Length, IsaError> {
let h = geopotential_altitude.get::<uom::si::length::kilometer>();
if h < -5.0 || 80.0 < h {
return Err(IsaError::InputOutOfRange);
}
match h {
h if -5.0 <= h && h <= 0.0 => {
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(-5.0))
},
h if 0.0 < h && h <= 11.0 => {
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(0.0))
},
h if 11.0 < h && h <= 20.0 => {
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(11.0))
},
h if 20.0 < h && h <= 32.0 => {
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(20.0))
},
h if 32.0 < h && h <= 47.0 => {
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(32.0))
},
h if 47.0 < h && h <= 51.0 => {
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(47.0))
},
h if 51.0 < h && h <= 71.0 => {
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(51.0))
},
h if 71.0 < h && h <= 80.0 => {
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(71.0))
},
_ => {
Err(IsaError::InputOutOfRange)
}
}
}
pub fn altitude_to_lapse_rate(geopotential_altitude: uom::si::f64::Length) -> Result<f64, IsaError> {
let h = geopotential_altitude.get::<uom::si::length::kilometer>();
if h < -5.0 || 80.0 < h { return Err(IsaError::InputOutOfRange); }
match geopotential_altitude.get::<uom::si::length::kilometer>() {
h if -5.0 <= h && h <= 11.0 => {
Ok(-6.5) },
h if 11.0 < h && h <= 20.0 => {
Ok(0.0) },
h if 20.0 < h && h <= 32.0 => {
Ok(1.0) },
h if 32.0 < h && h <= 47.0 => {
Ok(2.8) },
h if 47.0 < h && h <= 51.0 => {
Ok(0.0) },
h if 51.0 < h && h <= 71.0 => {
Ok(-2.8) },
h if 71.0 < h && h <= 80.0 => {
Ok(-2.0) },
_ => {
Err(IsaError::InputOutOfRange)
}
}
}
pub fn altitude_geopotential_to_geometric(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Length, IsaError> {
let geopotential_alt_meter = geopotential_altitude.get::<uom::si::length::meter>();
if geopotential_alt_meter < -5.0 || geopotential_alt_meter > 80_000.0 { return Err(IsaError::InputOutOfRange); }
let rad_meter = Self::constant_earth_radius().get::<uom::si::length::meter>();
let radius_minus_altitude = rad_meter - geopotential_alt_meter;
if radius_minus_altitude == 0.0 {return Err(IsaError::ComputationError)}
Ok(uom::si::f64::Length::new::<uom::si::length::meter>((rad_meter * geopotential_alt_meter) / radius_minus_altitude))
}
pub fn altitude_geometric_to_geopotential(geometric_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Length, IsaError> {
let geometric_alt_meter = geometric_altitude.get::<uom::si::length::meter>();
if geometric_alt_meter < -5.0 || geometric_alt_meter > 80_000.0 { return Err(IsaError::InputOutOfRange); }
let rad_meter = Self::constant_earth_radius().get::<uom::si::length::meter>();
let radius_plus_altitude = rad_meter + geometric_alt_meter;
if radius_plus_altitude == 0.0 {return Err(IsaError::ComputationError)}
Ok(uom::si::f64::Length::new::<uom::si::length::meter>((rad_meter * geometric_alt_meter) / radius_plus_altitude))
}
pub
fn altitude_to_pressure (geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Pressure, IsaError> {
let t_base: f64 = Self::altitude_to_base_temperature(geopotential_altitude)?.get::<uom::si::thermodynamic_temperature::kelvin>(); let h: f64 = geopotential_altitude.get::<uom::si::length::kilometer>(); let h_base: f64 = Self::altitude_to_base_geopotential_altitude(geopotential_altitude)?.get::<uom::si::length::kilometer>(); let m: f64 = Self::constant_molar_mass_dry_air().get::<uom::si::molar_mass::gram_per_mole>(); let g: f64 = Self::constant_gravity_sealevel().get::<uom::si::acceleration::meter_per_second_squared>(); let r_star: f64 = Self::constant_universal_gas_constant(); let beta: f64 = Self::altitude_to_lapse_rate(geopotential_altitude)?;
let p_base: f64 = match h {
h if -5.0 <= h && h <= 0.0 => {
1.75364e0 * 101_325.0 },
h if 0.0 <= h && h <= 11.0 => {
101_325.0 },
_ => {
InternationalStandardAtmosphere::altitude_to_pressure(
uom::si::f64::Length::new::<uom::si::length::kilometer>(h_base)
)?.get::<uom::si::pressure::pascal>()
}
};
if h < -5.0 || h > 80.0 { return Err(IsaError::InputOutOfRange); }
let p = match beta {
beta if beta == 0.0 => {
p_base * (- ( (m * g * (h - h_base)) / (r_star * t_base) ) ).exp()
},
_ => {
p_base * (t_base / (t_base + (beta * (h - h_base) )) ).powf( (m * g) / (r_star * beta))
}
};
return Ok(uom::si::f64::Pressure::new::<uom::si::pressure::pascal>(p));
}
pub fn altitude_to_pressure_ratio(geopotential_altitude: uom::si::f64::Length) -> Result<f64, IsaError> {
let p = Self::altitude_to_pressure(geopotential_altitude)?.get::<uom::si::pressure::pascal>();
let p0 = Self::constant_sea_level_standard_pressure().get::<uom::si::pressure::pascal>();
if p0 == 0.0 {return Err(IsaError::ComputationError);}
Ok(p / p0)
}
pub fn altitude_to_density (geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::MassDensity, IsaError> {
let p = Self::altitude_to_pressure(geopotential_altitude)?.get::<uom::si::pressure::pascal>(); let t = Self::altitude_to_temperature(geopotential_altitude)?.get::<uom::si::thermodynamic_temperature::kelvin>(); let r_specific = Self::constant_specific_gas_constant_air();
let divisor = r_specific * t;
if divisor == 0.0 {return Err(IsaError::ComputationError);}
let rho = p / divisor;
Ok(uom::si::f64::MassDensity::new::<uom::si::mass_density::kilogram_per_cubic_meter>(rho))
}
pub fn altitude_to_density_ratio(geopotential_altitude: uom::si::f64::Length) -> Result<f64, IsaError> {
let rho = Self::altitude_to_density(geopotential_altitude)?.get::<uom::si::mass_density::kilogram_per_cubic_meter>();
let rho_0 = Self::altitude_to_density(uom::si::f64::Length::new::<uom::si::length::meter>(0.0))?.get::<uom::si::mass_density::kilogram_per_cubic_meter>();
if rho_0 == 0.0 {return Err(IsaError::ComputationError);}
Ok(rho / rho_0)
}
pub fn altitude_to_sqrt_density_ratio(geopotential_altitude: uom::si::f64::Length) -> Result<f64, IsaError> {
let density_ratio = Self::altitude_to_density_ratio(geopotential_altitude)?;
Ok(density_ratio.sqrt())
}
pub fn altitude_to_speed_of_sound(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Velocity, IsaError> {
let gamma: f64 = Self::constant_ratio_specific_heats_air(); let r_specific: f64 = Self::constant_specific_gas_constant_air(); let t: f64 = Self::altitude_to_temperature(geopotential_altitude)?.get::<uom::si::thermodynamic_temperature::kelvin>();
let a = (gamma * r_specific * t).sqrt();
Ok(uom::si::f64::Velocity::new::<uom::si::velocity::meter_per_second>(a))
}
pub fn altitude_to_dynamic_viscosity(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::DynamicViscosity, IsaError> {
let t: f64 = Self::altitude_to_temperature(geopotential_altitude)?.get::<uom::si::thermodynamic_temperature::kelvin>(); let beta_s: f64 = Self::constant_sutherland_beta_s(); let s: f64 = Self::constant_sutherland_s().get::<uom::si::thermodynamic_temperature::kelvin>();
let mu = (beta_s * t.powf(3.0/2.0)) / (t + s);
Ok(uom::si::f64::DynamicViscosity::new::<uom::si::dynamic_viscosity::pascal_second>(mu))
}
pub fn altitude_to_kinematic_viscosity(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::KinematicViscosity, IsaError> {
let mu = Self::altitude_to_dynamic_viscosity(geopotential_altitude)?.get::<uom::si::dynamic_viscosity::pascal_second>(); let rho = Self::altitude_to_density(geopotential_altitude)?.get::<uom::si::mass_density::kilogram_per_cubic_meter>();
if rho == 0.0 {return Err(IsaError::ComputationError);}
let nu = mu / rho;
Ok(uom::si::f64::KinematicViscosity::new::<uom::si::kinematic_viscosity::square_meter_per_second>(nu))
}
pub fn altitude_to_thermal_conductivity(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::ThermalConductivity, IsaError> {
let t: f64 = Self::altitude_to_temperature(geopotential_altitude)?.get::<uom::si::thermodynamic_temperature::kelvin>();
let dividend = 2.648_151e-3 * t.powf(3.0/2.0);
let divisor = t + (245.4 * 10.0_f64.powf(-(12.0/t)));
if divisor == 0.0 {return Err(IsaError::ComputationError);}
let lambda = dividend / divisor;
Ok(uom::si::f64::ThermalConductivity::new::<uom::si::thermal_conductivity::watt_per_meter_kelvin>(lambda))
}
pub fn altitude_to_number_density(geopotential_altitude: uom::si::f64::Length) -> Result<f64, IsaError> {
let p = Self::altitude_to_pressure(geopotential_altitude)?.get::<uom::si::pressure::pascal>(); let t = Self::altitude_to_temperature(geopotential_altitude)?.get::<uom::si::thermodynamic_temperature::kelvin>(); let r_star = Self::constant_universal_gas_constant(); let n_a = Self::constant_avogadro_number();
let divisor = r_star * t;
if divisor == 0.0 {return Err(IsaError::ComputationError);}
let n = (p * n_a) / divisor;
Ok(n)
}
pub fn altitude_to_mean_particle_speed(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Velocity, IsaError> {
let t: f64 = Self::altitude_to_temperature(geopotential_altitude)?.get::<uom::si::thermodynamic_temperature::kelvin>(); let r: f64 = Self::constant_specific_gas_constant_air();
let constant = 1.595_769;
let v_bar = constant * (r * t).sqrt();
Ok(uom::si::f64::Velocity::new::<uom::si::velocity::meter_per_second>(v_bar))
}
pub fn altitude_to_mean_free_path(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Length, IsaError> {
let pi = std::f64::consts::PI;
let sigma = Self::constant_effective_collision_diameter_air_molecule().get::<uom::si::length::meter>(); let n = Self::altitude_to_number_density(geopotential_altitude)?;
let divisor = (2.0_f64).sqrt() * pi * sigma.powf(2.0_f64) * n;
if divisor == 0.0 { return Err(IsaError::ComputationError)}
Ok(uom::si::f64::Length::new::<uom::si::length::meter>(1.0_f64 / divisor))
}
pub fn altitude_to_collision_frequency(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Frequency, IsaError> {
let v_bar = Self::altitude_to_mean_particle_speed(geopotential_altitude)?.get::<uom::si::velocity::meter_per_second>(); let l = Self::altitude_to_mean_free_path(geopotential_altitude)?.get::<uom::si::length::meter>();
if l == 0.0 {return Err(IsaError::ComputationError);}
let omega = v_bar / l;
Ok(uom::si::f64::Frequency::new::<uom::si::frequency::hertz>(omega))
}
pub fn altitude_to_gravitational_acceleration(geopotential_altitude: uom::si::f64::Length) -> Result<uom::si::f64::Acceleration, IsaError> {
let h = geopotential_altitude.get::<uom::si::length::meter>();
if h < -5_000.0 || h > 80_000.0 { return Err(IsaError::InputOutOfRange); }
let g0 = Self::constant_gravity_sealevel().get::<uom::si::acceleration::meter_per_second_squared>(); let r = Self::constant_earth_radius().get::<uom::si::length::meter>();
let divisor = r + h;
if divisor == 0.0 {return Err(IsaError::ComputationError);}
let g = g0 * (r / divisor).powf(2.0);
Ok(uom::si::f64::Acceleration::new::<uom::si::acceleration::meter_per_second_squared>(g))
}
pub fn altitude_to_specific_weight(geopotential_altitude: uom::si::f64::Length) -> Result<f64, IsaError> {
let rho = Self::altitude_to_density(geopotential_altitude)?.get::<uom::si::mass_density::kilogram_per_cubic_meter>(); let g = Self::altitude_to_gravitational_acceleration(geopotential_altitude)?.get::<uom::si::acceleration::meter_per_second_squared>();
let specific_weight = rho * g;
Ok(specific_weight)
}
pub fn altitude_from_pressure (pressure: uom::si::f64::Pressure) -> Result<uom::si::f64::Length, IsaError> {
let p_pa: f64 = pressure.get::<uom::si::pressure::pascal>();
let p_0: f64 = 101325.0;
let h_b_and_p_b = vec![
(-5.0, 1.75364e0 * p_0),
(0.0, p_0),
(11.0, 2.23361e-1*p_0),
(20.0, 5.40328e-2*p_0),
(32.0, 8.56664e-3*p_0),
(47.0, 1.09455e-3*p_0),
(51.0, 6.60631e-4*p_0),
(71.0, 3.90465e-5*p_0),
(80.0, 8.74682e-6*p_0),
];
if p_pa > h_b_and_p_b[0].1 || p_pa < h_b_and_p_b[h_b_and_p_b.len()-1].1 { return Err(IsaError::InputOutOfRange) }
let mut h_b: f64 = -10.0; let mut p_b: f64 = -10.0;
for layer_index in 0..h_b_and_p_b.len() - 1 {
if p_pa <= h_b_and_p_b[layer_index].1 && p_pa > h_b_and_p_b[layer_index+1].1 {
(h_b, p_b) = (h_b_and_p_b[layer_index].0, h_b_and_p_b[layer_index].1);
break;
}
}
let r_star: f64 = Self::constant_universal_gas_constant(); let m: f64 = Self::constant_molar_mass_dry_air().get::<uom::si::molar_mass::gram_per_mole>(); let g: f64 = Self::constant_gravity_sealevel().get::<uom::si::acceleration::meter_per_second_squared>(); let h_b_false: f64 = h_b + 1.0;
let t_b: f64 = Self::altitude_to_base_temperature(uom::si::f64::Length::new::<uom::si::length::kilometer>(h_b_false))?.get::<uom::si::thermodynamic_temperature::kelvin>(); let beta = Self::altitude_to_lapse_rate(uom::si::f64::Length::new::<uom::si::length::kilometer>(h_b_false)).unwrap();
if beta == 0.0 {
let h = h_b + (- (r_star * t_b * (p_pa / p_b).ln())) / (m * g);
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(h)) } else {
let h = h_b + (t_b / (beta * (p_pa/p_b).powf((r_star*beta)/(m*g)))) - (t_b / beta);
Ok(uom::si::f64::Length::new::<uom::si::length::kilometer>(h)) }
}
pub fn altitude_from_density_and_temperature (rho: uom::si::f64::MassDensity, t: uom::si::f64::ThermodynamicTemperature) -> Result<uom::si::f64::Length, IsaError> {
let r = Self::constant_specific_gas_constant_air();
let rho = rho.get::<uom::si::mass_density::kilogram_per_cubic_meter>();
let t = t.get::<uom::si::thermodynamic_temperature::kelvin>();
let p = uom::si::f64::Pressure::new::<uom::si::pressure::pascal>(rho * r * t);
let alt = Self::altitude_from_pressure(p)?;
Ok(alt)
}
pub fn constant_earth_radius() -> uom::si::f64::Length {
return uom::si::f64::Length::new::<uom::si::length::meter>(6_356_766.0);
}
pub fn constant_gravity_sealevel() -> uom::si::f64::Acceleration {
uom::si::f64::Acceleration::new::<uom::si::acceleration::meter_per_second_squared>(9.806_65)
}
pub fn constant_molar_mass_dry_air() -> uom::si::f64::MolarMass {
uom::si::f64::MolarMass::new::<uom::si::molar_mass::gram_per_mole>(28_964.420)
}
pub fn constant_avogadro_number() -> f64 {
602.257e24 }
pub fn constant_specific_gas_constant_air() -> f64 {
287.052_870 }
pub fn constant_universal_gas_constant() -> f64 {
8_314.32 }
pub fn constant_sutherland_s() -> uom::si::f64::ThermodynamicTemperature {
uom::si::f64::ThermodynamicTemperature::new::<uom::si::thermodynamic_temperature::kelvin>(110.4)
}
pub fn constant_sutherland_beta_s() -> f64 {
1.458e-6 }
pub fn constant_ratio_specific_heats_air() -> f64 {
1.4
}
pub fn constant_effective_collision_diameter_air_molecule() -> uom::si::f64::Length {
uom::si::f64::Length::new::<uom::si::length::meter>(0.365e-9)
}
pub fn constant_sea_level_standard_pressure() -> uom::si::f64::Pressure {
uom::si::f64::Pressure::new::<uom::si::pressure::pascal>(101_325.0)
}
}