mod saastamoinen;
mod zwd;
use astrodynamics::time::model::{Instant, InstantRepr};
use crate::frame::Wgs84Geodetic;
pub(crate) use saastamoinen::slant_components;
pub use zwd::{
tropo_delay_xyz as tropo_zwd_delay_xyz, zenith_wet_delay as zwd_zenith_wet_delay,
AltitudeClamp, ZwdEpoch, ZwdProfile, ZwdSlantOptions,
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Met {
pub pressure_hpa: f64,
pub temperature_k: f64,
pub relative_humidity: f64,
}
impl Met {
pub const fn new(pressure_hpa: f64, temperature_k: f64, relative_humidity: f64) -> Self {
Self {
pressure_hpa,
temperature_k,
relative_humidity,
}
}
pub fn standard(height_m: f64, relative_humidity: f64) -> Self {
let s = saastamoinen::standard_atmosphere(height_m, relative_humidity);
Self {
pressure_hpa: s.pressure_hpa,
temperature_k: s.temperature_k,
relative_humidity: s.relative_humidity,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TropoModel {
Saastamoinen,
ZwdAltitudeScaled(ZwdProfile),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MappingModel {
Niell,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ZenithDelay {
pub dry_m: f64,
pub wet_m: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MappingFactors {
pub dry: f64,
pub wet: f64,
}
pub fn tropo_zenith(model: TropoModel, receiver: Wgs84Geodetic, met: Met) -> ZenithDelay {
match model {
TropoModel::Saastamoinen => {
let z = saastamoinen::zenith_delays(
met.pressure_hpa,
met.temperature_k,
met.relative_humidity,
receiver.lat_rad,
receiver.height_m,
);
ZenithDelay {
dry_m: z.zhd_m,
wet_m: z.zwd_m,
}
}
TropoModel::ZwdAltitudeScaled(profile) => {
let z = saastamoinen::zenith_delays(
met.pressure_hpa,
met.temperature_k,
met.relative_humidity,
receiver.lat_rad,
receiver.height_m,
);
ZenithDelay {
dry_m: z.zhd_m,
wet_m: zwd::zenith_wet_delay(profile, receiver.height_m),
}
}
}
}
pub fn tropo_mapping(
model: MappingModel,
elevation_rad: f64,
receiver: Wgs84Geodetic,
epoch: Instant,
) -> MappingFactors {
match model {
MappingModel::Niell => {
let doy = fractional_day_of_year(epoch);
let m = saastamoinen::niell_mapping(
elevation_rad,
receiver.lat_rad,
receiver.height_m,
doy,
);
MappingFactors {
dry: m.mh,
wet: m.mw,
}
}
}
}
pub fn tropo_slant(elevation_rad: f64, receiver: Wgs84Geodetic, met: Met, epoch: Instant) -> f64 {
let doy = fractional_day_of_year(epoch);
slant_components(
elevation_rad,
receiver.lat_rad,
receiver.lon_rad,
receiver.height_m,
met.pressure_hpa,
met.temperature_k,
met.relative_humidity,
doy,
)
.slant_m
}
fn fractional_day_of_year(epoch: Instant) -> f64 {
let jd = match epoch.repr {
InstantRepr::JulianDate(split) => split.jd_whole + split.fraction,
InstantRepr::Nanos(nanos) => {
(nanos as f64) / 1.0e9 / 86_400.0 + 2_451_545.0
}
};
let jd_midnight = jd + 0.5;
let day_floor = jd_midnight.floor();
let day_fraction = jd_midnight - day_floor;
let (year, month, day) = civil_from_jd_floor(day_floor);
let doy_integer = day_of_year(year, month, day);
doy_integer as f64 + day_fraction
}
fn civil_from_jd_floor(day_floor: f64) -> (i64, i64, i64) {
let jdn = day_floor as i64;
let l = jdn + 68_569;
let n = (4 * l) / 146_097;
let l = l - (146_097 * n + 3) / 4;
let i = (4_000 * (l + 1)) / 1_461_001;
let l = l - (1_461 * i) / 4 + 31;
let j = (80 * l) / 2_447;
let day = l - (2_447 * j) / 80;
let l = j / 11;
let month = j + 2 - 12 * l;
let year = 100 * (n - 49) + i + l;
(year, month, day)
}
fn day_of_year(year: i64, month: i64, day: i64) -> i64 {
const CUM: [i64; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
let leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
let mut doy = CUM[(month - 1) as usize] + day;
if leap && month > 2 {
doy += 1;
}
doy
}
#[cfg(test)]
mod tests;