use geometry_cs::{CoordinateSystem, SphericalFamily};
use geometry_tag::SameAs;
use geometry_trait::Point;
use crate::azimuth::AzimuthStrategy;
#[cfg(feature = "std")]
use crate::normalise::{HasAngularUnits, lonlat_radians};
#[derive(Debug, Default, Clone, Copy)]
pub struct SphericalAzimuth;
#[cfg(feature = "std")]
impl<P1, P2> AzimuthStrategy<P1, P2> for SphericalAzimuth
where
P1: Point<Scalar = f64>,
P2: Point<Scalar = f64>,
P1::Cs: HasAngularUnits,
P2::Cs: HasAngularUnits,
<P1::Cs as CoordinateSystem>::Family: SameAs<SphericalFamily>,
<P2::Cs as CoordinateSystem>::Family: SameAs<SphericalFamily>,
{
type Out = f64;
#[inline]
fn azimuth(&self, p1: &P1, p2: &P2) -> f64 {
let (lon1, lat1) = lonlat_radians(p1);
let (lon2, lat2) = lonlat_radians(p2);
let dlon = lon2 - lon1;
let y = dlon.sin() * lat2.cos();
let x = lat1.cos() * lat2.sin() - lat1.sin() * lat2.cos() * dlon.cos();
y.atan2(x)
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
#![allow(
clippy::float_cmp,
reason = "azimuths are compared with an explicit absolute tolerance, not `==`"
)]
#![allow(
clippy::excessive_precision,
reason = "reference constants are copied verbatim from Boost's azimuth.cpp; the trailing digits document the source value even past f64 precision."
)]
use super::SphericalAzimuth;
use crate::azimuth::AzimuthStrategy;
use geometry_adapt::{Adapt, WithCs};
use geometry_cs::{Degree, Spherical};
type Sp = WithCs<Adapt<[f64; 2]>, Spherical<Degree>>;
#[inline]
fn sp(lon: f64, lat: f64) -> Sp {
WithCs::new(Adapt([lon, lat]))
}
#[test]
fn coincident_is_zero() {
let got = SphericalAzimuth.azimuth(&sp(0., 0.), &sp(0., 0.));
assert!(got.abs() < 1e-12);
}
#[test]
fn diagonal() {
let got = SphericalAzimuth.azimuth(&sp(0., 0.), &sp(1., 1.));
let expected = 44.995_636_455_344_851_f64.to_radians();
assert!((got - expected).abs() < 1e-9, "got {got}");
}
#[test]
fn shallow_east() {
let got = SphericalAzimuth.azimuth(&sp(0., 0.), &sp(100., 1.));
let expected = 88.984_576_593_576_293_f64.to_radians();
assert!((got - expected).abs() < 1e-9, "got {got}");
}
#[test]
fn diagonal_west() {
let got = SphericalAzimuth.azimuth(&sp(0., 0.), &sp(-1., 1.));
let expected = (-44.995_636_455_344_851_f64).to_radians();
assert!((got - expected).abs() < 1e-9, "got {got}");
}
}