geometry_algorithm/
azimuth.rs1use geometry_cs::CoordinateSystem;
13use geometry_strategy::{AzimuthStrategy, DefaultAzimuth, DefaultAzimuthStrategy};
14use geometry_trait::Point;
15
16type Family<P> = <<P as Point>::Cs as CoordinateSystem>::Family;
20
21#[must_use]
29pub fn azimuth<P1, P2>(
30 p1: &P1,
31 p2: &P2,
32) -> <DefaultAzimuthStrategy<P1> as AzimuthStrategy<P1, P2>>::Out
33where
34 P1: Point,
35 P2: Point,
36 Family<P1>: DefaultAzimuth<Family<P1>>,
37 DefaultAzimuthStrategy<P1>: AzimuthStrategy<P1, P2> + Default,
38{
39 DefaultAzimuthStrategy::<P1>::default().azimuth(p1, p2)
40}
41
42#[must_use]
50#[allow(
51 clippy::needless_pass_by_value,
52 reason = "Strategies are ZST/small Copy configuration objects; by-value matches the user-facing call shape."
53)]
54pub fn azimuth_with<P1, P2, S>(p1: &P1, p2: &P2, s: S) -> S::Out
55where
56 P1: Point,
57 P2: Point,
58 S: AzimuthStrategy<P1, P2>,
59{
60 s.azimuth(p1, p2)
61}
62
63#[cfg(test)]
64mod tests {
65 #![allow(
70 clippy::float_cmp,
71 reason = "azimuths are compared with an explicit absolute tolerance, not `==`"
72 )]
73 #![allow(
74 clippy::excessive_precision,
75 reason = "reference constants are copied verbatim from Boost's azimuth.cpp; the trailing digits document the source value even past f64 precision."
76 )]
77
78 use super::azimuth;
79 use geometry_cs::Cartesian;
80 use geometry_model::Point2D;
81
82 type P = Point2D<f64, Cartesian>;
83
84 #[test]
85 fn due_north_is_zero() {
86 assert!((azimuth(&P::new(0.0, 0.0), &P::new(0.0, 1.0)) - 0.0).abs() < 1e-12);
87 }
88
89 #[test]
90 fn due_east_is_half_pi() {
91 let a = azimuth(&P::new(0.0, 0.0), &P::new(1.0, 0.0));
92 assert!((a - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
93 }
94
95 #[cfg(feature = "std")]
98 #[test]
99 fn spherical_diagonal_close_to_45() {
100 use geometry_adapt::{Adapt, WithCs};
101 use geometry_cs::{Degree, Spherical};
102
103 type Sp = WithCs<Adapt<[f64; 2]>, Spherical<Degree>>;
104 let sp = |lon: f64, lat: f64| -> Sp { WithCs::new(Adapt([lon, lat])) };
105
106 let got = azimuth(&sp(0., 0.), &sp(1., 1.));
107 let expected = 44.995_636_455_344_851_f64.to_radians();
108 assert!((got - expected).abs() < 1e-9, "got {got}");
109 }
110
111 #[cfg(feature = "std")]
114 #[test]
115 fn geographic_diagonal_close_to_45() {
116 use geometry_adapt::{Adapt, WithCs};
117 use geometry_cs::{Degree, Geographic};
118
119 type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
120 let gg = |lon: f64, lat: f64| -> Gg { WithCs::new(Adapt([lon, lat])) };
121
122 let got = azimuth(&gg(0., 0.), &gg(1., 1.));
123 let expected = 45.187_718_848_049_521_f64.to_radians();
124 assert!((got - expected).abs() < 1e-9, "got {got}");
125 }
126
127 #[cfg(feature = "std")]
130 #[test]
131 fn geographic_destination_at_a_pole() {
132 use geometry_adapt::{Adapt, WithCs};
133 use geometry_cs::{Degree, Geographic};
134
135 type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
136 let gg = |lon: f64, lat: f64| -> Gg { WithCs::new(Adapt([lon, lat])) };
137
138 let north = azimuth(&gg(10., 20.), &gg(10., 90.));
139 assert!(north.abs() < 1e-12, "got {north}");
140 let south = azimuth(&gg(10., 20.), &gg(10., -90.));
141 assert!((south - core::f64::consts::PI).abs() < 1e-12, "got {south}");
142 }
143
144 #[cfg(feature = "std")]
147 #[test]
148 fn geographic_start_at_north_pole_points_south() {
149 use geometry_adapt::{Adapt, WithCs};
150 use geometry_cs::{Degree, Geographic};
151
152 type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
153 let gg = |lon: f64, lat: f64| -> Gg { WithCs::new(Adapt([lon, lat])) };
154
155 let got = azimuth(&gg(0., 90.), &gg(0., 0.));
156 assert!((got - core::f64::consts::PI).abs() < 1e-12, "got {got}");
157 }
158
159 #[cfg(feature = "std")]
162 #[test]
163 fn geographic_antimeridian_crossing_heads_east() {
164 use geometry_adapt::{Adapt, WithCs};
165 use geometry_cs::{Degree, Geographic};
166
167 type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
168 let gg = |lon: f64, lat: f64| -> Gg { WithCs::new(Adapt([lon, lat])) };
169
170 let got = azimuth(&gg(179.5, 0.), &gg(-179.5, 0.));
171 assert!(
172 (got - core::f64::consts::FRAC_PI_2).abs() < 1e-6,
173 "got {got}"
174 );
175 }
176}