1extern crate nalgebra as na;
28
29use crate::{Vector3, great_circle};
30use angle_sc::{Angle, Radians, trig};
31use core::mem::size_of;
32use num_traits::{Float, float::FloatConst};
33
34pub mod intersection;
35
36pub const MIN_SIN_MULTIPLE: u32 = 16384;
39
40pub const MIN_SIN_MULTIPLE_F32: u32 = 4096;
42
43#[allow(clippy::missing_panics_doc)]
45#[must_use]
46pub fn get_min_sin_angle<T: Float>() -> T {
47 let min_angle_multiple = if size_of::<T>() < size_of::<f64>() {
48 MIN_SIN_MULTIPLE_F32
49 } else {
50 MIN_SIN_MULTIPLE
51 };
52 let min_angle_multiple =
53 T::from(min_angle_multiple).expect("Could not convert constant to Float");
54 min_angle_multiple * T::epsilon()
55}
56
57#[must_use]
65pub fn to_point<T: Float>(lat: Angle<T>, lon: Angle<T>) -> Vector3<T> {
66 Vector3::<T>::new(
67 lat.cos().0 * lon.cos().0,
68 lat.cos().0 * lon.sin().0,
69 lat.sin().0,
70 )
71}
72
73#[must_use]
79pub fn latitude<T: Float>(a: &Vector3<T>) -> Angle<T> {
80 Angle::from_y_x(a[2], a[0].hypot(a[1]))
81}
82
83#[must_use]
89pub fn longitude<T: Float>(a: &Vector3<T>) -> Angle<T> {
90 Angle::from_y_x(a[1], a[0])
91}
92
93#[allow(clippy::missing_panics_doc)]
99#[must_use]
100pub fn is_unit<T>(a: &Vector3<T>) -> bool
101where
102 T: Float + na::Scalar + na::ComplexField<RealField = T>,
103{
104 let twelve = T::from(12).expect("Could not convert constant to Float");
105 let min_sq_length = T::one() - twelve * T::epsilon();
106 let max_sq_length = T::one() + twelve * T::epsilon();
107
108 (min_sq_length..=max_sq_length).contains(&(a.norm_squared()))
109}
110
111#[must_use]
120pub fn normalise<T>(a: &Vector3<T>, min_sq_value: T) -> Option<Vector3<T>>
121where
122 T: Float + na::Scalar + na::ComplexField<RealField = T>,
123{
124 if a.norm_squared() < min_sq_value {
125 None
126 } else {
127 Some(a.normalize())
128 }
129}
130
131#[must_use]
139pub fn sq_distance<T>(a: &Vector3<T>, b: &Vector3<T>) -> T
140where
141 T: Float + na::Scalar + na::ComplexField<RealField = T>,
142{
143 (b - a).norm_squared()
144}
145
146#[must_use]
153pub fn distance<T>(a: &Vector3<T>, b: &Vector3<T>) -> T
154where
155 T: Float + na::Scalar + na::ComplexField<RealField = T>,
156{
157 (b - a).norm()
158}
159
160#[must_use]
166pub fn are_orthogonal<T>(a: &Vector3<T>, b: &Vector3<T>) -> bool
167where
168 T: Float + na::Scalar + na::ComplexField<RealField = T>,
169{
170 let two_epsilon = T::epsilon() + T::epsilon();
171 let max_length = two_epsilon + two_epsilon;
172
173 (-max_length..=max_length).contains(&(a.dot(b)))
174}
175
176#[must_use]
183pub fn delta_longitude<T>(a: &Vector3<T>, b: &Vector3<T>) -> Angle<T>
184where
185 T: Float + na::Scalar + na::ComplexField<RealField = T>,
186{
187 let a_lon = a.xy();
188 let b_lon = b.xy();
189 Angle::from_y_x(b_lon.perp(&a_lon), b_lon.dot(&a_lon))
190}
191
192#[must_use]
199pub fn is_south_of<T: Float>(a: &Vector3<T>, b: &Vector3<T>) -> bool {
200 a[2] < b[2]
201}
202#[must_use]
209pub fn is_west_of<T>(a: &Vector3<T>, b: &Vector3<T>) -> bool
210where
211 T: Float + na::Scalar + na::ComplexField<RealField = T>,
212{
213 b.xy().perp(&a.xy()) < T::zero()
214}
215
216#[must_use]
226pub fn calculate_pole<T: Float>(lat: Angle<T>, lon: Angle<T>, azi: Angle<T>) -> Vector3<T> {
227 let x = trig::UnitNegRange::<T>::clamp(
228 lon.sin().0 * azi.cos().0 - lat.sin().0 * lon.cos().0 * azi.sin().0,
229 );
230 let y = trig::UnitNegRange::<T>::clamp(
231 T::zero() - lon.cos().0 * azi.cos().0 - lat.sin().0 * lon.sin().0 * azi.sin().0,
232 );
233 let z = trig::UnitNegRange::<T>(lat.cos().0 * azi.sin().0);
234
235 Vector3::new(x.0, y.0, z.0)
236}
237
238#[must_use]
245pub fn calculate_azimuth<T>(point: &Vector3<T>, pole: &Vector3<T>) -> Angle<T>
246where
247 T: Float + na::Scalar + na::ComplexField<RealField = T>,
248{
249 let max_lat = T::one() - (T::epsilon() + T::epsilon());
250
251 let sin_lat: T = point[2];
252 if max_lat <= Float::abs(sin_lat) {
254 return if sin_lat.is_sign_negative() {
256 Angle::default()
257 } else {
258 Angle::new(trig::UnitNegRange(T::zero()), trig::UnitNegRange(-T::one()))
259 };
260 }
261
262 Angle::from_y_x(pole[2], pole.xy().perp(&point.xy()))
263}
264
265#[must_use]
276pub fn calculate_direction<T: Float>(lat: Angle<T>, lon: Angle<T>, azi: Angle<T>) -> Vector3<T> {
277 let x = trig::UnitNegRange::clamp(
278 T::zero() - lat.sin().0 * lon.cos().0 * azi.cos().0 - lon.sin().0 * azi.sin().0,
279 );
280 let y = trig::UnitNegRange::clamp(
281 T::zero() - lat.sin().0 * lon.sin().0 * azi.cos().0 + lon.cos().0 * azi.sin().0,
282 );
283 let z = trig::UnitNegRange(lat.cos().0 * azi.cos().0);
284
285 Vector3::new(x.0, y.0, z.0)
286}
287
288#[must_use]
295pub fn direction<T>(a: &Vector3<T>, pole: &Vector3<T>) -> Vector3<T>
296where
297 T: Float + na::Scalar + na::ComplexField<RealField = T>,
298{
299 pole.cross(a)
300}
301
302#[must_use]
310pub fn position<T>(a: &Vector3<T>, dir: &Vector3<T>, distance: Angle<T>) -> Vector3<T>
311where
312 T: Float + na::Scalar + na::ComplexField<RealField = T>,
313{
314 a * distance.cos().0 + dir * distance.sin().0
315}
316
317#[must_use]
326pub fn rotate<T>(dir: &Vector3<T>, pole: &Vector3<T>, angle: Angle<T>) -> Vector3<T>
327where
328 T: Float + na::Scalar + na::ComplexField<RealField = T>,
329{
330 position(dir, pole, angle)
331}
332
333#[must_use]
342pub fn rotate_position<T>(
343 a: &Vector3<T>,
344 pole: &Vector3<T>,
345 angle: Angle<T>,
346 radius: Angle<T>,
347) -> Vector3<T>
348where
349 T: Float + na::Scalar + na::ComplexField<RealField = T>,
350{
351 position(a, &rotate(&direction(a, pole), pole, angle), radius)
352}
353
354#[must_use]
362fn sin_xtd<T>(pole: &Vector3<T>, point: &Vector3<T>) -> trig::UnitNegRange<T>
363where
364 T: Float + na::Scalar + na::ComplexField<RealField = T>,
365{
366 trig::UnitNegRange::clamp(pole.dot(point))
367}
368
369#[must_use]
378pub fn is_right_of<T>(pole: &Vector3<T>, point: &Vector3<T>) -> bool
379where
380 T: Float + na::Scalar + na::ComplexField<RealField = T>,
381{
382 pole.dot(point) < T::zero()
383}
384
385#[must_use]
392pub fn cross_track_distance<T>(pole: &Vector3<T>, point: &Vector3<T>) -> Radians<T>
393where
394 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
395{
396 let sin_d = sin_xtd(pole, point);
397 if Float::abs(sin_d.0) < T::epsilon() {
398 Radians(T::zero())
399 } else {
400 Radians(Float::asin(sin_d.0))
401 }
402}
403
404#[must_use]
412pub fn sq_cross_track_distance<T>(pole: &Vector3<T>, point: &Vector3<T>) -> T
413where
414 T: Float + na::Scalar + na::ComplexField<RealField = T>,
415{
416 let two = T::one() + T::one();
417 let sin_d = sin_xtd(pole, point);
418 if Float::abs(sin_d.0) < T::epsilon() {
419 T::zero()
420 } else {
421 two * (T::one() - trig::swap_sin_cos(sin_d).0)
422 }
423}
424
425#[must_use]
433fn calculate_point_on_plane<T>(pole: &Vector3<T>, point: &Vector3<T>) -> Vector3<T>
434where
435 T: Float + na::Scalar + na::ComplexField<RealField = T>,
436{
437 let t = sin_xtd(pole, point);
438 point - pole * t.0
439}
440
441#[must_use]
452pub fn sin_atd<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> trig::UnitNegRange<T>
453where
454 T: Float + na::Scalar + na::ComplexField<RealField = T>,
455{
456 trig::UnitNegRange::clamp(pole.cross(a).dot(point))
457}
458
459#[must_use]
468pub fn calculate_great_circle_atd<T>(
469 a: &Vector3<T>,
470 pole: &Vector3<T>,
471 point: &Vector3<T>,
472) -> Radians<T>
473where
474 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
475{
476 let min_distance = T::epsilon() + T::epsilon();
477 let min_sq_distance = min_distance * min_distance;
478
479 let sq_atd = sq_distance(a, point);
480 if sq_atd < min_sq_distance {
481 Radians(T::zero())
482 } else {
483 Radians(
484 great_circle::e2gc_distance(Float::sqrt(sq_atd))
485 .0
486 .copysign(sin_atd(a, pole, point).0),
487 )
488 }
489}
490
491#[allow(clippy::missing_panics_doc)]
500#[must_use]
501pub fn along_track_distance<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> Radians<T>
502where
503 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
504{
505 let min_sin_angle = get_min_sin_angle::<T>();
506 let min_sq_norm = min_sin_angle * min_sin_angle;
507
508 let plane_point = calculate_point_on_plane(pole, point);
509 normalise(&plane_point, min_sq_norm).map_or_else(
510 || Radians(T::zero()), |c| calculate_great_circle_atd(a, pole, &c),
512 )
513}
514
515#[allow(clippy::missing_panics_doc)]
525#[must_use]
526pub fn sq_along_track_distance<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> T
527where
528 T: Float + na::Scalar + na::ComplexField<RealField = T>,
529{
530 let min_distance = T::epsilon() + T::epsilon();
531 let min_sq_distance = min_distance * min_distance;
532
533 let min_sin_angle = get_min_sin_angle::<T>();
534 let min_sq_norm = min_sin_angle * min_sin_angle;
535
536 let plane_point = calculate_point_on_plane(pole, point);
537 normalise(&plane_point, min_sq_norm).map_or_else(
538 || T::zero(), |c| {
540 let sq_d = sq_distance(a, &(c));
541 if sq_d < min_sq_distance {
542 T::zero()
543 } else {
544 sq_d
545 }
546 },
547 )
548}
549
550#[allow(clippy::missing_panics_doc)]
559#[allow(clippy::similar_names)]
560#[must_use]
561pub fn calculate_atd_and_xtd<T>(
562 a: &Vector3<T>,
563 pole: &Vector3<T>,
564 p: &Vector3<T>,
565) -> (Radians<T>, Radians<T>)
566where
567 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
568{
569 let min_distance = T::epsilon() + T::epsilon();
570 let min_sq_distance = min_distance * min_distance;
571
572 let min_sin_angle = get_min_sin_angle::<T>();
573 let min_sq_norm = min_sin_angle * min_sin_angle;
574
575 let mut atd = Radians(T::zero());
576 let mut xtd = Radians(T::zero());
577
578 let sq_d = sq_distance(a, p);
579 if sq_d >= min_sq_distance {
580 let sin_xtd = sin_xtd(pole, p).0;
582 if Float::abs(sin_xtd) >= T::epsilon() {
583 xtd = Radians(Float::asin(sin_xtd));
584 }
585
586 let plane_point = p - pole * sin_xtd;
588 atd = normalise(&plane_point, min_sq_norm).map_or_else(
589 || Radians(T::zero()), |c| calculate_great_circle_atd(a, pole, &c),
591 );
592 }
593
594 (atd, xtd)
595}
596
597#[must_use]
607pub fn normalise_centroid<T>(
608 centroid: &Vector3<T>,
609 point: &Vector3<T>,
610 pole: &Vector3<T>,
611) -> Vector3<T>
612where
613 T: Float + na::Scalar + na::ComplexField<RealField = T>,
614{
615 let min_sin_angle = get_min_sin_angle::<T>();
616 let min_sq_norm = min_sin_angle * min_sin_angle;
617
618 normalise(centroid, min_sq_norm).unwrap_or_else(|| {
619 position(
624 point,
625 &direction(point, pole),
626 Angle::default().quarter_turn_ccw(),
627 )
628 })
629}
630
631#[cfg(test)]
632mod tests {
633 use core::f64;
634
635 use super::*;
636 use crate::LatLong;
637 use angle_sc::{Degrees, Radians, is_within_tolerance};
638
639 pub const MIN_SIN_ANGLE: f64 = (MIN_SIN_MULTIPLE as f64) * f64::EPSILON;
640 pub const MIN_SQ_NORM: f64 = MIN_SIN_ANGLE * MIN_SIN_ANGLE;
641
642 #[test]
643 fn test_normalise() {
644 let zero = Vector3::new(0.0, 0.0, 0.0);
645 assert!(normalise(&zero, MIN_SQ_NORM).is_none());
646
647 let g_eq = Vector3::new(1.0, 0.0, 0.0);
649 assert!(normalise(&g_eq, MIN_SQ_NORM).is_some());
650
651 let too_small = Vector3::new(16383.0 * f64::EPSILON, 0.0, 0.0);
653 assert!(normalise(&too_small, MIN_SQ_NORM).is_none());
654
655 assert_eq!(2.0844083160439303e-10, MIN_SIN_ANGLE.to_degrees().asin());
656
657 let small = Vector3::new(MIN_SIN_ANGLE, 0.0, 0.0);
659 let result = normalise(&small, MIN_SQ_NORM);
660 assert!(result.is_some());
661
662 assert!(is_unit(&result.unwrap()));
663 assert_eq!(result.unwrap(), g_eq);
664 }
665
666 #[test]
667 fn test_point_lat_longs() {
668 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(180.0));
670 let point_south = Vector3::from(&lat_lon_south);
671 assert!(is_unit(&point_south));
672 assert_eq!(Vector3::new(0.0, 0.0, -1.0), point_south);
673
674 assert_eq!(Degrees(-90.0), Degrees::from(latitude(&point_south)));
675 assert_eq!(Degrees(0.0), Degrees::from(longitude(&point_south)));
676
677 let result = LatLong::from(&point_south);
678 assert_eq!(-90.0, result.lat().0);
679 assert_eq!(0.0, result.lon().0);
681
682 let lat_lon_0_0 = LatLong::new(Degrees(0.0), Degrees(0.0));
684 let point_0 = Vector3::from(&lat_lon_0_0);
685 assert!(is_unit(&point_0));
686 assert_eq!(Vector3::new(1.0, 0.0, 0.0), point_0);
687 assert_eq!(lat_lon_0_0, LatLong::from(&point_0));
688
689 let lat_lon_0_180 = LatLong::new(Degrees(0.0), Degrees(180.0));
691 let point_1 = Vector3::from(&lat_lon_0_180);
692 assert!(is_unit(&point_1));
693 assert_eq!(Vector3::new(-1.0, 0.0, 0.0), point_1);
694 assert_eq!(false, is_west_of(&point_0, &point_1));
695 assert_eq!(
696 Radians(core::f64::consts::PI),
697 Radians::from(delta_longitude(&point_0, &point_1)).abs()
698 );
699
700 let lat_lon_0_m180 = LatLong::new(Degrees(0.0), Degrees(-180.0));
701 let point_2 = Vector3::from(&lat_lon_0_m180);
702 assert!(is_unit(&point_2));
703 assert_eq!(Vector3::new(-1.0, 0.0, 0.0), point_2);
704 assert_eq!(lat_lon_0_180, LatLong::from(&point_2));
706
707 assert_eq!(false, is_west_of(&point_0, &point_2));
708 assert_eq!(
709 -core::f64::consts::PI,
710 Radians::from(delta_longitude(&point_0, &point_2)).0
711 );
712
713 let lat_lon_0_r3 = LatLong::new(Degrees(0.0), Degrees(3.0_f64.to_degrees()));
714 let point_3 = Vector3::from(&lat_lon_0_r3);
715 assert!(is_unit(&point_3));
716 let result = LatLong::from(&point_3);
717 assert_eq!(0.0, result.lat().0);
718 assert_eq!(
719 3.0_f64,
720 Radians::from(delta_longitude(&point_3, &point_0)).0
721 );
722 assert_eq!(3.0_f64.to_degrees(), result.lon().0);
723 assert!(is_west_of(&point_0, &point_3));
724 assert_eq!(-3.0, Radians::from(delta_longitude(&point_0, &point_3)).0);
725
726 assert_eq!(false, is_west_of(&point_1, &point_3));
727 assert!(is_within_tolerance(
728 core::f64::consts::PI - 3.0,
729 Radians::from(delta_longitude(&point_1, &point_3)).0,
730 f64::EPSILON
731 ));
732
733 let lat_lon_0_mr3 = LatLong::new(Degrees(0.0), Degrees(-3.0_f64.to_degrees()));
734 let point_4 = Vector3::from(&lat_lon_0_mr3);
735 assert!(is_unit(&point_4));
736 assert_eq!(3.0, Radians::from(delta_longitude(&point_0, &point_4)).0);
737
738 let result = LatLong::from(&point_4);
739 assert_eq!(0.0, result.lat().0);
740 assert_eq!(-3.0_f64.to_degrees(), result.lon().0);
741 assert!(is_west_of(&point_1, &point_4));
742 assert!(is_within_tolerance(
743 3.0 - core::f64::consts::PI,
744 Radians::from(delta_longitude(&point_1, &point_4)).0,
745 f64::EPSILON
746 ));
747 }
748
749 #[test]
750 fn test_point_distance() {
751 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(0.0));
752 let south_pole = Vector3::from(&lat_lon_south);
753
754 let lat_lon_north = LatLong::new(Degrees(90.0), Degrees(0.0));
755 let north_pole = Vector3::from(&lat_lon_north);
756
757 assert_eq!(0.0, sq_distance(&south_pole, &south_pole));
758 assert_eq!(0.0, sq_distance(&north_pole, &north_pole));
759 assert_eq!(4.0, sq_distance(&south_pole, &north_pole));
760
761 assert_eq!(0.0, distance(&south_pole, &south_pole));
762 assert_eq!(0.0, distance(&north_pole, &north_pole));
763 assert_eq!(2.0, distance(&south_pole, &north_pole));
764
765 let g_eq = Vector3::new(1.0, 0.0, 0.0);
767
768 let idl_eq = Vector3::new(-1.0, 0.0, 0.0);
770
771 assert_eq!(0.0, sq_distance(&g_eq, &g_eq));
772 assert_eq!(0.0, sq_distance(&idl_eq, &idl_eq));
773 assert_eq!(4.0, sq_distance(&g_eq, &idl_eq));
774
775 assert_eq!(0.0, distance(&g_eq, &g_eq));
776 assert_eq!(0.0, distance(&idl_eq, &idl_eq));
777 assert_eq!(2.0, distance(&g_eq, &idl_eq));
778 }
779
780 #[test]
781 fn test_calculate_azimuth_at_poles() {
782 let g_eq = Vector3::new(1.0, 0.0, 0.0);
784 let south_pole = Vector3::new(0.0, 0.0, -1.0);
785 let result = calculate_azimuth(&south_pole, &g_eq);
786 assert_eq!(Angle::default(), result);
787
788 let north_pole = Vector3::new(0.0, 0.0, 1.0);
789 let result = calculate_azimuth(&north_pole, &g_eq);
790 assert_eq!(Angle::default().opposite(), result);
791 }
792
793 #[test]
794 fn test_calculate_pole_azimuth_and_direction() {
795 let g_eq = Vector3::new(1.0, 0.0, 0.0);
797
798 let e_eq = Vector3::new(0.0, 1.0, 0.0);
800
801 let w_eq = Vector3::new(0.0, -1.0, 0.0);
803
804 let angle_90 = Angle::from(Degrees(90.0));
805 let pole_a = calculate_pole(
806 Angle::from(Degrees(0.0)),
807 Angle::from(Degrees(0.0)),
808 angle_90,
809 );
810 assert!(are_orthogonal(&g_eq, &pole_a));
811
812 let dir_a = calculate_direction(
813 Angle::from(Degrees(0.0)),
814 Angle::from(Degrees(0.0)),
815 angle_90,
816 );
817 assert!(are_orthogonal(&g_eq, &dir_a));
818 assert!(are_orthogonal(&pole_a, &dir_a));
819 assert_eq!(dir_a, direction(&g_eq, &pole_a));
820
821 let north_pole = Vector3::new(0.0, 0.0, 1.0);
822 assert_eq!(north_pole, pole_a);
823
824 let result = g_eq.cross(&e_eq);
825 assert_eq!(north_pole, result);
826
827 let result = calculate_azimuth(&g_eq, &pole_a);
828 assert_eq!(angle_90, result);
829
830 let pole_b = calculate_pole(
831 Angle::from(Degrees(0.0)),
832 Angle::from(Degrees(0.0)),
833 -angle_90,
834 );
835 assert!(are_orthogonal(&g_eq, &pole_b));
836
837 let dir_b = calculate_direction(
838 Angle::from(Degrees(0.0)),
839 Angle::from(Degrees(0.0)),
840 -angle_90,
841 );
842 assert!(are_orthogonal(&g_eq, &dir_b));
843 assert!(are_orthogonal(&pole_b, &dir_b));
844 assert_eq!(dir_b, direction(&g_eq, &pole_b));
845
846 let south_pole = Vector3::new(0.0, 0.0, -1.0);
847 assert_eq!(south_pole, pole_b);
848
849 let result = g_eq.cross(&w_eq);
850 assert_eq!(south_pole, result);
851
852 let result = calculate_azimuth(&g_eq, &pole_b);
853 assert_eq!(-angle_90, result);
854 }
855
856 #[test]
857 fn test_calculate_position() {
858 let g_eq = Vector3::new(1.0, 0.0, 0.0);
860
861 let e_eq = Vector3::new(0.0, 1.0, 0.0);
863
864 let pole_0 = g_eq.cross(&e_eq);
865
866 let angle_90 = Angle::from(Degrees(90.0));
867
868 let pos_1 = position(&g_eq, &direction(&g_eq, &pole_0), angle_90);
869 assert_eq!(e_eq, pos_1);
870
871 let pos_2 = rotate_position(&g_eq, &pole_0, Angle::default(), angle_90);
872 assert_eq!(e_eq, pos_2);
873
874 let pos_3 = rotate_position(&g_eq, &pole_0, angle_90, angle_90);
875 assert_eq!(pole_0, pos_3);
876 }
877
878 #[test]
879 fn test_calculate_cross_track_distance_and_square() {
880 let g_eq = Vector3::new(1.0, 0.0, 0.0);
882
883 let e_eq = Vector3::new(0.0, 1.0, 0.0);
885
886 let pole_0 = g_eq.cross(&e_eq);
887
888 let longitude = Degrees(1.0);
889
890 for lat in -89..90 {
891 let latitude = Degrees(f64::from(lat));
892 let latlong = LatLong::new(latitude, longitude);
893 let point = Vector3::from(&latlong);
894
895 assert_eq!(lat < 0, is_south_of(&point, &g_eq));
896 assert_eq!(lat >= 0, !is_south_of(&point, &e_eq));
897 assert_eq!(lat < 0, is_right_of(&pole_0, &point));
898
899 let expected = (f64::from(lat)).to_radians();
900 let xtd = cross_track_distance(&pole_0, &point);
901 let tolerance = if (-83..84).contains(&lat) {
903 2.0 * f64::EPSILON
904 } else {
905 32.0 * f64::EPSILON
906 };
907 assert!(is_within_tolerance(expected, xtd.0, tolerance));
908
909 let expected = great_circle::gc2e_distance(Radians(expected));
910 let expected = expected * expected;
911 let xtd2 = sq_cross_track_distance(&pole_0, &point);
912 let tolerance = if (-83..84).contains(&lat) {
914 4.0 * f64::EPSILON
915 } else {
916 64.0 * f64::EPSILON
917 };
918 assert!(is_within_tolerance(expected, xtd2, tolerance));
919 }
920 }
921
922 #[test]
923 fn test_calculate_along_track_distance_and_square() {
924 let g_eq = Vector3::new(1.0, 0.0, 0.0);
926
927 let e_eq = Vector3::new(0.0, 1.0, 0.0);
929
930 let pole_0 = g_eq.cross(&e_eq);
931
932 let latitude = Degrees(1.0);
934
935 for lon in -179..180 {
936 let longitude = Degrees(f64::from(lon));
937 let latlong = LatLong::new(latitude, longitude);
938 let point = Vector3::from(&latlong);
939
940 let expected = (f64::from(lon)).to_radians();
941 let atd = along_track_distance(&g_eq, &pole_0, &point);
942 let tolerance = if (-153..154).contains(&lon) {
944 4.0 * f64::EPSILON
945 } else {
946 32.0 * f64::EPSILON
947 };
948 assert!(is_within_tolerance(expected, atd.0, tolerance));
949
950 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
951 assert!(is_within_tolerance(expected, atd.0, tolerance));
952 assert!(is_within_tolerance(1_f64.to_radians(), xtd.0, f64::EPSILON));
953
954 let expected = great_circle::gc2e_distance(Radians(expected));
955 let expected = expected * expected;
956 let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
957 let tolerance = if (-86..87).contains(&lon) {
959 2.0 * f64::EPSILON
960 } else {
961 32.0 * f64::EPSILON
962 };
963 assert!(is_within_tolerance(expected, atd2, tolerance));
964 }
965 }
966
967 #[test]
968 fn test_calculate_along_track_distance_and_square_f32() {
969 let g_eq = Vector3::new(1.0_f32, 0.0_f32, 0.0_f32);
971
972 let e_eq = Vector3::new(0.0_f32, 1.0_f32, 0.0_f32);
974
975 let pole_0 = g_eq.cross(&e_eq);
976
977 let latitude = Degrees(1.0_f32);
979
980 for lon in -179..180 {
981 let longitude = lon as f32;
982 let latlong = LatLong::new(latitude, Degrees(longitude));
983 let point = Vector3::from(&latlong);
984
985 let expected = longitude.to_radians();
986 let atd = along_track_distance(&g_eq, &pole_0, &point);
987 let tolerance = if (-153..154).contains(&lon) {
989 4.0 * f32::EPSILON
990 } else {
991 32.0 * f32::EPSILON
992 };
993 assert!(is_within_tolerance(expected, atd.0, tolerance));
994
995 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
996 assert!(is_within_tolerance(expected, atd.0, tolerance));
997 assert!(is_within_tolerance(
998 1.0_f32.to_radians(),
999 xtd.0,
1000 f32::EPSILON
1001 ));
1002
1003 let expected = great_circle::gc2e_distance(Radians(expected));
1004 let expected = expected * expected;
1005 let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
1006 let tolerance = if (-86..87).contains(&lon) {
1008 2.0 * f32::EPSILON
1009 } else {
1010 8.0 * f32::EPSILON
1011 };
1012 assert!(is_within_tolerance(expected, atd2, tolerance));
1013 }
1014 }
1015
1016 #[test]
1017 fn test_special_cases() {
1018 let g_eq = Vector3::new(1.0, 0.0, 0.0);
1020
1021 let e_eq = Vector3::new(0.0, 1.0, 0.0);
1023
1024 let pole_0 = g_eq.cross(&e_eq);
1025
1026 assert_eq!(0.0, along_track_distance(&g_eq, &pole_0, &pole_0).0);
1028 assert_eq!(0.0, sq_along_track_distance(&g_eq, &pole_0, &pole_0));
1029
1030 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &g_eq);
1031 assert_eq!(0.0, atd.0);
1032 assert_eq!(0.0, xtd.0);
1033
1034 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &pole_0);
1035 assert_eq!(0.0, atd.0);
1036 assert_eq!(core::f64::consts::FRAC_PI_2, xtd.0);
1037
1038 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &-pole_0);
1039 assert_eq!(0.0, atd.0);
1040 assert_eq!(-core::f64::consts::FRAC_PI_2, xtd.0);
1041
1042 let near_north_pole = LatLong::new(Degrees(89.99999), Degrees(0.0));
1044 let p = Vector3::from(&near_north_pole);
1045 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &p);
1046 assert_eq!(0.0, atd.0);
1047 assert!(is_within_tolerance(
1048 core::f64::consts::FRAC_PI_2,
1049 xtd.0,
1050 0.000001
1051 ));
1052 }
1053
1054 #[test]
1055 fn test_normalise_centroid() {
1056 let point_0 = Vector3::new(0.0, 0.0, 0.0);
1057 let point_1 = Vector3::new(1.0, 0.0, 0.0);
1058 let point_m1 = -point_1;
1059 let pole_1 = Vector3::new(0.0, 0.0, 1.0);
1060
1061 let result = normalise_centroid(&point_0, &point_1, &pole_1);
1063 assert_eq!(Vector3::new(0.0, -1.0, 0.0), result);
1064
1065 let result = normalise_centroid(&point_0, &point_m1, &pole_1);
1067 assert_eq!(Vector3::new(0.0, 1.0, 0.0), result);
1068
1069 let point_2 = point_1 + point_1;
1071 let result = normalise_centroid(&point_2, &point_1, &pole_1);
1072 assert_eq!(point_1, result);
1073 }
1074
1075 #[test]
1076 fn test_normalise_centroid_f32() {
1077 let point_0 = Vector3::new(0.0_f32, 0.0_f32, 0.0_f32);
1078 let point_1 = Vector3::new(1.0_f32, 0.0_f32, 0.0_f32);
1079 let point_m1 = -point_1;
1080 let pole_1 = Vector3::new(0.0_f32, 0.0_f32, 1.0_f32);
1081
1082 let result = normalise_centroid(&point_0, &point_1, &pole_1);
1084 assert_eq!(Vector3::new(0.0_f32, -1.0_f32, 0.0_f32), result);
1085
1086 let result = normalise_centroid(&point_0, &point_m1, &pole_1);
1088 assert_eq!(Vector3::new(0.0_f32, 1.0_f32, 0.0_f32), result);
1089
1090 let point_2 = point_1 + point_1;
1092 let result = normalise_centroid(&point_2, &point_1, &pole_1);
1093 assert_eq!(point_1, result);
1094 }
1095}