#[cfg(feature = "no_std")]
use alloc::vec;
#[cfg(feature = "no_std")]
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MDist {
nb: usize,
es: f64,
big_e: f64,
b: Vec<f64>,
}
pub fn proj_mdist_ini(es: f64) -> MDist {
const MAX_ITER: usize = 20;
let mut e_series = [0.0_f64; MAX_ITER];
e_series[0] = 1.0;
let mut ens = es;
let mut numf = 1.0_f64;
let mut twon1 = 1.0_f64;
let mut denfi = 1.0_f64;
let mut denf = 1.0_f64;
let mut twon = 4.0_f64;
let mut es_acc = 1.0_f64; let mut el = 1.0_f64; let mut last_i = MAX_ITER;
let mut i = 1usize;
while i < MAX_ITER {
numf *= twon1 * twon1;
let den = twon * denf * denf * twon1;
let t = numf / den;
let term = t * ens;
e_series[i] = term;
es_acc -= term;
ens *= es;
twon *= 4.0;
denfi += 1.0; denf *= denfi; twon1 += 2.0;
if es_acc == el {
last_i = i;
break;
}
el = es_acc;
i += 1;
}
if i >= MAX_ITER {
last_i = MAX_ITER;
}
let nb = last_i - 1;
let big_e = es_acc;
let mut b = vec![0.0_f64; last_i];
b[0] = 1.0 - es_acc;
let mut es2 = 1.0 - es_acc; let mut numf2 = 1.0_f64;
let mut denf2 = 1.0_f64;
let mut numfi = 2.0_f64;
let mut denfi2 = 3.0_f64;
let mut j = 1usize;
while j < last_i {
es2 -= e_series[j];
numf2 *= numfi;
denf2 *= denfi2;
b[j] = es2 * numf2 / denf2;
numfi += 2.0;
denfi2 += 2.0;
j += 1;
}
MDist { nb, es, big_e, b }
}
pub fn proj_mdist(phi: f64, sphi: f64, cphi: f64, m: &MDist) -> f64 {
let sc = sphi * cphi;
let sphi2 = sphi * sphi;
let d = phi * m.big_e - m.es * sc / (1.0 - m.es * sphi2).sqrt();
let mut sum = match m.b.get(m.nb) {
Some(v) => *v,
None => 0.0,
};
let mut i = m.nb;
while i > 0 {
i -= 1;
let bi = match m.b.get(i) {
Some(v) => *v,
None => 0.0,
};
sum = bi + sphi2 * sum;
}
d + sc * sum
}
pub fn proj_inv_mdist(dist: f64, m: &MDist) -> f64 {
const TOL: f64 = 1e-14;
const MAX_ITER: usize = 20;
let k = 1.0 / (1.0 - m.es);
let mut phi = dist;
let mut iter = MAX_ITER;
while iter > 0 {
let s = phi.sin();
let t = 1.0 - m.es * s * s;
let delta = (proj_mdist(phi, s, phi.cos(), m) - dist) * (t * t.sqrt()) * k;
phi -= delta;
if delta.abs() < TOL {
return phi;
}
iter -= 1;
}
phi
}
#[cfg(test)]
mod tests {
use super::*;
const WGS84_ES: f64 = 0.0066943799901413165;
#[test]
fn ini_produces_consistent_series_lengths() {
let m = proj_mdist_ini(WGS84_ES);
assert!(m.nb >= 1, "nb should be at least 1, got {}", m.nb);
assert_eq!(
m.b.len(),
m.nb + 1,
"b vector length should be nb + 1 (len={}, nb={})",
m.b.len(),
m.nb
);
assert_eq!(m.es, WGS84_ES);
assert!(m.big_e.is_finite());
}
#[test]
fn mdist_at_equator_is_zero() {
let m = proj_mdist_ini(WGS84_ES);
let d = proj_mdist(0.0, 0.0, 1.0, &m);
assert!(
d.abs() < 1e-15,
"distance at equator should be ~0, got {}",
d
);
}
#[test]
fn round_trip_sampled_latitudes() {
let m = proj_mdist_ini(WGS84_ES);
let phis = [-1.4_f64, -0.7, -0.1, 0.0, 0.1, 0.7, 1.4];
for &phi in &phis {
let d = proj_mdist(phi, phi.sin(), phi.cos(), &m);
let back = proj_inv_mdist(d, &m);
assert!((back - phi).abs() < 1e-11, "phi={} back={}", phi, back);
}
}
#[test]
fn round_trip_dense_sweep() {
let m = proj_mdist_ini(WGS84_ES);
let mut step = 0;
loop {
let phi = -1.45_f64 + 0.05 * (step as f64);
if phi > 1.45 + 1e-9 {
break;
}
let d = proj_mdist(phi, phi.sin(), phi.cos(), &m);
let back = proj_inv_mdist(d, &m);
assert!((back - phi).abs() < 1e-11, "phi={} back={}", phi, back);
step += 1;
}
}
#[test]
fn mdist_is_monotonic_increasing() {
let m = proj_mdist_ini(WGS84_ES);
let lo = proj_mdist(0.1, 0.1_f64.sin(), 0.1_f64.cos(), &m);
let hi = proj_mdist(0.7, 0.7_f64.sin(), 0.7_f64.cos(), &m);
assert!(
hi > lo,
"distance should grow with latitude: lo={} hi={}",
lo,
hi
);
}
#[test]
fn mdist_is_odd_in_phi() {
let m = proj_mdist_ini(WGS84_ES);
let phi = 0.6_f64;
let pos = proj_mdist(phi, phi.sin(), phi.cos(), &m);
let neg = proj_mdist(-phi, (-phi).sin(), (-phi).cos(), &m);
assert!(
(pos + neg).abs() < 1e-13,
"mdist should be odd: pos={} neg={}",
pos,
neg
);
}
}