pub(crate) const K_IONO: f64 = 40.3e16;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct PiercePoint {
pub s: f64,
pub psi: f64,
pub phi_ipp_deg: f64,
pub lambda_ipp_deg: f64,
}
pub(crate) fn pierce_point(
lat_rad: f64,
lon_rad: f64,
az_rad: f64,
el_rad: f64,
re_km: f64,
h_km: f64,
) -> PiercePoint {
use core::f64::consts::PI;
let r2d = 180.0 / PI;
let s = re_km / (re_km + h_km) * el_rad.cos();
let psi = PI / 2.0 - el_rad - s.asin();
let phi_ipp = (lat_rad.sin() * psi.cos() + lat_rad.cos() * psi.sin() * az_rad.cos()).asin();
let lambda_ipp = lon_rad + (psi.sin() * az_rad.sin() / phi_ipp.cos()).asin();
PiercePoint {
s,
psi,
phi_ipp_deg: phi_ipp * r2d,
lambda_ipp_deg: lambda_ipp * r2d,
}
}
fn normalize_lon_deg(mut lon_deg: f64, lon1: f64, lon2: f64) -> f64 {
while lon_deg < lon1 {
lon_deg += 360.0;
}
while lon_deg > lon2 {
lon_deg -= 360.0;
}
lon_deg
}
fn bracket(value: f64, v1: f64, step: f64, n: usize) -> usize {
let idx = ((value - v1) / step) as i64;
if idx < 0 {
0
} else if idx > (n as i64) - 2 {
n - 2
} else {
idx as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct BilinearVtec {
pub vtec: f64,
pub p: f64,
pub q: f64,
}
pub(crate) fn bilinear_vtec(
vtec_map: &[Vec<f64>],
lat_arr: &[f64],
lon_arr: &[f64],
dlat: f64,
dlon: f64,
phi_deg: f64,
lam_deg: f64,
) -> BilinearVtec {
let nlat = lat_arr.len();
let nlon = lon_arr.len();
let lat_hi = lat_arr[0];
let lat_lo = lat_arr[nlat - 1];
let mut phi = phi_deg;
if phi > lat_hi {
phi = lat_hi;
}
if phi < lat_lo {
phi = lat_lo;
}
let i = bracket(phi, lat_arr[0], dlat, nlat);
let j = bracket(lam_deg, lon_arr[0], dlon, nlon);
let lat0 = lat_arr[i];
let lon0 = lon_arr[j];
let q = (phi - lat0) / dlat;
let p = (lam_deg - lon0) / dlon;
let e00 = vtec_map[i][j];
let e01 = vtec_map[i][j + 1];
let e10 = vtec_map[i + 1][j];
let e11 = vtec_map[i + 1][j + 1];
let one_p = 1.0 - p;
let one_q = 1.0 - q;
let vtec = one_p * one_q * e00 + p * one_q * e01 + one_p * q * e10 + p * q * e11;
BilinearVtec { vtec, p, q }
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct SlantComponents {
pub s: f64,
pub psi: f64,
pub phi_ipp_deg: f64,
pub lambda_ipp_deg_raw: f64,
pub lambda_ipp_deg: f64,
pub map_index: usize,
pub w: f64,
pub vtec0: f64,
pub vtec1: f64,
pub p0: f64,
pub q0: f64,
pub vtec: f64,
pub m: f64,
pub stec: f64,
pub delay_m: f64,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn slant_delay_components(
lat_rad: f64,
lon_rad: f64,
az_rad: f64,
el_rad: f64,
frequency_hz: f64,
re_km: f64,
h_km: f64,
epoch_s: i64,
map_epochs_s: &[i64],
maps: &[Vec<Vec<f64>>],
lat_arr: &[f64],
lon_arr: &[f64],
dlat: f64,
dlon: f64,
) -> SlantComponents {
let geom = pierce_point(lat_rad, lon_rad, az_rad, el_rad, re_km, h_km);
let s = geom.s;
let lon1 = lon_arr[0];
let lon2 = lon_arr[lon_arr.len() - 1];
let lam_deg = normalize_lon_deg(geom.lambda_ipp_deg, lon1, lon2);
let phi_deg = geom.phi_ipp_deg;
let nmaps = map_epochs_s.len();
let (ti, ti1, w) = if nmaps <= 1 {
(0usize, 0usize, 0.0)
} else {
let mut ti = 0usize;
while ti < nmaps - 2 && epoch_s >= map_epochs_s[ti + 1] {
ti += 1;
}
let t0 = map_epochs_s[ti];
let t1 = map_epochs_s[ti + 1];
let mut w = (epoch_s as f64 - t0 as f64) / (t1 as f64 - t0 as f64);
#[allow(clippy::manual_clamp)]
if w < 0.0 {
w = 0.0;
}
if w > 1.0 {
w = 1.0;
}
(ti, ti + 1, w)
};
let b0 = bilinear_vtec(&maps[ti], lat_arr, lon_arr, dlat, dlon, phi_deg, lam_deg);
let b1 = bilinear_vtec(&maps[ti1], lat_arr, lon_arr, dlat, dlon, phi_deg, lam_deg);
let vtec0 = b0.vtec;
let vtec1 = b1.vtec;
let vtec = (1.0 - w) * vtec0 + w * vtec1;
let m = 1.0 / (1.0 - s * s).sqrt();
let stec = m * vtec;
let delay_m = (K_IONO / (frequency_hz * frequency_hz)) * stec;
SlantComponents {
s,
psi: geom.psi,
phi_ipp_deg: phi_deg,
lambda_ipp_deg_raw: geom.lambda_ipp_deg,
lambda_ipp_deg: lam_deg,
map_index: ti,
w,
vtec0,
vtec1,
p0: b0.p,
q0: b0.q,
vtec,
m,
stec,
delay_m,
}
}