apsis-rs/crates/rf/apsis-atmosphere/src/p835.rs

149 lines
5.7 KiB
Rust

//! The reference standard atmosphere of ITU-R Recommendation P.835.
//!
//! The exact slant path method of Recommendation P.676 integrates the
//! specific attenuation through a stack of layers, so it needs the
//! temperature, pressure, and water vapour density at each layer height.
//! This module supplies those from the mean annual global reference
//! atmosphere of Recommendation P.835.
//!
//! Height is measured as geopotential height up to the base of the
//! thermosphere. The geopotential height is the geometric height adjusted
//! for the variation of gravity with altitude, and it is the independent
//! variable the temperature and pressure profiles are expressed in.
//!
//! Source: ITU-R Recommendation P.835-6, *Reference Standard Atmospheres*,
//! section 1, the mean annual global reference atmosphere.
/// The radius of the Earth used in the geopotential conversion, in
/// kilometers.
///
/// The recommendation fixes this value for the standard atmosphere, which is
/// slightly different from the polar radius used elsewhere. It is the value
/// that makes the geopotential height consistent with the tabulated
/// profiles.
const GEOPOTENTIAL_EARTH_RADIUS_KM: f64 = 6_356.766;
/// Returns the geopotential height for a geometric height, in kilometers.
///
/// The relation is
///
/// ```text
/// h_p = R H / (R + H)
/// ```
///
/// with `R` the fixed Earth radius of the standard atmosphere and `H` the
/// geometric height. The geopotential height is always the smaller of the
/// two.
pub fn geopotential_height(geometric_height_km: f64) -> f64 {
GEOPOTENTIAL_EARTH_RADIUS_KM * geometric_height_km
/ (GEOPOTENTIAL_EARTH_RADIUS_KM + geometric_height_km)
}
/// Returns the temperature of the reference atmosphere, in kelvin.
///
/// The profile is piecewise linear in geopotential height up to 84.852 km
/// and is then defined by two further segments that extend to 100 km. Above
/// 100 km the recommendation does not define a global profile and this
/// function returns the value at 100 km.
///
/// The geometric height is in kilometers.
pub fn standard_temperature(geometric_height_km: f64) -> f64 {
let height = geopotential_height(geometric_height_km);
if height <= 11.0 {
288.15 - 6.5 * height
} else if height <= 20.0 {
216.65
} else if height <= 32.0 {
216.65 + (height - 20.0)
} else if height <= 47.0 {
228.65 + 2.8 * (height - 32.0)
} else if height <= 51.0 {
270.65
} else if height <= 71.0 {
270.65 - 2.8 * (height - 51.0)
} else if height <= 84.852 {
214.65 - 2.0 * (height - 71.0)
} else if (86.0..=91.0).contains(&geometric_height_km) {
186.867_3
} else if geometric_height_km > 91.0 && geometric_height_km <= 100.0 {
let ratio = (geometric_height_km - 91.0) / 19.942_9;
263.190_5 - 76.323_2 * (1.0 - ratio * ratio).max(0.0).sqrt()
} else {
195.081_34
}
}
/// Returns the pressure of the reference atmosphere, in hectopascals.
///
/// The profile is piecewise, continuous at the segment boundaries, and
/// follows from the hydrostatic relation applied to each temperature
/// segment.
///
/// The geometric height is in kilometers.
pub fn standard_pressure(geometric_height_km: f64) -> f64 {
let height = geopotential_height(geometric_height_km);
if height <= 11.0 {
1_013.25 * (288.15 / (288.15 - 6.5 * height)).powf(-34.163_2 / 6.5)
} else if height <= 20.0 {
226.322_6 * (-34.163_2 * (height - 11.0) / 216.65).exp()
} else if height <= 32.0 {
54.749_80 * (216.65 / (216.65 + (height - 20.0))).powf(34.163_2)
} else if height <= 47.0 {
8.680_422 * (228.65 / (228.65 + 2.8 * (height - 32.0))).powf(34.163_2 / 2.8)
} else if height <= 51.0 {
1.109_106 * (-34.163_2 * (height - 47.0) / 270.65).exp()
} else if height <= 71.0 {
0.669_416_7 * (270.65 / (270.65 - 2.8 * (height - 51.0))).powf(-34.163_2 / 2.8)
} else if height <= 84.852 {
0.039_566_49 * (214.65 / (214.65 - 2.0 * (height - 71.0))).powf(-34.163_2 / 2.0)
} else if (86.0..=100.0).contains(&geometric_height_km) {
let h = geometric_height_km;
(95.571_899 - 4.011_801 * h + 6.424_731e-2 * h * h - 4.789_660e-4 * h * h * h
+ 1.340_543e-6 * h * h * h * h)
.exp()
} else {
1.0e-62
}
}
/// Returns the water vapour density of the reference atmosphere, in grams
/// per cubic meter.
///
/// The profile is exponential with a scale height of two kilometers, scaled
/// so that the density at the surface is the value given.
///
/// The geometric height is in kilometers.
pub fn standard_water_vapour_density(geometric_height_km: f64, surface_density: f64) -> f64 {
surface_density * (-geometric_height_km / 2.0).exp()
}
/// Returns the radio refractive index of moist air, dimensionless.
///
/// The relation is
///
/// ```text
/// N = 77.6 P_d / T + 72 e / T + 3.75e5 e / T^2
/// n = 1 + N 1e-6
/// ```
///
/// with `P_d` the dry air pressure and `e` the water vapour partial
/// pressure, both in hectopascals, and `T` the temperature in kelvin. The
/// first two terms are the non-dispersive refractivity and the third is the
/// dipole term that gives water vapour its strong effect.
///
/// Source: ITU-R Recommendation P.453, which gives this expression for the
/// refractive index of moist air.
pub fn radio_refractive_index(
dry_pressure_hpa: f64,
vapour_pressure_hpa: f64,
temperature_k: f64,
) -> f64 {
if temperature_k == 0.0 {
return 1.0;
}
let refractivity = 77.6 * dry_pressure_hpa / temperature_k
+ 72.0 * vapour_pressure_hpa / temperature_k
+ 3.75e5 * vapour_pressure_hpa / (temperature_k * temperature_k);
1.0 + refractivity * 1.0e-6
}