mapping_algorithms/geo/
haversine.rs1use nalgebra::{Point2, RealField, Scalar};
25use num_traits::Float;
26
27#[inline]
28fn half_angle_sine_squared<T>(input: T) -> T
29where
30 T: Float,
31{
32 (input.to_radians() / (T::one() + T::one())).sin().powi(2)
33}
34
35#[cfg_attr(
48 feature = "tracing",
49 tracing::instrument("Calculate Haversine Distance", skip_all)
50)]
51pub fn calculate_haversine_distance<T>(
52 point_a: Point2<T>,
53 point_b: Point2<T>,
54 sphere_radius: T,
55) -> T
56where
57 T: Scalar + Float,
58{
59 let delta_lat = point_b.x - point_a.x;
60 let delta_lon = point_b.y - point_a.y;
61
62 let lat1_radians = point_a.x.to_radians();
63 let lat2_radians = point_b.x.to_radians();
64
65 let basic_haversine = half_angle_sine_squared(delta_lat)
66 + half_angle_sine_squared(delta_lon) * lat1_radians.cos() * lat2_radians.cos();
67
68 let inverse_haversine = (T::one() + T::one())
69 * basic_haversine
70 .sqrt()
71 .atan2((T::one() - basic_haversine).sqrt());
72
73 sphere_radius * inverse_haversine
74}
75
76#[cfg_attr(
93 feature = "tracing",
94 tracing::instrument("Calculate Bearing Between Points", skip_all)
95)]
96pub fn calculate_sphere_bearing<T>(point_a: Point2<T>, point_b: Point2<T>) -> T
97where
98 T: Scalar + Float + RealField,
99{
100 let lat1_rad = point_a.x.to_radians();
101 let lat2_rad = point_b.x.to_radians();
102
103 let lon_delta_radians = (point_b.y - point_a.y).to_radians();
104
105 let x = <T as Float>::sin(lon_delta_radians) * <T as Float>::cos(lat2_rad);
106 let y = (<T as Float>::cos(lat1_rad) * <T as Float>::sin(lat2_rad))
107 - (<T as Float>::sin(lat1_rad)
108 * <T as Float>::cos(lat2_rad)
109 * <T as Float>::cos(lon_delta_radians));
110
111 (<T as Float>::atan2(x, y) + T::two_pi()) % T::two_pi()
112}
113
114#[cfg(feature = "pregenerated")]
115macro_rules! impl_haversine_formula {
116 ($prec:expr, doc $doc:tt) => {
117 ::paste::paste! {
118 pub(super) mod [<$doc _precision>] {
119 #[doc = "A premade variant of the haversine distance calculation function, made for " $doc " precision floating-point arithmetic."]
120 pub fn calculate_haversine_distance(point_a: nalgebra::Point2<$prec>, point_b: nalgebra::Point2<$prec>, sphere_radius: $prec) -> $prec {
121 super::calculate_haversine_distance(point_a,point_b,sphere_radius)
122 }
123
124 #[doc = "A premade variant of the sphere bearing calculation function, made for " $doc " precision floating-point arithmetic."]
125 pub fn calculate_sphere_bearing(point_a: nalgebra::Point2<$prec>, point_b: nalgebra::Point2<$prec>) -> $prec {
126 super::calculate_sphere_bearing(point_a,point_b)}
127 }
128 }
129 }
130}
131
132#[cfg(feature = "pregenerated")]
133impl_haversine_formula!(f32, doc single);
134#[cfg(feature = "pregenerated")]
135impl_haversine_formula!(f64, doc double);
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_distance() {
143 let point_a = Point2::new(52.5200, 13.4050); let point_b = Point2::new(48.8566, 2.3522); let earth_radius_km = 6371.0;
147 let distance = calculate_haversine_distance(point_a, point_b, earth_radius_km);
148 let expected_distance = 877.46; assert!(
150 (distance - expected_distance).abs() < 0.01,
151 "Distance between Berlin and Paris should be roughly 877.46 km, found {}",
152 distance
153 );
154 }
155
156 #[test]
157 fn test_bearing() {
158 let point_a = Point2::new(39.099_91, -94.581213); let point_b = Point2::new(38.627_09, -90.200_2); let bearing = calculate_sphere_bearing(point_a, point_b);
162 let expected_bearing = 96.51; assert!(
164 (bearing - expected_bearing.to_radians()).abs() < 0.01,
165 "Bearing from Kansas City to St Louis should be roughly 96.51 degrees, found {}",
166 bearing.to_degrees()
167 );
168 }
169}