use float_cmp::approx_eq;
use crate::{ellipsoids::Ellipsoid, Projection, ProjectionError};
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct ModifiedAzimuthalEquidistant {
lon_0: f64,
lat_0: f64,
n_1: f64,
g: f64,
ellps: Ellipsoid,
}
impl ModifiedAzimuthalEquidistant {
pub fn new(ref_lon: f64, ref_lat: f64, ellps: Ellipsoid) -> Result<Self, ProjectionError> {
if !(-180.0..180.0).contains(&ref_lon) {
return Err(ProjectionError::IncorrectParams(
"longitude must be between -180..180",
));
}
if !(-90.0..90.0).contains(&ref_lat) {
return Err(ProjectionError::IncorrectParams(
"latitude must be between -90..90",
));
}
if !ref_lon.is_finite() || !ref_lat.is_finite() {
return Err(ProjectionError::IncorrectParams(
"one of arguments is not finite",
));
}
let lon_0 = ref_lon.to_radians();
let lat_0 = ref_lat.to_radians();
let n_1 = ellps.A / (1.0 - (ellps.E.powi(2) * (lat_0.sin()).powi(2))).sqrt();
let g = ellps.E * lat_0.sin() / (1.0 - ellps.E.powi(2)).sqrt();
Ok(ModifiedAzimuthalEquidistant {
lon_0,
lat_0,
n_1,
g,
ellps,
})
}
}
impl Projection for ModifiedAzimuthalEquidistant {
fn project_unchecked(&self, lon: f64, lat: f64) -> (f64, f64) {
let lon = lon.to_radians();
let lat = lat.to_radians();
let n = self.ellps.A / (1.0 - (self.ellps.E.powi(2) * (lat.sin()).powi(2))).sqrt();
let psi = (((1.0 - self.ellps.E.powi(2)) * lat.tan())
+ ((self.ellps.E.powi(2) * self.n_1 * self.lat_0.sin()) / (n * lat.cos())))
.atan();
let az = ((lon - self.lon_0).sin())
.atan2((self.lat_0.cos() * psi.tan()) - (self.lat_0.sin() * (lon - self.lon_0).cos()));
let s = if approx_eq!(f64, az.sin(), 0.0) {
((self.lat_0.cos() * psi.sin()) - (self.lat_0.sin() * psi.cos()))
.asin()
.abs()
* az.cos().signum()
} else {
(((lon - self.lon_0).sin() * psi.cos()) / (az.sin())).asin()
};
let h = self.ellps.E * self.lat_0.cos() * az.cos() / (1.0 - self.ellps.E.powi(2)).sqrt();
let c = (self.n_1 * s)
* (1.0 - (s.powi(2) * h.powi(2) * (1.0 - h.powi(2)) / 6.0)
+ ((s.powi(3) / 8.0) * self.g * h * (1.0 - 2.0 * h.powi(2)))
+ ((s.powi(4) / 120.0)
* ((h.powi(2) * (4.0 - 7.0 * h.powi(2)))
- (3.0 * self.g.powi(2) * (1.0 - 7.0 * h.powi(2)))))
- ((s.powi(5) / 48.0) * self.g * h));
let x = c * az.sin();
let y = c * az.cos();
(x, y)
}
fn inverse_project_unchecked(&self, x: f64, y: f64) -> (f64, f64) {
let c = (x * x + y * y).sqrt();
let az = x.atan2(y);
let big_a =
-self.ellps.E * self.ellps.E * ((self.lat_0.cos()).powi(2)) * ((az.cos()).powi(2))
/ (1.0 - self.ellps.E * self.ellps.E);
let big_b = 3.0
* self.ellps.E
* self.ellps.E
* (1.0 - big_a)
* self.lat_0.sin()
* self.lat_0.cos()
* az.cos()
/ (1.0 - self.ellps.E * self.ellps.E);
let big_d = c / self.n_1;
let big_e = big_d
- (big_a * (1.0 + big_a) * big_d.powi(3) / 6.0)
- (big_b * (1.0 + 3.0 * big_a) * big_d.powi(4) / 24.0);
let big_f = 1.0 - (big_a * big_e * big_e / 2.0) - (big_b * big_e.powi(3) / 6.0);
let psi =
((self.lat_0.sin() * big_e.cos()) + (self.lat_0.cos() * big_e.sin() * az.cos())).asin();
let lon = self.lon_0 + (az.sin() * big_e.sin() / psi.cos()).asin();
let lat = ((1.0 - (self.ellps.E * self.ellps.E * big_f * self.lat_0.sin() / psi.sin()))
* psi.tan()
/ (1.0 - self.ellps.E * self.ellps.E))
.atan();
let lon = lon.to_degrees();
let lat = lat.to_degrees();
(lon, lat)
}
}