1use crate::{CoordFloat, Point, MEAN_EARTH_RADIUS};
2use num_traits::FromPrimitive;
3
4pub trait HaversineIntermediate<T: CoordFloat> {
7 fn haversine_intermediate(&self, other: &Point<T>, f: T) -> Point<T>;
34 fn haversine_intermediate_fill(
35 &self,
36 other: &Point<T>,
37 max_dist: T,
38 include_ends: bool,
39 ) -> Vec<Point<T>>;
40}
41
42impl<T> HaversineIntermediate<T> for Point<T>
43where
44 T: CoordFloat + FromPrimitive,
45{
46 fn haversine_intermediate(&self, other: &Point<T>, f: T) -> Point<T> {
47 let params = get_params(self, other);
48 get_point(¶ms, f)
49 }
50
51 fn haversine_intermediate_fill(
52 &self,
53 other: &Point<T>,
54 max_dist: T,
55 include_ends: bool,
56 ) -> Vec<Point<T>> {
57 let params = get_params(self, other);
58 let HaversineParams { d, .. } = params;
59
60 let total_distance = d * T::from(MEAN_EARTH_RADIUS).unwrap();
61
62 if total_distance <= max_dist {
63 return if include_ends {
64 vec![*self, *other]
65 } else {
66 vec![]
67 };
68 }
69
70 let number_of_points = (total_distance / max_dist).ceil();
71 let interval = T::one() / number_of_points;
72
73 let mut current_step = interval;
74 let mut points = if include_ends { vec![*self] } else { vec![] };
75
76 while current_step < T::one() {
77 let point = get_point(¶ms, current_step);
78 points.push(point);
79 current_step = current_step + interval;
80 }
81
82 if include_ends {
83 points.push(*other);
84 }
85
86 points
87 }
88}
89
90#[allow(clippy::many_single_char_names)]
91struct HaversineParams<T: num_traits::Float + FromPrimitive> {
92 d: T,
93 n: T,
94 o: T,
95 p: T,
96 q: T,
97 r: T,
98 s: T,
99}
100
101#[allow(clippy::many_single_char_names)]
102fn get_point<T: CoordFloat + FromPrimitive>(params: &HaversineParams<T>, f: T) -> Point<T> {
103 let one = T::one();
104
105 let HaversineParams {
106 d,
107 n,
108 o,
109 p,
110 q,
111 r,
112 s,
113 } = *params;
114
115 let a = ((one - f) * d).sin() / d.sin();
116 let b = (f * d).sin() / d.sin();
117
118 let x = a * n + b * o;
119 let y = a * p + b * q;
120 let z = a * r + b * s;
121
122 let lat = z.atan2(x.hypot(y));
123 let lon = y.atan2(x);
124
125 Point::new(lon.to_degrees(), lat.to_degrees())
126}
127
128#[allow(clippy::many_single_char_names)]
129fn get_params<T: CoordFloat + FromPrimitive>(p1: &Point<T>, p2: &Point<T>) -> HaversineParams<T> {
130 let one = T::one();
131 let two = one + one;
132
133 let lat1 = p1.y().to_radians();
134 let lon1 = p1.x().to_radians();
135 let lat2 = p2.y().to_radians();
136 let lon2 = p2.x().to_radians();
137
138 let (lat1_sin, lat1_cos) = lat1.sin_cos();
139 let (lat2_sin, lat2_cos) = lat2.sin_cos();
140 let (lon1_sin, lon1_cos) = lon1.sin_cos();
141 let (lon2_sin, lon2_cos) = lon2.sin_cos();
142
143 let m = lat1_cos * lat2_cos;
144
145 let n = lat1_cos * lon1_cos;
146 let o = lat2_cos * lon2_cos;
147 let p = lat1_cos * lon1_sin;
148 let q = lat2_cos * lon2_sin;
149
150 let k = (((lat1 - lat2) / two).sin().powi(2) + m * ((lon1 - lon2) / two).sin().powi(2)).sqrt();
151
152 let d = two * k.asin();
153
154 HaversineParams {
155 d,
156 n,
157 o,
158 p,
159 q,
160 r: lat1_sin,
161 s: lat2_sin,
162 }
163}
164
165#[cfg(test)]
166mod test {
167 use super::*;
168 use crate::HaversineIntermediate;
169
170 #[test]
171 fn f_is_zero_or_one_test() {
172 let p1 = Point::new(10.0, 20.0);
173 let p2 = Point::new(15.0, 25.0);
174 let i0 = p1.haversine_intermediate(&p2, 0.0);
175 let i100 = p1.haversine_intermediate(&p2, 1.0);
176 assert_relative_eq!(i0.x(), p1.x(), epsilon = 1.0e-6);
177 assert_relative_eq!(i0.y(), p1.y(), epsilon = 1.0e-6);
178 assert_relative_eq!(i100.x(), p2.x(), epsilon = 1.0e-6);
179 assert_relative_eq!(i100.y(), p2.y(), epsilon = 1.0e-6);
180 }
181
182 #[test]
183 fn various_f_values_test() {
184 let p1 = Point::new(10.0, 20.0);
185 let p2 = Point::new(125.0, 25.0);
186 let i20 = p1.haversine_intermediate(&p2, 0.2);
187 let i50 = p1.haversine_intermediate(&p2, 0.5);
188 let i80 = p1.haversine_intermediate(&p2, 0.8);
189 let i20_should = Point::new(29.83519, 29.94841);
190 let i50_should = Point::new(65.87471, 37.72201);
191 let i80_should = Point::new(103.56036, 33.50518);
192 assert_relative_eq!(i20.x(), i20_should.x(), epsilon = 0.2);
193 assert_relative_eq!(i20.y(), i20_should.y(), epsilon = 0.2);
194 assert_relative_eq!(i50.x(), i50_should.x(), epsilon = 0.2);
195 assert_relative_eq!(i50.y(), i50_should.y(), epsilon = 0.2);
196 assert_relative_eq!(i80.x(), i80_should.x(), epsilon = 0.2);
197 assert_relative_eq!(i80.y(), i80_should.y(), epsilon = 0.2);
198 }
199
200 #[test]
201 fn should_be_north_pole_test() {
202 let p1 = Point::new(0.0, 10.0);
203 let p2 = Point::new(180.0, 10.0);
204 let i50 = p1.haversine_intermediate(&p2, 0.5);
205 let i50_should = Point::new(90.0, 90.0);
206 assert_relative_eq!(i50.x(), i50_should.x(), epsilon = 1.0e-6);
207 assert_relative_eq!(i50.y(), i50_should.y(), epsilon = 1.0e-6);
208 }
209
210 #[test]
211 fn should_be_start_end_test() {
212 let p1 = Point::new(30.0, 40.0);
213 let p2 = Point::new(40.0, 50.0);
214 let max_dist = 1500000.0; let include_ends = true;
216 let route = p1.haversine_intermediate_fill(&p2, max_dist, include_ends);
217 assert_eq!(route, vec![p1, p2]);
218 }
219
220 #[test]
221 fn should_add_i50_test() {
222 let p1 = Point::new(30.0, 40.0);
223 let p2 = Point::new(40.0, 50.0);
224 let max_dist = 1000000.0; let include_ends = true;
226 let i50 = p1.clone().haversine_intermediate(&p2, 0.5);
227 let route = p1.haversine_intermediate_fill(&p2, max_dist, include_ends);
228 assert_eq!(route, vec![p1, i50, p2]);
229 }
230
231 #[test]
232 fn should_add_i25_i50_i75_test() {
233 let p1 = Point::new(30.0, 40.0);
234 let p2 = Point::new(40.0, 50.0);
235 let max_dist = 400000.0; let include_ends = true;
237 let i25 = p1.clone().haversine_intermediate(&p2, 0.25);
238 let i50 = p1.clone().haversine_intermediate(&p2, 0.5);
239 let i75 = p1.clone().haversine_intermediate(&p2, 0.75);
240 let route = p1.haversine_intermediate_fill(&p2, max_dist, include_ends);
241 assert_eq!(route, vec![p1, i25, i50, i75, p2]);
242 }
243}