use crate::{
Ellipsoid, Projection, ProjectionError,
errors::{ensure_finite, ensure_within_range, unpack_required_parameter},
};
use geographiclib_rs::{DirectGeodesic, Geodesic, InverseGeodesic};
#[cfg(feature = "tracing")]
use tracing::instrument;
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct AzimuthalEquidistant {
lon_0: f64,
lat_0: f64,
geod: Geodesic,
}
impl AzimuthalEquidistant {
#[must_use]
pub fn builder() -> AzimuthalEquidistantBuilder {
AzimuthalEquidistantBuilder::default()
}
}
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct AzimuthalEquidistantBuilder {
ref_lon: Option<f64>,
ref_lat: Option<f64>,
ellipsoid: Ellipsoid,
}
impl Default for AzimuthalEquidistantBuilder {
fn default() -> Self {
Self {
ref_lon: None,
ref_lat: None,
ellipsoid: Ellipsoid::WGS84,
}
}
}
impl AzimuthalEquidistantBuilder {
pub const fn ref_lonlat(&mut self, lon: f64, lat: f64) -> &mut Self {
self.ref_lon = Some(lon);
self.ref_lat = Some(lat);
self
}
pub const fn ellipsoid(&mut self, ellps: Ellipsoid) -> &mut Self {
self.ellipsoid = ellps;
self
}
pub fn initialize_projection(&self) -> Result<AzimuthalEquidistant, ProjectionError> {
let ref_lon = unpack_required_parameter!(self, ref_lon);
let ref_lat = unpack_required_parameter!(self, ref_lat);
let ellps = self.ellipsoid;
ensure_finite!(ref_lon, ref_lat);
ensure_within_range!(ref_lon, -180.0..180.0);
ensure_within_range!(ref_lat, -90.0..90.0);
Ok(AzimuthalEquidistant {
lon_0: ref_lon,
lat_0: ref_lat,
geod: ellps.into(),
})
}
}
impl Projection for AzimuthalEquidistant {
#[cfg_attr(feature = "tracing", instrument(level = "trace"))]
fn project_unchecked(&self, lon: f64, lat: f64) -> (f64, f64) {
let (s12, azi1, _, _) = self.geod.inverse(self.lat_0, self.lon_0, lat, lon);
let x = s12 * azi1.to_radians().sin();
let y = s12 * azi1.to_radians().cos();
(x, y)
}
#[cfg_attr(feature = "tracing", instrument(level = "trace"))]
fn inverse_project_unchecked(&self, x: f64, y: f64) -> (f64, f64) {
let azi1 = x.atan2(y).to_degrees();
let s12 = x.hypot(y);
let (lat, lon) = self.geod.direct(self.lat_0, self.lon_0, azi1, s12);
(lon, lat)
}
}