rf: derive the rain height from P.839 in the path method

This commit is contained in:
ApsisRS contributors 2026-09-26 15:35:20 +08:00
parent b298a00651
commit a0730cfb33
3 changed files with 83 additions and 6 deletions

View File

@ -65,7 +65,7 @@ pub use geometry::{air_mass, elevation_from_zenith, zenith_to_elevation};
pub use p839::{RAIN_HEIGHT_OFFSET_KM, grid_step_degrees, rain_height, zero_isotherm_height}; pub use p839::{RAIN_HEIGHT_OFFSET_KM, grid_step_degrees, rain_height, zero_isotherm_height};
pub use rain::{ pub use rain::{
DEFAULT_RAIN_HEIGHT_KM, RainAttenuation, polarisation_tilt, rain_attenuation, DEFAULT_RAIN_HEIGHT_KM, RainAttenuation, polarisation_tilt, rain_attenuation,
rain_coefficients, specific_rain_attenuation, rain_attenuation_at, rain_coefficients, specific_rain_attenuation,
}; };
pub use apsis_rf::Decibel; pub use apsis_rf::Decibel;

View File

@ -31,12 +31,12 @@ use apsis_rf::Decibel;
/// which makes the Earth appear larger than it is. /// which makes the Earth appear larger than it is.
const EFFECTIVE_EARTH_RADIUS_KM: f64 = 8_500.0; const EFFECTIVE_EARTH_RADIUS_KM: f64 = 8_500.0;
/// The default height of the rain layer above sea level, in kilometers. /// A mid latitude rain height, in kilometers above sea level.
/// ///
/// Recommendation P.618 takes this from the zero degree isotherm height, /// Recommendation P.618 takes the rain height from the zero degree isotherm
/// which depends on latitude and season and is tabulated separately. This /// height of Recommendation P.839, which is tabulated as a function of
/// value is representative of mid latitudes; a caller working in the tropics /// latitude and longitude. Prefer [`rain_attenuation_at`], which looks the
/// should raise it and one in polar regions should lower it. /// value up, over [`rain_attenuation`] with this constant.
pub const DEFAULT_RAIN_HEIGHT_KM: f64 = 3.0; pub const DEFAULT_RAIN_HEIGHT_KM: f64 = 3.0;
/// One term of a P.838-3 curve fit. /// One term of a P.838-3 curve fit.
@ -435,3 +435,39 @@ pub fn rain_attenuation(
pub const fn polarisation_tilt(degrees_from_horizontal: f64) -> f64 { pub const fn polarisation_tilt(degrees_from_horizontal: f64) -> f64 {
degrees_from_horizontal * core::f64::consts::PI / 180.0 degrees_from_horizontal * core::f64::consts::PI / 180.0
} }
/// Returns the total rain attenuation along a slant path, taking the rain
/// height from the Recommendation P.839 model.
///
/// This is the same calculation as [`rain_attenuation`], with the rain layer
/// height looked up from the tabulated zero degree isotherm height at the
/// given latitude and longitude rather than supplied by the caller. It is
/// the form to use unless a locally measured isotherm height is available.
///
/// The station height must be given; pass zero for a station at sea level.
///
/// Returns `None` when the coordinates are not usable, or when any of the
/// other arguments is rejected by [`rain_attenuation`].
///
/// Source: the rain height is from ITU-R Recommendation P.839-4 and the path
/// method from ITU-R Recommendation P.618-13.
pub fn rain_attenuation_at(
rain_rate: f64,
frequency: f64,
polarisation: f64,
elevation: f64,
station_height_km: f64,
latitude: f64,
longitude: f64,
) -> Option<RainAttenuation> {
let rain_height_km = crate::p839::rain_height(latitude, longitude)?;
rain_attenuation(
rain_rate,
frequency,
polarisation,
elevation,
station_height_km,
rain_height_km,
latitude,
)
}

View File

@ -393,3 +393,44 @@ fn polarisation_tilt_converts_degrees() {
"forty five degrees", "forty five degrees",
); );
} }
#[test]
fn the_integrated_path_uses_the_p839_rain_height() {
use apsis_atmosphere::{rain_attenuation_at, rain_height};
let latitude = 45.0;
let longitude = 0.0;
let integrated = rain_attenuation_at(
20.0,
12.0e9,
HORIZONTAL,
THIRTY_DEGREES,
0.0,
latitude,
longitude,
)
.expect("valid");
let explicit = rain_attenuation(
20.0,
12.0e9,
HORIZONTAL,
THIRTY_DEGREES,
0.0,
rain_height(latitude, longitude).expect("valid coordinates"),
latitude,
)
.expect("valid");
assert!((integrated.attenuation_db - explicit.attenuation_db).abs() < 1e-12);
}
#[test]
fn the_integrated_path_rejects_unusable_coordinates() {
use apsis_atmosphere::rain_attenuation_at;
assert_eq!(
rain_attenuation_at(20.0, 12.0e9, HORIZONTAL, THIRTY_DEGREES, 0.0, 91.0, 0.0),
None
);
assert_eq!(
rain_attenuation_at(20.0, 12.0e9, HORIZONTAL, THIRTY_DEGREES, 0.0, 0.0, f64::NAN),
None
);
}