use crate::astro::dynamics::{Position, Velocity};
use crate::coordinates::frames::GCRS;
use crate::time::JulianDate;
use qtty::Meter;
use super::error::PodObservationsError;
use super::obs_trait::{CartesianState, ObsType, Observation};
use crate::pod::observation::provider_bundle::ProviderBundle;
const C_M_S: f64 = 299_792_458.0;
const GM_M3_S2: f64 = 3.986_004_418e14;
fn marini_murray_f_lambda(lambda_um: f64) -> f64 {
0.9650 + 0.0164 / (lambda_um * lambda_um) + 0.000_228 / (lambda_um.powi(4))
}
fn marini_murray_one_way_m(elevation_rad: f64, lambda_um: f64) -> f64 {
let e = elevation_rad.max(3.0_f64.to_radians());
let sin_e = e.sin();
let tan_e = e.tan();
let f_lambda = marini_murray_f_lambda(lambda_um);
let a = 0.002_277 * 1013.25;
let b = 0.0014;
let c = 0.045;
f_lambda * a / (sin_e + b / (tan_e + c))
}
fn shapiro_slr_m(r_rx_km: f64, r_sat_km: f64, rho_km: f64) -> f64 {
let two_gm_c2 = 2.0 * GM_M3_S2 / (C_M_S * C_M_S); let numer = r_rx_km * 1_000.0 + r_sat_km * 1_000.0 + rho_km * 1_000.0;
let denom = r_rx_km * 1_000.0 + r_sat_km * 1_000.0 - rho_km * 1_000.0;
if denom > 1.0 {
2.0 * two_gm_c2 * (numer / denom).ln()
} else {
0.0
}
}
#[derive(Debug, Clone)]
pub struct SlrNormalPointObs {
pub station_id: String,
pub epoch: JulianDate,
pub measured_m: Meter,
pub sigma: Meter,
pub station_gcrs_km: Position<GCRS>,
pub sat_pos_gcrs_km: Position<GCRS>,
pub sat_vel_gcrs_km_s: Velocity<GCRS>,
pub elevation_rad: f64,
pub wavelength_um: f64,
}
impl Observation for SlrNormalPointObs {
type Residual = f64;
fn residual(
&self,
_state: &CartesianState,
_providers: &dyn ProviderBundle,
) -> Result<f64, PodObservationsError> {
let r_st = self.station_gcrs_km;
let r_sat = self.sat_pos_gcrs_km;
let v_sat = self.sat_vel_gcrs_km_s;
let los = r_st - r_sat;
let rho_km = los.magnitude().value();
let dt_s = rho_km / (C_M_S / 1_000.0); let sat_pos_refined = Position::<GCRS>::new(
r_sat.x().value() - v_sat.x().value() * dt_s,
r_sat.y().value() - v_sat.y().value() * dt_s,
r_sat.z().value() - v_sat.z().value() * dt_s,
);
let los2 = r_st - sat_pos_refined;
let rho_km = los2.magnitude().value();
let rho_m = rho_km * 1_000.0;
let two_way_m = 2.0 * rho_m;
let trop_m = 2.0 * marini_murray_one_way_m(self.elevation_rad, self.wavelength_um);
let r_rx_km = {
let x = r_st.x().value();
let y = r_st.y().value();
let z = r_st.z().value();
(x * x + y * y + z * z).sqrt()
};
let r_sat_km = {
let x = sat_pos_refined.x().value();
let y = sat_pos_refined.y().value();
let z = sat_pos_refined.z().value();
(x * x + y * y + z * z).sqrt()
};
let shapiro_m = shapiro_slr_m(r_rx_km, r_sat_km, rho_km);
let modelled = two_way_m + trop_m + shapiro_m;
Ok(self.measured_m.value() - modelled)
}
fn obs_type(&self) -> ObsType {
ObsType::SlrNormalPoint
}
fn epoch(&self) -> JulianDate {
self.epoch
}
fn sigma(&self) -> Meter {
self.sigma
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::astro::dynamics::{Position, Velocity};
use crate::pod::observation::provider_bundle::NullProviderBundle;
use approx::assert_abs_diff_eq;
use qtty::Meter;
const EPOCH: fn() -> JulianDate = || JulianDate::new(2_451_545.0);
fn make_state(pos: Position<GCRS>) -> CartesianState {
CartesianState::new(
EPOCH().to_j2000s(),
pos,
Velocity::<GCRS>::new(0.0, 7.5, 0.0),
)
}
#[test]
fn two_way_range_exact_for_consistent_obs() {
let sat_pos = Position::<GCRS>::new(7_000.0, 0.0, 0.0);
let sta_pos = Position::<GCRS>::new(6_378.0, 0.0, 0.0);
let state = make_state(sat_pos);
let obs_probe = SlrNormalPointObs {
station_id: "7840".to_string(),
epoch: EPOCH(),
measured_m: Meter::new(0.0),
sigma: Meter::new(0.02),
station_gcrs_km: sta_pos,
sat_pos_gcrs_km: sat_pos,
sat_vel_gcrs_km_s: Velocity::<GCRS>::new(0.0, 7.5, 0.0),
elevation_rad: std::f64::consts::FRAC_PI_2,
wavelength_um: 0.532,
};
let neg_res = obs_probe.residual(&state, &NullProviderBundle).unwrap();
let modelled = -neg_res;
let obs = SlrNormalPointObs {
measured_m: Meter::new(modelled),
..obs_probe
};
let r = obs.residual(&state, &NullProviderBundle).unwrap();
assert_abs_diff_eq!(r, 0.0, epsilon = 1e-6);
}
#[test]
fn zenith_trop_delay_near_5m_two_way() {
let d = 2.0 * marini_murray_one_way_m(std::f64::consts::FRAC_PI_2, 0.532);
assert!(d > 4.0 && d < 6.0, "two-way zenith trop = {d:.3} m");
}
#[test]
fn marini_murray_wavelength_factor_532nm() {
let f = marini_murray_f_lambda(0.532);
assert!(f > 1.02 && f < 1.04, "f(532 nm) = {f:.4}");
}
#[test]
fn shapiro_delay_positive_and_small() {
let d = shapiro_slr_m(6_378.0, 7_000.0, 622.0);
assert!(d > 0.0 && d < 0.1, "Shapiro = {d:.6} m");
}
}