1use super::{Vector3, calculate_great_circle_atd, normalise, normalise_centroid, sq_distance};
45use angle_sc::{Angle, Radians};
46use num_traits::{Float, float::FloatConst};
47
48#[must_use]
55pub fn calculate_intersection<T>(
56 pole_0: &Vector3<T>,
57 pole_1: &Vector3<T>,
58 min_sq_value: T,
59) -> Option<Vector3<T>>
60where
61 T: Float + na::Scalar + na::ComplexField<RealField = T>,
62{
63 normalise(&pole_0.cross(pole_1), min_sq_value)
64}
65
66#[must_use]
75pub fn use_antipodal_point<T>(point: &Vector3<T>, centroid: &Vector3<T>) -> bool
76where
77 T: Float + na::Scalar + na::ComplexField<RealField = T>,
78{
79 sq_distance(centroid, &(-*point)) < sq_distance(centroid, point)
80}
81
82#[must_use]
90pub fn closest_intersection_point<T>(point: &Vector3<T>, centroid: &Vector3<T>) -> Vector3<T>
91where
92 T: Float + na::Scalar + na::ComplexField<RealField = T>,
93{
94 if use_antipodal_point(point, centroid) {
95 -*point
96 } else {
97 *point
98 }
99}
100
101#[must_use]
114pub fn calculate_reference_point_and_angle<T>(
115 mid_point_0: &Vector3<T>,
116 pole_0: &Vector3<T>,
117 mid_point_1: &Vector3<T>,
118 pole_1: &Vector3<T>,
119 sq_sin_max_coincident_angle: T,
120) -> (Vector3<T>, Angle<T>)
121where
122 T: Float + na::Scalar + na::ComplexField<RealField = T>,
123 f64: From<T>,
124{
125 let centroid = mid_point_0 + mid_point_1;
126 let point = pole_0.cross(pole_1);
128 normalise(&point, sq_sin_max_coincident_angle).map_or_else(
129 || {
130 let c = normalise_centroid(¢roid, mid_point_0, pole_0);
133 (c, Angle::from_y_x(point.norm(), pole_0.dot(pole_1)))
134 },
135 |p| {
136 let x = closest_intersection_point(&p, ¢roid);
139 (x, Angle::from_y_x(point.norm(), pole_0.dot(pole_1)))
140 },
141 )
142}
143
144#[must_use]
157pub fn calculate_arc_reference_distances_and_angle<T>(
158 mid_point_0: &Vector3<T>,
159 pole_0: &Vector3<T>,
160 mid_point_1: &Vector3<T>,
161 pole_1: &Vector3<T>,
162 sq_sin_max_coincident_angle: T,
163) -> (Radians<T>, Radians<T>, Angle<T>)
164where
165 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
166 f64: From<T>,
167{
168 let (point, angle) = calculate_reference_point_and_angle(
169 mid_point_0,
170 pole_0,
171 mid_point_1,
172 pole_1,
173 sq_sin_max_coincident_angle,
174 );
175 let distance_0 = calculate_great_circle_atd(mid_point_0, pole_0, &point);
176 let distance_1 = calculate_great_circle_atd(mid_point_1, pole_1, &point);
177
178 (distance_0, distance_1, angle)
179}
180
181#[cfg(test)]
182mod tests {
183 use std::f64;
184
185 use super::*;
186 use crate::{LatLong, vector};
187 use angle_sc::{Angle, Degrees, is_within_tolerance};
188
189 pub const MIN_SQ_NORM: f64 = vector::tests::MIN_SQ_NORM;
190
191 #[test]
192 fn test_calculate_intersection() {
193 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(0.0));
194 let south_pole = Vector3::from(&lat_lon_south);
195
196 let lat_lon_north = LatLong::new(Degrees(90.0), Degrees(0.0));
197 let north_pole = Vector3::from(&lat_lon_north);
198
199 let lat_lon_idl = LatLong::new(Degrees(0.0), Degrees(180.0));
200 let idl = Vector3::from(&lat_lon_idl);
201
202 let equator_intersection = calculate_intersection(&south_pole, &north_pole, MIN_SQ_NORM);
203 assert!(equator_intersection.is_none());
204
205 let gc_intersection1 = calculate_intersection(&idl, &north_pole, MIN_SQ_NORM).unwrap();
206 let gc_intersection2 = calculate_intersection(&idl, &south_pole, MIN_SQ_NORM).unwrap();
207
208 assert_eq!(gc_intersection1, -gc_intersection2);
209 }
210
211 #[test]
212 fn test_calculate_arc_reference_distances_and_angle_coincident_great_circles() {
213 let point_1 = Vector3::new(1.0, 0.0, 0.0);
214 let pole_1 = Vector3::new(0.0, 0.0, 1.0);
215
216 let result = calculate_arc_reference_distances_and_angle(
218 &point_1,
219 &pole_1,
220 &point_1,
221 &pole_1,
222 MIN_SQ_NORM,
223 );
224 assert_eq!(Radians(0.0), result.0);
225 assert_eq!(Radians(0.0), result.1);
226 assert_eq!(Degrees(0.0), Degrees::from(result.2));
227
228 let point_m1 = -point_1;
230 let result = calculate_arc_reference_distances_and_angle(
231 &point_1,
232 &pole_1,
233 &point_m1,
234 &pole_1,
235 MIN_SQ_NORM,
236 );
237 assert!(is_within_tolerance(
238 -f64::consts::FRAC_PI_2,
239 result.0.0,
240 f64::EPSILON
241 ));
242 assert!(is_within_tolerance(
243 f64::consts::FRAC_PI_2,
244 result.1.0,
245 f64::EPSILON
246 ));
247 assert_eq!(Degrees(0.0), Degrees::from(result.2));
248
249 let pole_m1 = -pole_1;
251 let result = calculate_arc_reference_distances_and_angle(
252 &point_1,
253 &pole_1,
254 &point_m1,
255 &pole_m1,
256 MIN_SQ_NORM,
257 );
258 assert!(is_within_tolerance(
259 -f64::consts::FRAC_PI_2,
260 result.0.0,
261 f64::EPSILON
262 ));
263 assert!(is_within_tolerance(
264 -f64::consts::FRAC_PI_2,
265 result.1.0,
266 f64::EPSILON
267 ));
268 assert_eq!(Degrees(180.0), Degrees::from(result.2));
269 }
270
271 #[test]
272 fn test_calculate_arc_reference_distances_and_angle_intersecting_great_circles() {
273 let point_1 = Vector3::new(1.0, 0.0, 0.0);
274 let pole_1 = Vector3::new(0.0, 0.0, 1.0);
275 let pole_2 = Vector3::new(0.0, 1.0, 0.0);
276
277 let result = calculate_arc_reference_distances_and_angle(
279 &point_1,
280 &pole_1,
281 &point_1,
282 &pole_2,
283 MIN_SQ_NORM,
284 );
285 assert_eq!(Radians(0.0), result.0);
286 assert_eq!(Radians(0.0), result.1);
287 assert_eq!(Degrees(90.0), Degrees::from(result.2));
288
289 let pole_3 = (pole_1 + pole_2).normalize();
291 let result = calculate_arc_reference_distances_and_angle(
292 &point_1,
293 &pole_1,
294 &point_1,
295 &pole_3,
296 MIN_SQ_NORM,
297 );
298 assert_eq!(Radians(0.0), result.0);
299 assert_eq!(Radians(0.0), result.1);
300 assert_eq!(Degrees(45.0), Degrees::from(result.2));
301
302 let pole_m3 = -pole_3;
304 let result = calculate_arc_reference_distances_and_angle(
305 &point_1,
306 &pole_1,
307 &point_1,
308 &pole_m3,
309 MIN_SQ_NORM,
310 );
311 assert_eq!(Radians(0.0), result.0);
312 assert_eq!(Radians(0.0), result.1);
313 assert_eq!(Degrees(135.0), Degrees::from(result.2));
314
315 let point_2 = vector::position(
317 &point_1,
318 &vector::direction(&point_1, &pole_3),
319 Angle::default().quarter_turn_cw(),
320 );
321 let result = calculate_arc_reference_distances_and_angle(
322 &point_1,
323 &pole_1,
324 &point_2,
325 &pole_3,
326 MIN_SQ_NORM,
327 );
328 assert_eq!(Radians(0.0), result.0);
329 assert!(is_within_tolerance(
330 -f64::consts::FRAC_PI_2,
331 result.1.0,
332 f64::EPSILON
333 ));
334 assert_eq!(Degrees(45.0), Degrees::from(result.2));
335
336 let result = calculate_arc_reference_distances_and_angle(
338 &point_1,
339 &pole_1,
340 &point_2,
341 &pole_m3,
342 MIN_SQ_NORM,
343 );
344 assert_eq!(Radians(0.0), result.0);
345 assert!(is_within_tolerance(
346 f64::consts::FRAC_PI_2,
347 result.1.0,
348 f64::EPSILON
349 ));
350 assert_eq!(Degrees(135.0), Degrees::from(result.2));
351 }
352}