1extern crate nalgebra as na;
28
29use crate::{Vector3, great_circle};
30use angle_sc::{Angle, Radians, trig};
31use num_traits::{Float, float::FloatConst};
32
33pub mod intersection;
34
35pub const MIN_SIN_MULTIPLE: u32 = 16384;
38
39pub const MIN_SIN_MULTIPLE_F32: u32 = 4096;
41
42#[must_use]
50pub fn to_point<T: Float>(lat: Angle<T>, lon: Angle<T>) -> Vector3<T> {
51 Vector3::<T>::new(
52 lat.cos().0 * lon.cos().0,
53 lat.cos().0 * lon.sin().0,
54 lat.sin().0,
55 )
56}
57
58#[must_use]
64pub fn latitude<T: Float>(a: &Vector3<T>) -> Angle<T> {
65 Angle::from_y_x(a[2], a[0].hypot(a[1]))
66}
67
68#[must_use]
74pub fn longitude<T: Float>(a: &Vector3<T>) -> Angle<T> {
75 Angle::from_y_x(a[1], a[0])
76}
77
78#[allow(clippy::missing_panics_doc)]
84#[must_use]
85pub fn is_unit<T>(a: &Vector3<T>) -> bool
86where
87 T: Float + na::Scalar + na::ComplexField<RealField = T>,
88{
89 let twelve = T::from(12).expect("Could not convert constant to Float");
90 let min_sq_length = T::one() - twelve * T::epsilon();
91 let max_sq_length = T::one() + twelve * T::epsilon();
92
93 (min_sq_length..=max_sq_length).contains(&(a.norm_squared()))
94}
95
96#[must_use]
105pub fn normalise<T>(a: &Vector3<T>, min_sq_value: T) -> Option<Vector3<T>>
106where
107 T: Float + na::Scalar + na::ComplexField<RealField = T>,
108{
109 if a.norm_squared() < min_sq_value {
110 None
111 } else {
112 Some(a.normalize())
113 }
114}
115
116#[must_use]
124pub fn sq_distance<T>(a: &Vector3<T>, b: &Vector3<T>) -> T
125where
126 T: Float + na::Scalar + na::ComplexField<RealField = T>,
127{
128 (b - a).norm_squared()
129}
130
131#[must_use]
138pub fn distance<T>(a: &Vector3<T>, b: &Vector3<T>) -> T
139where
140 T: Float + na::Scalar + na::ComplexField<RealField = T>,
141{
142 (b - a).norm()
143}
144
145#[must_use]
151pub fn are_orthogonal<T>(a: &Vector3<T>, b: &Vector3<T>) -> bool
152where
153 T: Float + na::Scalar + na::ComplexField<RealField = T>,
154{
155 let two_epsilon = T::epsilon() + T::epsilon();
156 let max_length = two_epsilon + two_epsilon;
157
158 (-max_length..=max_length).contains(&(a.dot(b)))
159}
160
161#[must_use]
168pub fn delta_longitude<T>(a: &Vector3<T>, b: &Vector3<T>) -> Angle<T>
169where
170 T: Float + na::Scalar + na::ComplexField<RealField = T>,
171{
172 let a_lon = a.xy();
173 let b_lon = b.xy();
174 Angle::from_y_x(b_lon.perp(&a_lon), b_lon.dot(&a_lon))
175}
176
177#[must_use]
184pub fn is_south_of<T: Float>(a: &Vector3<T>, b: &Vector3<T>) -> bool {
185 a[2] < b[2]
186}
187#[must_use]
194pub fn is_west_of<T>(a: &Vector3<T>, b: &Vector3<T>) -> bool
195where
196 T: Float + na::Scalar + na::ComplexField<RealField = T>,
197{
198 b.xy().perp(&a.xy()) < T::zero()
199}
200
201#[must_use]
211pub fn calculate_pole<T: Float>(lat: Angle<T>, lon: Angle<T>, azi: Angle<T>) -> Vector3<T> {
212 let x = trig::UnitNegRange::<T>::clamp(
213 lon.sin().0 * azi.cos().0 - lat.sin().0 * lon.cos().0 * azi.sin().0,
214 );
215 let y = trig::UnitNegRange::<T>::clamp(
216 T::zero() - lon.cos().0 * azi.cos().0 - lat.sin().0 * lon.sin().0 * azi.sin().0,
217 );
218 let z = trig::UnitNegRange::<T>(lat.cos().0 * azi.sin().0);
219
220 Vector3::new(x.0, y.0, z.0)
221}
222
223#[must_use]
230pub fn calculate_azimuth<T>(point: &Vector3<T>, pole: &Vector3<T>) -> Angle<T>
231where
232 T: Float + na::Scalar + na::ComplexField<RealField = T>,
233{
234 let max_lat = T::one() - (T::epsilon() + T::epsilon());
235
236 let sin_lat: T = point[2];
237 if max_lat <= Float::abs(sin_lat) {
239 return if sin_lat.is_sign_negative() {
241 Angle::default()
242 } else {
243 Angle::new(trig::UnitNegRange(T::zero()), trig::UnitNegRange(-T::one()))
244 };
245 }
246
247 Angle::from_y_x(pole[2], pole.xy().perp(&point.xy()))
248}
249
250#[must_use]
261pub fn calculate_direction<T: Float>(lat: Angle<T>, lon: Angle<T>, azi: Angle<T>) -> Vector3<T> {
262 let x = trig::UnitNegRange::clamp(
263 T::zero() - lat.sin().0 * lon.cos().0 * azi.cos().0 - lon.sin().0 * azi.sin().0,
264 );
265 let y = trig::UnitNegRange::clamp(
266 T::zero() - lat.sin().0 * lon.sin().0 * azi.cos().0 + lon.cos().0 * azi.sin().0,
267 );
268 let z = trig::UnitNegRange(lat.cos().0 * azi.cos().0);
269
270 Vector3::new(x.0, y.0, z.0)
271}
272
273#[must_use]
280pub fn direction<T>(a: &Vector3<T>, pole: &Vector3<T>) -> Vector3<T>
281where
282 T: Float + na::Scalar + na::ComplexField<RealField = T>,
283{
284 pole.cross(a)
285}
286
287#[must_use]
295pub fn position<T>(a: &Vector3<T>, dir: &Vector3<T>, distance: Angle<T>) -> Vector3<T>
296where
297 T: Float + na::Scalar + na::ComplexField<RealField = T>,
298{
299 a * distance.cos().0 + dir * distance.sin().0
300}
301
302#[must_use]
311pub fn rotate<T>(dir: &Vector3<T>, pole: &Vector3<T>, angle: Angle<T>) -> Vector3<T>
312where
313 T: Float + na::Scalar + na::ComplexField<RealField = T>,
314{
315 position(dir, pole, angle)
316}
317
318#[must_use]
327pub fn rotate_position<T>(
328 a: &Vector3<T>,
329 pole: &Vector3<T>,
330 angle: Angle<T>,
331 radius: Angle<T>,
332) -> Vector3<T>
333where
334 T: Float + na::Scalar + na::ComplexField<RealField = T>,
335{
336 position(a, &rotate(&direction(a, pole), pole, angle), radius)
337}
338
339#[must_use]
347fn sin_xtd<T>(pole: &Vector3<T>, point: &Vector3<T>) -> trig::UnitNegRange<T>
348where
349 T: Float + na::Scalar + na::ComplexField<RealField = T>,
350{
351 trig::UnitNegRange::clamp(pole.dot(point))
352}
353
354#[must_use]
363pub fn is_right_of<T>(pole: &Vector3<T>, point: &Vector3<T>) -> bool
364where
365 T: Float + na::Scalar + na::ComplexField<RealField = T>,
366{
367 pole.dot(point) < T::zero()
368}
369
370#[must_use]
377pub fn cross_track_distance<T>(pole: &Vector3<T>, point: &Vector3<T>) -> Radians<T>
378where
379 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
380{
381 let sin_d = sin_xtd(pole, point);
382 if Float::abs(sin_d.0) < T::epsilon() {
383 Radians(T::zero())
384 } else {
385 Radians(Float::asin(sin_d.0))
386 }
387}
388
389#[must_use]
397pub fn sq_cross_track_distance<T>(pole: &Vector3<T>, point: &Vector3<T>) -> T
398where
399 T: Float + na::Scalar + na::ComplexField<RealField = T>,
400{
401 let two = T::one() + T::one();
402 let sin_d = sin_xtd(pole, point);
403 if Float::abs(sin_d.0) < T::epsilon() {
404 T::zero()
405 } else {
406 two * (T::one() - trig::swap_sin_cos(sin_d).0)
407 }
408}
409
410#[must_use]
418fn calculate_point_on_plane<T>(pole: &Vector3<T>, point: &Vector3<T>) -> Vector3<T>
419where
420 T: Float + na::Scalar + na::ComplexField<RealField = T>,
421{
422 let t = sin_xtd(pole, point);
423 point - pole * t.0
424}
425
426#[must_use]
437fn sin_atd<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> trig::UnitNegRange<T>
438where
439 T: Float + na::Scalar + na::ComplexField<RealField = T>,
440{
441 trig::UnitNegRange::clamp(pole.cross(a).dot(point))
442}
443
444#[must_use]
453pub fn calculate_great_circle_atd<T>(
454 a: &Vector3<T>,
455 pole: &Vector3<T>,
456 point: &Vector3<T>,
457) -> Radians<T>
458where
459 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
460{
461 let min_distance = T::epsilon() + T::epsilon();
462 let min_sq_distance = min_distance * min_distance;
463
464 let sq_atd = sq_distance(a, point);
465 if sq_atd < min_sq_distance {
466 Radians(T::zero())
467 } else {
468 Radians(
469 great_circle::e2gc_distance(Float::sqrt(sq_atd))
470 .0
471 .copysign(sin_atd(a, pole, point).0),
472 )
473 }
474}
475
476#[allow(clippy::missing_panics_doc)]
485#[must_use]
486pub fn along_track_distance<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> Radians<T>
487where
488 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
489 f64: From<T>,
490{
491 let min_angle_multiple = if f64::from(T::epsilon()) > f64::epsilon() {
492 MIN_SIN_MULTIPLE_F32
493 } else {
494 MIN_SIN_MULTIPLE
495 };
496 let min_angle_multiple =
497 T::from(min_angle_multiple).expect("Could not convert constant to Float");
498 let min_sin_angle = min_angle_multiple * T::epsilon();
499 let min_sq_norm = min_sin_angle * min_sin_angle;
500
501 let plane_point = calculate_point_on_plane(pole, point);
502 normalise(&plane_point, min_sq_norm).map_or_else(
503 || Radians(T::zero()), |c| calculate_great_circle_atd(a, pole, &c),
505 )
506}
507
508#[allow(clippy::missing_panics_doc)]
518#[must_use]
519pub fn sq_along_track_distance<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> T
520where
521 T: Float + na::Scalar + na::ComplexField<RealField = T>,
522 f64: From<T>,
523{
524 let min_distance = T::epsilon() + T::epsilon();
525 let min_sq_distance = min_distance * min_distance;
526
527 let min_angle_multiple = if f64::from(T::epsilon()) > f64::epsilon() {
528 MIN_SIN_MULTIPLE_F32
529 } else {
530 MIN_SIN_MULTIPLE
531 };
532 let min_angle_multiple =
533 T::from(min_angle_multiple).expect("Could not convert constant to Float");
534 let min_sin_angle = min_angle_multiple * T::epsilon();
535 let min_sq_norm = min_sin_angle * min_sin_angle;
536
537 let plane_point = calculate_point_on_plane(pole, point);
538 normalise(&plane_point, min_sq_norm).map_or_else(
539 || T::zero(), |c| {
541 let sq_d = sq_distance(a, &(c));
542 if sq_d < min_sq_distance {
543 T::zero()
544 } else {
545 sq_d
546 }
547 },
548 )
549}
550
551#[allow(clippy::missing_panics_doc)]
560#[allow(clippy::similar_names)]
561#[must_use]
562pub fn calculate_atd_and_xtd<T>(
563 a: &Vector3<T>,
564 pole: &Vector3<T>,
565 p: &Vector3<T>,
566) -> (Radians<T>, Radians<T>)
567where
568 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
569 f64: From<T>,
570{
571 let min_distance = T::epsilon() + T::epsilon();
572 let min_sq_distance = min_distance * min_distance;
573
574 let min_angle_multiple = if f64::from(T::epsilon()) > f64::epsilon() {
575 MIN_SIN_MULTIPLE_F32
576 } else {
577 MIN_SIN_MULTIPLE
578 };
579 let min_angle_multiple =
580 T::from(min_angle_multiple).expect("Could not convert constant to Float");
581 let min_sin_angle = min_angle_multiple * T::epsilon();
582 let min_sq_norm = min_sin_angle * min_sin_angle;
583
584 let mut atd = Radians(T::zero());
585 let mut xtd = Radians(T::zero());
586
587 let sq_d = sq_distance(a, p);
588 if sq_d >= min_sq_distance {
589 let sin_xtd = sin_xtd(pole, p).0;
591 if Float::abs(sin_xtd) >= T::epsilon() {
592 xtd = Radians(Float::asin(sin_xtd));
593 }
594
595 let plane_point = p - pole * sin_xtd;
597 atd = normalise(&plane_point, min_sq_norm).map_or_else(
598 || Radians(T::zero()), |c| calculate_great_circle_atd(a, pole, &c),
600 );
601 }
602
603 (atd, xtd)
604}
605
606#[allow(clippy::missing_panics_doc)]
616#[must_use]
617pub fn normalise_centroid<T>(
618 centroid: &Vector3<T>,
619 point: &Vector3<T>,
620 pole: &Vector3<T>,
621) -> Vector3<T>
622where
623 T: Float + na::Scalar + na::ComplexField<RealField = T>,
624 f64: From<T>,
625{
626 let min_angle_multiple = if f64::from(T::epsilon()) > f64::epsilon() {
627 MIN_SIN_MULTIPLE_F32
628 } else {
629 MIN_SIN_MULTIPLE
630 };
631 let min_angle_multiple =
632 T::from(min_angle_multiple).expect("Could not convert constant to Float");
633 let min_sin_angle = min_angle_multiple * T::epsilon();
634 let min_sq_norm = min_sin_angle * min_sin_angle;
635
636 normalise(centroid, min_sq_norm).unwrap_or_else(|| {
637 position(
642 point,
643 &direction(point, pole),
644 Angle::default().quarter_turn_ccw(),
645 )
646 })
647}
648
649#[cfg(test)]
650mod tests {
651 use core::f64;
652
653 use super::*;
654 use crate::LatLong;
655 use angle_sc::{Degrees, Radians, is_within_tolerance};
656
657 pub const MIN_SIN_ANGLE: f64 = (MIN_SIN_MULTIPLE as f64) * f64::EPSILON;
658 pub const MIN_SQ_NORM: f64 = MIN_SIN_ANGLE * MIN_SIN_ANGLE;
659
660 #[test]
661 fn test_normalise() {
662 let zero = Vector3::new(0.0, 0.0, 0.0);
663 assert!(normalise(&zero, MIN_SQ_NORM).is_none());
664
665 let g_eq = Vector3::new(1.0, 0.0, 0.0);
667 assert!(normalise(&g_eq, MIN_SQ_NORM).is_some());
668
669 let too_small = Vector3::new(16383.0 * f64::EPSILON, 0.0, 0.0);
671 assert!(normalise(&too_small, MIN_SQ_NORM).is_none());
672
673 assert_eq!(2.0844083160439303e-10, MIN_SIN_ANGLE.to_degrees().asin());
674
675 let small = Vector3::new(MIN_SIN_ANGLE, 0.0, 0.0);
677 let result = normalise(&small, MIN_SQ_NORM);
678 assert!(result.is_some());
679
680 assert!(is_unit(&result.unwrap()));
681 assert_eq!(result.unwrap(), g_eq);
682 }
683
684 #[test]
685 fn test_point_lat_longs() {
686 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(180.0));
688 let point_south = Vector3::from(&lat_lon_south);
689 assert!(is_unit(&point_south));
690 assert_eq!(Vector3::new(0.0, 0.0, -1.0), point_south);
691
692 assert_eq!(Degrees(-90.0), Degrees::from(latitude(&point_south)));
693 assert_eq!(Degrees(0.0), Degrees::from(longitude(&point_south)));
694
695 let result = LatLong::from(&point_south);
696 assert_eq!(-90.0, result.lat().0);
697 assert_eq!(0.0, result.lon().0);
699
700 let lat_lon_0_0 = LatLong::new(Degrees(0.0), Degrees(0.0));
702 let point_0 = Vector3::from(&lat_lon_0_0);
703 assert!(is_unit(&point_0));
704 assert_eq!(Vector3::new(1.0, 0.0, 0.0), point_0);
705 assert_eq!(lat_lon_0_0, LatLong::from(&point_0));
706
707 let lat_lon_0_180 = LatLong::new(Degrees(0.0), Degrees(180.0));
709 let point_1 = Vector3::from(&lat_lon_0_180);
710 assert!(is_unit(&point_1));
711 assert_eq!(Vector3::new(-1.0, 0.0, 0.0), point_1);
712 assert_eq!(false, is_west_of(&point_0, &point_1));
713 assert_eq!(
714 Radians(core::f64::consts::PI),
715 Radians::from(delta_longitude(&point_0, &point_1)).abs()
716 );
717
718 let lat_lon_0_m180 = LatLong::new(Degrees(0.0), Degrees(-180.0));
719 let point_2 = Vector3::from(&lat_lon_0_m180);
720 assert!(is_unit(&point_2));
721 assert_eq!(Vector3::new(-1.0, 0.0, 0.0), point_2);
722 assert_eq!(lat_lon_0_180, LatLong::from(&point_2));
724
725 assert_eq!(false, is_west_of(&point_0, &point_2));
726 assert_eq!(
727 -core::f64::consts::PI,
728 Radians::from(delta_longitude(&point_0, &point_2)).0
729 );
730
731 let lat_lon_0_r3 = LatLong::new(Degrees(0.0), Degrees(3.0_f64.to_degrees()));
732 let point_3 = Vector3::from(&lat_lon_0_r3);
733 assert!(is_unit(&point_3));
734 let result = LatLong::from(&point_3);
735 assert_eq!(0.0, result.lat().0);
736 assert_eq!(
737 3.0_f64,
738 Radians::from(delta_longitude(&point_3, &point_0)).0
739 );
740 assert_eq!(3.0_f64.to_degrees(), result.lon().0);
741 assert!(is_west_of(&point_0, &point_3));
742 assert_eq!(-3.0, Radians::from(delta_longitude(&point_0, &point_3)).0);
743
744 assert_eq!(false, is_west_of(&point_1, &point_3));
745 assert!(is_within_tolerance(
746 core::f64::consts::PI - 3.0,
747 Radians::from(delta_longitude(&point_1, &point_3)).0,
748 f64::EPSILON
749 ));
750
751 let lat_lon_0_mr3 = LatLong::new(Degrees(0.0), Degrees(-3.0_f64.to_degrees()));
752 let point_4 = Vector3::from(&lat_lon_0_mr3);
753 assert!(is_unit(&point_4));
754 assert_eq!(3.0, Radians::from(delta_longitude(&point_0, &point_4)).0);
755
756 let result = LatLong::from(&point_4);
757 assert_eq!(0.0, result.lat().0);
758 assert_eq!(-3.0_f64.to_degrees(), result.lon().0);
759 assert!(is_west_of(&point_1, &point_4));
760 assert!(is_within_tolerance(
761 3.0 - core::f64::consts::PI,
762 Radians::from(delta_longitude(&point_1, &point_4)).0,
763 f64::EPSILON
764 ));
765 }
766
767 #[test]
768 fn test_point_distance() {
769 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(0.0));
770 let south_pole = Vector3::from(&lat_lon_south);
771
772 let lat_lon_north = LatLong::new(Degrees(90.0), Degrees(0.0));
773 let north_pole = Vector3::from(&lat_lon_north);
774
775 assert_eq!(0.0, sq_distance(&south_pole, &south_pole));
776 assert_eq!(0.0, sq_distance(&north_pole, &north_pole));
777 assert_eq!(4.0, sq_distance(&south_pole, &north_pole));
778
779 assert_eq!(0.0, distance(&south_pole, &south_pole));
780 assert_eq!(0.0, distance(&north_pole, &north_pole));
781 assert_eq!(2.0, distance(&south_pole, &north_pole));
782
783 let g_eq = Vector3::new(1.0, 0.0, 0.0);
785
786 let idl_eq = Vector3::new(-1.0, 0.0, 0.0);
788
789 assert_eq!(0.0, sq_distance(&g_eq, &g_eq));
790 assert_eq!(0.0, sq_distance(&idl_eq, &idl_eq));
791 assert_eq!(4.0, sq_distance(&g_eq, &idl_eq));
792
793 assert_eq!(0.0, distance(&g_eq, &g_eq));
794 assert_eq!(0.0, distance(&idl_eq, &idl_eq));
795 assert_eq!(2.0, distance(&g_eq, &idl_eq));
796 }
797
798 #[test]
799 fn test_calculate_azimuth_at_poles() {
800 let g_eq = Vector3::new(1.0, 0.0, 0.0);
802 let south_pole = Vector3::new(0.0, 0.0, -1.0);
803 let result = calculate_azimuth(&south_pole, &g_eq);
804 assert_eq!(Angle::default(), result);
805
806 let north_pole = Vector3::new(0.0, 0.0, 1.0);
807 let result = calculate_azimuth(&north_pole, &g_eq);
808 assert_eq!(Angle::default().opposite(), result);
809 }
810
811 #[test]
812 fn test_calculate_pole_azimuth_and_direction() {
813 let g_eq = Vector3::new(1.0, 0.0, 0.0);
815
816 let e_eq = Vector3::new(0.0, 1.0, 0.0);
818
819 let w_eq = Vector3::new(0.0, -1.0, 0.0);
821
822 let angle_90 = Angle::from(Degrees(90.0));
823 let pole_a = calculate_pole(
824 Angle::from(Degrees(0.0)),
825 Angle::from(Degrees(0.0)),
826 angle_90,
827 );
828 assert!(are_orthogonal(&g_eq, &pole_a));
829
830 let dir_a = calculate_direction(
831 Angle::from(Degrees(0.0)),
832 Angle::from(Degrees(0.0)),
833 angle_90,
834 );
835 assert!(are_orthogonal(&g_eq, &dir_a));
836 assert!(are_orthogonal(&pole_a, &dir_a));
837 assert_eq!(dir_a, direction(&g_eq, &pole_a));
838
839 let north_pole = Vector3::new(0.0, 0.0, 1.0);
840 assert_eq!(north_pole, pole_a);
841
842 let result = g_eq.cross(&e_eq);
843 assert_eq!(north_pole, result);
844
845 let result = calculate_azimuth(&g_eq, &pole_a);
846 assert_eq!(angle_90, result);
847
848 let pole_b = calculate_pole(
849 Angle::from(Degrees(0.0)),
850 Angle::from(Degrees(0.0)),
851 -angle_90,
852 );
853 assert!(are_orthogonal(&g_eq, &pole_b));
854
855 let dir_b = calculate_direction(
856 Angle::from(Degrees(0.0)),
857 Angle::from(Degrees(0.0)),
858 -angle_90,
859 );
860 assert!(are_orthogonal(&g_eq, &dir_b));
861 assert!(are_orthogonal(&pole_b, &dir_b));
862 assert_eq!(dir_b, direction(&g_eq, &pole_b));
863
864 let south_pole = Vector3::new(0.0, 0.0, -1.0);
865 assert_eq!(south_pole, pole_b);
866
867 let result = g_eq.cross(&w_eq);
868 assert_eq!(south_pole, result);
869
870 let result = calculate_azimuth(&g_eq, &pole_b);
871 assert_eq!(-angle_90, result);
872 }
873
874 #[test]
875 fn test_calculate_position() {
876 let g_eq = Vector3::new(1.0, 0.0, 0.0);
878
879 let e_eq = Vector3::new(0.0, 1.0, 0.0);
881
882 let pole_0 = g_eq.cross(&e_eq);
883
884 let angle_90 = Angle::from(Degrees(90.0));
885
886 let pos_1 = position(&g_eq, &direction(&g_eq, &pole_0), angle_90);
887 assert_eq!(e_eq, pos_1);
888
889 let pos_2 = rotate_position(&g_eq, &pole_0, Angle::default(), angle_90);
890 assert_eq!(e_eq, pos_2);
891
892 let pos_3 = rotate_position(&g_eq, &pole_0, angle_90, angle_90);
893 assert_eq!(pole_0, pos_3);
894 }
895
896 #[test]
897 fn test_calculate_cross_track_distance_and_square() {
898 let g_eq = Vector3::new(1.0, 0.0, 0.0);
900
901 let e_eq = Vector3::new(0.0, 1.0, 0.0);
903
904 let pole_0 = g_eq.cross(&e_eq);
905
906 let longitude = Degrees(1.0);
907
908 for lat in -89..90 {
909 let latitude = Degrees(f64::from(lat));
910 let latlong = LatLong::new(latitude, longitude);
911 let point = Vector3::from(&latlong);
912
913 assert_eq!(lat < 0, is_south_of(&point, &g_eq));
914 assert_eq!(lat >= 0, !is_south_of(&point, &e_eq));
915 assert_eq!(lat < 0, is_right_of(&pole_0, &point));
916
917 let expected = (f64::from(lat)).to_radians();
918 let xtd = cross_track_distance(&pole_0, &point);
919 let tolerance = if (-83..84).contains(&lat) {
921 2.0 * f64::EPSILON
922 } else {
923 32.0 * f64::EPSILON
924 };
925 assert!(is_within_tolerance(expected, xtd.0, tolerance));
926
927 let expected = great_circle::gc2e_distance(Radians(expected));
928 let expected = expected * expected;
929 let xtd2 = sq_cross_track_distance(&pole_0, &point);
930 let tolerance = if (-83..84).contains(&lat) {
932 4.0 * f64::EPSILON
933 } else {
934 64.0 * f64::EPSILON
935 };
936 assert!(is_within_tolerance(expected, xtd2, tolerance));
937 }
938 }
939
940 #[test]
941 fn test_calculate_along_track_distance_and_square() {
942 let g_eq = Vector3::new(1.0, 0.0, 0.0);
944
945 let e_eq = Vector3::new(0.0, 1.0, 0.0);
947
948 let pole_0 = g_eq.cross(&e_eq);
949
950 let latitude = Degrees(1.0);
952
953 for lon in -179..180 {
954 let longitude = Degrees(f64::from(lon));
955 let latlong = LatLong::new(latitude, longitude);
956 let point = Vector3::from(&latlong);
957
958 let expected = (f64::from(lon)).to_radians();
959 let atd = along_track_distance(&g_eq, &pole_0, &point);
960 let tolerance = if (-153..154).contains(&lon) {
962 4.0 * f64::EPSILON
963 } else {
964 32.0 * f64::EPSILON
965 };
966 assert!(is_within_tolerance(expected, atd.0, tolerance));
967
968 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
969 assert!(is_within_tolerance(expected, atd.0, tolerance));
970 assert!(is_within_tolerance(1_f64.to_radians(), xtd.0, f64::EPSILON));
971
972 let expected = great_circle::gc2e_distance(Radians(expected));
973 let expected = expected * expected;
974 let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
975 let tolerance = if (-86..87).contains(&lon) {
977 2.0 * f64::EPSILON
978 } else {
979 32.0 * f64::EPSILON
980 };
981 assert!(is_within_tolerance(expected, atd2, tolerance));
982 }
983 }
984
985 #[test]
986 fn test_calculate_along_track_distance_and_square_f32() {
987 let g_eq = Vector3::new(1.0_f32, 0.0_f32, 0.0_f32);
989
990 let e_eq = Vector3::new(0.0_f32, 1.0_f32, 0.0_f32);
992
993 let pole_0 = g_eq.cross(&e_eq);
994
995 let latitude = Degrees(1.0_f32);
997
998 for lon in -179..180 {
999 let longitude = lon as f32;
1000 let latlong = LatLong::new(latitude, Degrees(longitude));
1001 let point = Vector3::from(&latlong);
1002
1003 let expected = longitude.to_radians();
1004 let atd = along_track_distance(&g_eq, &pole_0, &point);
1005 let tolerance = if (-153..154).contains(&lon) {
1007 4.0 * f32::EPSILON
1008 } else {
1009 32.0 * f32::EPSILON
1010 };
1011 assert!(is_within_tolerance(expected, atd.0, tolerance));
1012
1013 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
1014 assert!(is_within_tolerance(expected, atd.0, tolerance));
1015 assert!(is_within_tolerance(
1016 1.0_f32.to_radians(),
1017 xtd.0,
1018 f32::EPSILON
1019 ));
1020
1021 let expected = great_circle::gc2e_distance(Radians(expected));
1022 let expected = expected * expected;
1023 let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
1024 let tolerance = if (-86..87).contains(&lon) {
1026 2.0 * f32::EPSILON
1027 } else {
1028 8.0 * f32::EPSILON
1029 };
1030 assert!(is_within_tolerance(expected, atd2, tolerance));
1031 }
1032 }
1033
1034 #[test]
1035 fn test_special_cases() {
1036 let g_eq = Vector3::new(1.0, 0.0, 0.0);
1038
1039 let e_eq = Vector3::new(0.0, 1.0, 0.0);
1041
1042 let pole_0 = g_eq.cross(&e_eq);
1043
1044 assert_eq!(0.0, along_track_distance(&g_eq, &pole_0, &pole_0).0);
1046 assert_eq!(0.0, sq_along_track_distance(&g_eq, &pole_0, &pole_0));
1047
1048 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &g_eq);
1049 assert_eq!(0.0, atd.0);
1050 assert_eq!(0.0, xtd.0);
1051
1052 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &pole_0);
1053 assert_eq!(0.0, atd.0);
1054 assert_eq!(core::f64::consts::FRAC_PI_2, xtd.0);
1055
1056 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &-pole_0);
1057 assert_eq!(0.0, atd.0);
1058 assert_eq!(-core::f64::consts::FRAC_PI_2, xtd.0);
1059
1060 let near_north_pole = LatLong::new(Degrees(89.99999), Degrees(0.0));
1062 let p = Vector3::from(&near_north_pole);
1063 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &p);
1064 assert_eq!(0.0, atd.0);
1065 assert!(is_within_tolerance(
1066 core::f64::consts::FRAC_PI_2,
1067 xtd.0,
1068 0.000001
1069 ));
1070 }
1071
1072 #[test]
1073 fn test_normalise_centroid() {
1074 let point_0 = Vector3::new(0.0, 0.0, 0.0);
1075 let point_1 = Vector3::new(1.0, 0.0, 0.0);
1076 let point_m1 = -point_1;
1077 let pole_1 = Vector3::new(0.0, 0.0, 1.0);
1078
1079 let result = normalise_centroid(&point_0, &point_1, &pole_1);
1081 assert_eq!(Vector3::new(0.0, -1.0, 0.0), result);
1082
1083 let result = normalise_centroid(&point_0, &point_m1, &pole_1);
1085 assert_eq!(Vector3::new(0.0, 1.0, 0.0), result);
1086
1087 let point_2 = point_1 + point_1;
1089 let result = normalise_centroid(&point_2, &point_1, &pole_1);
1090 assert_eq!(point_1, result);
1091 }
1092
1093 #[test]
1094 fn test_normalise_centroid_f32() {
1095 let point_0 = Vector3::new(0.0_f32, 0.0_f32, 0.0_f32);
1096 let point_1 = Vector3::new(1.0_f32, 0.0_f32, 0.0_f32);
1097 let point_m1 = -point_1;
1098 let pole_1 = Vector3::new(0.0_f32, 0.0_f32, 1.0_f32);
1099
1100 let result = normalise_centroid(&point_0, &point_1, &pole_1);
1102 assert_eq!(Vector3::new(0.0_f32, -1.0_f32, 0.0_f32), result);
1103
1104 let result = normalise_centroid(&point_0, &point_m1, &pole_1);
1106 assert_eq!(Vector3::new(0.0_f32, 1.0_f32, 0.0_f32), result);
1107
1108 let point_2 = point_1 + point_1;
1110 let result = normalise_centroid(&point_2, &point_1, &pole_1);
1111 assert_eq!(point_1, result);
1112 }
1113}