From 1ff6c261f119c2325648698ea79389026351a1b8 Mon Sep 17 00:00:00 2001 From: ApsisRS contributors Date: Sat, 26 Sep 2026 15:35:20 +0800 Subject: [PATCH] rf: derive the rain height from P.839 in the path method --- crates/rf/apsis-atmosphere/src/lib.rs | 2 +- crates/rf/apsis-atmosphere/src/rain.rs | 46 +++++++++++++++++++++--- crates/rf/apsis-atmosphere/tests/rain.rs | 41 +++++++++++++++++++++ 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/crates/rf/apsis-atmosphere/src/lib.rs b/crates/rf/apsis-atmosphere/src/lib.rs index 457d968..03e5f10 100644 --- a/crates/rf/apsis-atmosphere/src/lib.rs +++ b/crates/rf/apsis-atmosphere/src/lib.rs @@ -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 rain::{ 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; diff --git a/crates/rf/apsis-atmosphere/src/rain.rs b/crates/rf/apsis-atmosphere/src/rain.rs index 486752b..8c1e340 100644 --- a/crates/rf/apsis-atmosphere/src/rain.rs +++ b/crates/rf/apsis-atmosphere/src/rain.rs @@ -31,12 +31,12 @@ use apsis_rf::Decibel; /// which makes the Earth appear larger than it is. 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, -/// which depends on latitude and season and is tabulated separately. This -/// value is representative of mid latitudes; a caller working in the tropics -/// should raise it and one in polar regions should lower it. +/// Recommendation P.618 takes the rain height from the zero degree isotherm +/// height of Recommendation P.839, which is tabulated as a function of +/// latitude and longitude. Prefer [`rain_attenuation_at`], which looks the +/// value up, over [`rain_attenuation`] with this constant. pub const DEFAULT_RAIN_HEIGHT_KM: f64 = 3.0; /// 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 { 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 { + let rain_height_km = crate::p839::rain_height(latitude, longitude)?; + rain_attenuation( + rain_rate, + frequency, + polarisation, + elevation, + station_height_km, + rain_height_km, + latitude, + ) +} diff --git a/crates/rf/apsis-atmosphere/tests/rain.rs b/crates/rf/apsis-atmosphere/tests/rain.rs index 62ff8f8..7f7b769 100644 --- a/crates/rf/apsis-atmosphere/tests/rain.rs +++ b/crates/rf/apsis-atmosphere/tests/rain.rs @@ -393,3 +393,44 @@ fn polarisation_tilt_converts_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 + ); +}