geo/algorithm/
haversine_bearing.rs

1use crate::{CoordFloat, Point};
2
3/// Returns the bearing to another Point in degrees.
4///
5/// Bullock, R.: Great Circle Distances and Bearings Between Two Locations, 2007.
6/// (<https://dtcenter.org/met/users/docs/write_ups/gc_simple.pdf>)
7
8pub trait HaversineBearing<T: CoordFloat> {
9    /// Returns the bearing to another Point in degrees, where North is 0° and East is 90°.
10    ///
11    /// # Examples
12    ///
13    /// ```
14    /// # #[macro_use] extern crate approx;
15    /// #
16    /// use geo::HaversineBearing;
17    /// use geo::Point;
18    ///
19    /// let p_1 = Point::new(9.177789688110352, 48.776781529534965);
20    /// let p_2 = Point::new(9.274410083250379, 48.84033282787534);
21    /// let bearing = p_1.haversine_bearing(p_2);
22    /// assert_relative_eq!(bearing, 45., epsilon = 1.0e-6);
23    /// ```
24    fn haversine_bearing(&self, point: Point<T>) -> T;
25}
26
27impl<T> HaversineBearing<T> for Point<T>
28where
29    T: CoordFloat,
30{
31    fn haversine_bearing(&self, point: Point<T>) -> T {
32        let (lng_a, lat_a) = (self.x().to_radians(), self.y().to_radians());
33        let (lng_b, lat_b) = (point.x().to_radians(), point.y().to_radians());
34        let delta_lng = lng_b - lng_a;
35        let s = lat_b.cos() * delta_lng.sin();
36        let c = lat_a.cos() * lat_b.sin() - lat_a.sin() * lat_b.cos() * delta_lng.cos();
37
38        T::atan2(s, c).to_degrees()
39    }
40}
41
42#[cfg(test)]
43mod test {
44    use crate::point;
45    use crate::HaversineBearing;
46    use crate::HaversineDestination;
47
48    #[test]
49    fn north_bearing() {
50        let p_1 = point!(x: 9., y: 47.);
51        let p_2 = point!(x: 9., y: 48.);
52        let bearing = p_1.haversine_bearing(p_2);
53        assert_relative_eq!(bearing, 0.);
54    }
55
56    #[test]
57    fn equatorial_east_bearing() {
58        let p_1 = point!(x: 9., y: 0.);
59        let p_2 = point!(x: 10., y: 0.);
60        let bearing = p_1.haversine_bearing(p_2);
61        assert_relative_eq!(bearing, 90.);
62    }
63
64    #[test]
65    fn east_bearing() {
66        let p_1 = point!(x: 9., y: 10.);
67        let p_2 = point!(x: 18.12961917258341, y: 9.875828894123304);
68
69        let bearing = p_1.haversine_bearing(p_2);
70        assert_relative_eq!(bearing, 90.);
71    }
72
73    #[test]
74    fn northeast_bearing() {
75        let p_1 = point!(x: 9.177789688110352f64, y: 48.776781529534965);
76        let p_2 = point!(x: 9.274409949623548, y: 48.84033274015048);
77        let bearing = p_1.haversine_bearing(p_2);
78        assert_relative_eq!(bearing, 45., epsilon = 1.0e-6);
79    }
80
81    #[test]
82    fn consistent_with_destination() {
83        let p_1 = point!(x: 9.177789688110352f64, y: 48.776781529534965);
84        let p_2 = p_1.haversine_destination(45., 10000.);
85
86        let b_1 = p_1.haversine_bearing(p_2);
87        assert_relative_eq!(b_1, 45., epsilon = 1.0e-6);
88    }
89}