1use crate::{great_circle, Vector3d};
28use angle_sc::{trig, Angle, Radians};
29
30pub mod intersection;
31
32pub const MIN_SIN_ANGLE: f64 = 16384.0 * f64::EPSILON;
35
36pub const MIN_SQ_NORM: f64 = MIN_SIN_ANGLE * MIN_SIN_ANGLE;
38
39pub const MIN_SQ_DISTANCE: f64 = great_circle::MIN_VALUE * great_circle::MIN_VALUE;
41
42#[must_use]
50pub fn to_point(lat: Angle, lon: Angle) -> Vector3d {
51 Vector3d::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(a: &Vector3d) -> Angle {
65 Angle::from_y_x(a.z, libm::hypot(a.x, a.y))
66}
67
68#[must_use]
74pub fn longitude(a: &Vector3d) -> Angle {
75 Angle::from_y_x(a.y, a.x)
76}
77
78#[must_use]
84pub fn is_unit(a: &Vector3d) -> bool {
85 const MIN_POINT_SQ_LENGTH: f64 = 1.0 - 12.0 * f64::EPSILON;
86 const MAX_POINT_SQ_LENGTH: f64 = 1.0 + 12.0 * f64::EPSILON;
87
88 (MIN_POINT_SQ_LENGTH..=MAX_POINT_SQ_LENGTH).contains(&(a.norm_squared()))
89}
90
91#[must_use]
100pub fn normalise(a: &Vector3d, min_sq_value: f64) -> Option<Vector3d> {
101 if a.norm_squared() < min_sq_value {
102 None
103 } else {
104 Some(a.normalize())
105 }
106}
107
108#[must_use]
116pub fn sq_distance(a: &Vector3d, b: &Vector3d) -> f64 {
117 (b - a).norm_squared()
118}
119
120#[must_use]
127pub fn distance(a: &Vector3d, b: &Vector3d) -> f64 {
128 (b - a).norm()
129}
130
131#[must_use]
137pub fn are_orthogonal(a: &Vector3d, b: &Vector3d) -> bool {
138 const MAX_LENGTH: f64 = 4.0 * f64::EPSILON;
139
140 (-MAX_LENGTH..=MAX_LENGTH).contains(&(a.dot(b)))
141}
142
143#[must_use]
150pub fn delta_longitude(a: &Vector3d, b: &Vector3d) -> Angle {
151 let a_lon = a.xy();
152 let b_lon = b.xy();
153 Angle::from_y_x(b_lon.perp(&a_lon), b_lon.dot(&a_lon))
154}
155
156#[must_use]
163pub fn is_south_of(a: &Vector3d, b: &Vector3d) -> bool {
164 a.z < b.z
165}
166
167#[must_use]
174pub fn is_west_of(a: &Vector3d, b: &Vector3d) -> bool {
175 b.xy().perp(&a.xy()) < 0.0
176}
177
178#[must_use]
188pub fn calculate_pole(lat: Angle, lon: Angle, azi: Angle) -> Vector3d {
189 let x = trig::UnitNegRange::clamp(
190 lon.sin().0 * azi.cos().0 - lat.sin().0 * lon.cos().0 * azi.sin().0,
191 );
192 let y = trig::UnitNegRange::clamp(
193 0.0 - lon.cos().0 * azi.cos().0 - lat.sin().0 * lon.sin().0 * azi.sin().0,
194 );
195 let z = trig::UnitNegRange(lat.cos().0 * azi.sin().0);
196
197 Vector3d::new(x.0, y.0, z.0)
198}
199
200#[must_use]
207pub fn calculate_azimuth(point: &Vector3d, pole: &Vector3d) -> Angle {
208 const MAX_LAT: f64 = 1.0 - great_circle::MIN_VALUE;
209
210 let sin_lat = point.z;
211 if MAX_LAT <= libm::fabs(sin_lat) {
213 return if sin_lat.is_sign_negative() {
215 Angle::default()
216 } else {
217 Angle::new(trig::UnitNegRange(0.0), trig::UnitNegRange(-1.0))
218 };
219 }
220
221 Angle::from_y_x(pole.z, pole.xy().perp(&point.xy()))
222}
223
224#[must_use]
235pub fn calculate_direction(lat: Angle, lon: Angle, azi: Angle) -> Vector3d {
236 let x = trig::UnitNegRange::clamp(
237 0.0 - lat.sin().0 * lon.cos().0 * azi.cos().0 - lon.sin().0 * azi.sin().0,
238 );
239 let y = trig::UnitNegRange::clamp(
240 0.0 - lat.sin().0 * lon.sin().0 * azi.cos().0 + lon.cos().0 * azi.sin().0,
241 );
242 let z = trig::UnitNegRange(lat.cos().0 * azi.cos().0);
243
244 Vector3d::new(x.0, y.0, z.0)
245}
246
247#[must_use]
254pub fn direction(a: &Vector3d, pole: &Vector3d) -> Vector3d {
255 pole.cross(a)
256}
257
258#[must_use]
266pub fn position(a: &Vector3d, dir: &Vector3d, distance: Angle) -> Vector3d {
267 distance.cos().0 * a + distance.sin().0 * dir
268}
269
270#[must_use]
279pub fn rotate(dir: &Vector3d, pole: &Vector3d, angle: Angle) -> Vector3d {
280 position(dir, pole, angle)
281}
282
283#[must_use]
292pub fn rotate_position(a: &Vector3d, pole: &Vector3d, angle: Angle, radius: Angle) -> Vector3d {
293 position(a, &rotate(&direction(a, pole), pole, angle), radius)
294}
295
296#[must_use]
304fn sin_xtd(pole: &Vector3d, point: &Vector3d) -> trig::UnitNegRange {
305 trig::UnitNegRange::clamp(pole.dot(point))
306}
307
308#[must_use]
317pub fn is_right_of(pole: &Vector3d, point: &Vector3d) -> bool {
318 pole.dot(point) < 0.0
319}
320
321#[must_use]
328pub fn cross_track_distance(pole: &Vector3d, point: &Vector3d) -> Radians {
329 let sin_d = sin_xtd(pole, point);
330 if libm::fabs(sin_d.0) < f64::EPSILON {
331 Radians(0.0)
332 } else {
333 Radians(libm::asin(sin_d.0))
334 }
335}
336
337#[must_use]
345pub fn sq_cross_track_distance(pole: &Vector3d, point: &Vector3d) -> f64 {
346 let sin_d = sin_xtd(pole, point);
347 if libm::fabs(sin_d.0) < f64::EPSILON {
348 0.0
349 } else {
350 2.0 * (1.0 - trig::swap_sin_cos(sin_d).0)
351 }
352}
353
354#[must_use]
362fn calculate_point_on_plane(pole: &Vector3d, point: &Vector3d) -> Vector3d {
363 let t = sin_xtd(pole, point);
364 point - pole * t.0
365}
366
367#[must_use]
378pub fn sin_atd(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> trig::UnitNegRange {
379 trig::UnitNegRange::clamp(pole.cross(a).dot(point))
380}
381
382#[must_use]
391pub fn calculate_great_circle_atd(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> Radians {
392 let sq_atd = sq_distance(a, point);
393 if sq_atd < MIN_SQ_DISTANCE {
394 Radians(0.0)
395 } else {
396 Radians(libm::copysign(
397 great_circle::e2gc_distance(libm::sqrt(sq_atd)).0,
398 sin_atd(a, pole, point).0,
399 ))
400 }
401}
402
403#[must_use]
412pub fn along_track_distance(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> Radians {
413 let plane_point = calculate_point_on_plane(pole, point);
414 normalise(&plane_point, MIN_SQ_NORM).map_or_else(
415 || Radians(0.0), |c| calculate_great_circle_atd(a, pole, &c),
417 )
418}
419
420#[must_use]
430pub fn sq_along_track_distance(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> f64 {
431 let plane_point = calculate_point_on_plane(pole, point);
432 normalise(&plane_point, MIN_SQ_NORM).map_or_else(
433 || 0.0, |c| {
435 let sq_d = sq_distance(a, &(c));
436 if sq_d < MIN_SQ_DISTANCE {
437 0.0
438 } else {
439 sq_d
440 }
441 },
442 )
443}
444
445#[allow(clippy::similar_names)]
454#[must_use]
455pub fn calculate_atd_and_xtd(a: &Vector3d, pole: &Vector3d, p: &Vector3d) -> (Radians, Radians) {
456 let mut atd = Radians(0.0);
457 let mut xtd = Radians(0.0);
458
459 let sq_d = sq_distance(a, p);
460 if sq_d >= MIN_SQ_DISTANCE {
461 let sin_xtd = sin_xtd(pole, p).0;
463 if libm::fabs(sin_xtd) >= f64::EPSILON {
464 xtd = Radians(libm::asin(sin_xtd));
465 }
466
467 let plane_point = p - pole * sin_xtd;
469 atd = normalise(&plane_point, MIN_SQ_NORM).map_or_else(
470 || Radians(0.0), |c| calculate_great_circle_atd(a, pole, &c),
472 );
473 }
474
475 (atd, xtd)
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::LatLong;
482 use angle_sc::{is_within_tolerance, Degrees, Radians};
483
484 #[test]
485 fn test_normalise() {
486 let zero = Vector3d::new(0.0, 0.0, 0.0);
487 assert!(normalise(&zero, MIN_SQ_NORM).is_none());
488
489 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
491 assert!(normalise(&g_eq, MIN_SQ_NORM).is_some());
492
493 let too_small = Vector3d::new(16383.0 * f64::EPSILON, 0.0, 0.0);
495 assert!(normalise(&too_small, MIN_SQ_NORM).is_none());
496
497 assert_eq!(
498 2.0844083160439303e-10,
499 libm::asin(MIN_SIN_ANGLE).to_degrees()
500 );
501
502 let small = Vector3d::new(MIN_SIN_ANGLE, 0.0, 0.0);
504 let result = normalise(&small, MIN_SQ_NORM);
505 assert!(result.is_some());
506
507 assert!(is_unit(&result.unwrap()));
508 assert_eq!(result.unwrap(), g_eq);
509 }
510
511 #[test]
512 fn test_point_lat_longs() {
513 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(180.0));
515 let point_south = Vector3d::from(&lat_lon_south);
516
517 assert_eq!(Degrees(-90.0), Degrees::from(latitude(&point_south)));
518 assert_eq!(Degrees(0.0), Degrees::from(longitude(&point_south)));
519
520 let result = LatLong::from(&point_south);
521 assert_eq!(-90.0, result.lat().0);
522 assert_eq!(0.0, result.lon().0);
524
525 let lat_lon_0_0 = LatLong::new(Degrees(0.0), Degrees(0.0));
527 let point_0 = Vector3d::from(&lat_lon_0_0);
528 assert!(is_unit(&point_0));
529 assert_eq!(lat_lon_0_0, LatLong::from(&point_0));
530
531 let lat_lon_0_180 = LatLong::new(Degrees(0.0), Degrees(180.0));
533 let point_1 = Vector3d::from(&lat_lon_0_180);
534 assert!(is_unit(&point_1));
535 assert_eq!(false, is_west_of(&point_0, &point_1));
536 assert_eq!(
537 Radians(core::f64::consts::PI),
538 Radians::from(delta_longitude(&point_0, &point_1)).abs()
539 );
540
541 let lat_lon_0_m180 = LatLong::new(Degrees(0.0), Degrees(-180.0));
542 let point_2 = Vector3d::from(&lat_lon_0_m180);
543 assert!(is_unit(&point_2));
544 assert_eq!(lat_lon_0_180, LatLong::from(&point_2));
546
547 assert_eq!(false, is_west_of(&point_0, &point_2));
548 assert_eq!(
549 -core::f64::consts::PI,
550 Radians::from(delta_longitude(&point_0, &point_2)).0
551 );
552
553 let lat_lon_0_r3 = LatLong::new(Degrees(0.0), Degrees(3.0_f64.to_degrees()));
554 let point_3 = Vector3d::from(&lat_lon_0_r3);
555 assert!(is_unit(&point_3));
556 let result = LatLong::from(&point_3);
557 assert_eq!(0.0, result.lat().0);
558 assert_eq!(
559 3.0_f64,
560 Radians::from(delta_longitude(&point_3, &point_0)).0
561 );
562 assert_eq!(3.0_f64.to_degrees(), result.lon().0);
563 assert!(is_west_of(&point_0, &point_3));
564 assert_eq!(-3.0, Radians::from(delta_longitude(&point_0, &point_3)).0);
565
566 assert_eq!(false, is_west_of(&point_1, &point_3));
567 assert_eq!(
568 core::f64::consts::PI - 3.0,
569 Radians::from(delta_longitude(&point_1, &point_3)).0
570 );
571
572 let lat_lon_0_mr3 = LatLong::new(Degrees(0.0), Degrees(-3.0_f64.to_degrees()));
573 let point_4 = Vector3d::from(&lat_lon_0_mr3);
574 assert!(is_unit(&point_4));
575 assert_eq!(3.0, Radians::from(delta_longitude(&point_0, &point_4)).0);
576
577 let result = LatLong::from(&point_4);
578 assert_eq!(0.0, result.lat().0);
579 assert_eq!(-3.0_f64.to_degrees(), result.lon().0);
580 assert!(is_west_of(&point_1, &point_4));
581 assert_eq!(
582 3.0 - core::f64::consts::PI,
583 Radians::from(delta_longitude(&point_1, &point_4)).0
584 );
585 }
586
587 #[test]
588 fn test_point_distance() {
589 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(0.0));
590 let south_pole = Vector3d::from(&lat_lon_south);
591
592 let lat_lon_north = LatLong::new(Degrees(90.0), Degrees(0.0));
593 let north_pole = Vector3d::from(&lat_lon_north);
594
595 assert_eq!(0.0, sq_distance(&south_pole, &south_pole));
596 assert_eq!(0.0, sq_distance(&north_pole, &north_pole));
597 assert_eq!(4.0, sq_distance(&south_pole, &north_pole));
598
599 assert_eq!(0.0, distance(&south_pole, &south_pole));
600 assert_eq!(0.0, distance(&north_pole, &north_pole));
601 assert_eq!(2.0, distance(&south_pole, &north_pole));
602
603 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
605
606 let idl_eq = Vector3d::new(-1.0, 0.0, 0.0);
608
609 assert_eq!(0.0, sq_distance(&g_eq, &g_eq));
610 assert_eq!(0.0, sq_distance(&idl_eq, &idl_eq));
611 assert_eq!(4.0, sq_distance(&g_eq, &idl_eq));
612
613 assert_eq!(0.0, distance(&g_eq, &g_eq));
614 assert_eq!(0.0, distance(&idl_eq, &idl_eq));
615 assert_eq!(2.0, distance(&g_eq, &idl_eq));
616 }
617
618 #[test]
619 fn test_calculate_azimuth_at_poles() {
620 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
622 let south_pole = Vector3d::new(0.0, 0.0, -1.0);
623 let result = calculate_azimuth(&south_pole, &g_eq);
624 assert_eq!(Angle::default(), result);
625
626 let north_pole = Vector3d::new(0.0, 0.0, 1.0);
627 let result = calculate_azimuth(&north_pole, &g_eq);
628 assert_eq!(Angle::default().opposite(), result);
629 }
630
631 #[test]
632 fn test_calculate_pole_azimuth_and_direction() {
633 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
635
636 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
638
639 let w_eq = Vector3d::new(0.0, -1.0, 0.0);
641
642 let angle_90 = Angle::from(Degrees(90.0));
643 let pole_a = calculate_pole(
644 Angle::from(Degrees(0.0)),
645 Angle::from(Degrees(0.0)),
646 angle_90,
647 );
648 assert!(are_orthogonal(&g_eq, &pole_a));
649
650 let dir_a = calculate_direction(
651 Angle::from(Degrees(0.0)),
652 Angle::from(Degrees(0.0)),
653 angle_90,
654 );
655 assert!(are_orthogonal(&g_eq, &dir_a));
656 assert!(are_orthogonal(&pole_a, &dir_a));
657 assert_eq!(dir_a, direction(&g_eq, &pole_a));
658
659 let north_pole = Vector3d::new(0.0, 0.0, 1.0);
660 assert_eq!(north_pole, pole_a);
661
662 let result = g_eq.cross(&e_eq);
663 assert_eq!(north_pole, result);
664
665 let result = calculate_azimuth(&g_eq, &pole_a);
666 assert_eq!(angle_90, result);
667
668 let pole_b = calculate_pole(
669 Angle::from(Degrees(0.0)),
670 Angle::from(Degrees(0.0)),
671 -angle_90,
672 );
673 assert!(are_orthogonal(&g_eq, &pole_b));
674
675 let dir_b = calculate_direction(
676 Angle::from(Degrees(0.0)),
677 Angle::from(Degrees(0.0)),
678 -angle_90,
679 );
680 assert!(are_orthogonal(&g_eq, &dir_b));
681 assert!(are_orthogonal(&pole_b, &dir_b));
682 assert_eq!(dir_b, direction(&g_eq, &pole_b));
683
684 let south_pole = Vector3d::new(0.0, 0.0, -1.0);
685 assert_eq!(south_pole, pole_b);
686
687 let result = g_eq.cross(&w_eq);
688 assert_eq!(south_pole, result);
689
690 let result = calculate_azimuth(&g_eq, &pole_b);
691 assert_eq!(-angle_90, result);
692 }
693
694 #[test]
695 fn test_calculate_position() {
696 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
698
699 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
701
702 let pole_0 = g_eq.cross(&e_eq);
703
704 let angle_90 = Angle::from(Degrees(90.0));
705
706 let pos_1 = position(&g_eq, &direction(&g_eq, &pole_0), angle_90);
707 assert_eq!(e_eq, pos_1);
708
709 let pos_2 = rotate_position(&g_eq, &pole_0, Angle::default(), angle_90);
710 assert_eq!(e_eq, pos_2);
711
712 let pos_3 = rotate_position(&g_eq, &pole_0, angle_90, angle_90);
713 assert_eq!(pole_0, pos_3);
714 }
715
716 #[test]
717 fn test_calculate_cross_track_distance_and_square() {
718 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
720
721 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
723
724 let pole_0 = g_eq.cross(&e_eq);
725
726 let longitude = Degrees(1.0);
727
728 for lat in -89..90 {
729 let latitude = Degrees(lat as f64);
730 let latlong = LatLong::new(latitude, longitude);
731 let point = Vector3d::from(&latlong);
732
733 assert_eq!(lat < 0, is_south_of(&point, &g_eq));
734 assert_eq!(lat >= 0, !is_south_of(&point, &e_eq));
735 assert_eq!(lat < 0, is_right_of(&pole_0, &point));
736
737 let expected = (lat as f64).to_radians();
738 let xtd = cross_track_distance(&pole_0, &point);
739 let tolerance = if (-83..84).contains(&lat) {
741 2.0 * f64::EPSILON
742 } else {
743 32.0 * f64::EPSILON
744 };
745 assert!(is_within_tolerance(expected, xtd.0, tolerance));
746
747 let expected = great_circle::gc2e_distance(Radians(expected));
748 let expected = expected * expected;
749 let xtd2 = sq_cross_track_distance(&pole_0, &point);
750 let tolerance = if (-83..84).contains(&lat) {
752 4.0 * f64::EPSILON
753 } else {
754 64.0 * f64::EPSILON
755 };
756 assert!(is_within_tolerance(expected, xtd2, tolerance));
757 }
758 }
759
760 #[test]
761 fn test_calculate_along_track_distance_and_square() {
762 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
764
765 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
767
768 let pole_0 = g_eq.cross(&e_eq);
769
770 let latitude = Degrees(1.0);
772
773 for lon in -179..180 {
774 let longitude = Degrees(lon as f64);
775 let latlong = LatLong::new(latitude, longitude);
776 let point = Vector3d::from(&latlong);
777
778 let expected = (lon as f64).to_radians();
779 let atd = along_track_distance(&g_eq, &pole_0, &point);
780 let tolerance = if (-153..154).contains(&lon) {
782 2.0 * f64::EPSILON
783 } else {
784 32.0 * f64::EPSILON
785 };
786 assert!(is_within_tolerance(expected, atd.0, tolerance));
787
788 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
789 assert!(is_within_tolerance(expected, atd.0, tolerance));
790 assert!(is_within_tolerance(1_f64.to_radians(), xtd.0, f64::EPSILON));
791
792 let expected = great_circle::gc2e_distance(Radians(expected));
793 let expected = expected * expected;
794 let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
795 let tolerance = if (-86..87).contains(&lon) {
797 2.0 * f64::EPSILON
798 } else {
799 32.0 * f64::EPSILON
800 };
801 assert!(is_within_tolerance(expected, atd2, tolerance));
802 }
803 }
804
805 #[test]
806 fn test_special_cases() {
807 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
809
810 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
812
813 let pole_0 = g_eq.cross(&e_eq);
814
815 assert_eq!(0.0, along_track_distance(&g_eq, &pole_0, &pole_0).0);
817 assert_eq!(0.0, sq_along_track_distance(&g_eq, &pole_0, &pole_0));
818
819 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &g_eq);
820 assert_eq!(0.0, atd.0);
821 assert_eq!(0.0, xtd.0);
822
823 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &pole_0);
824 assert_eq!(0.0, atd.0);
825 assert_eq!(core::f64::consts::FRAC_PI_2, xtd.0);
826
827 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &-pole_0);
828 assert_eq!(0.0, atd.0);
829 assert_eq!(-core::f64::consts::FRAC_PI_2, xtd.0);
830
831 let near_north_pole = LatLong::new(Degrees(89.99999), Degrees(0.0));
833 let p = Vector3d::from(&near_north_pole);
834 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &p);
835 assert_eq!(0.0, atd.0);
836 assert!(is_within_tolerance(
837 core::f64::consts::FRAC_PI_2,
838 xtd.0,
839 0.000001
840 ));
841 }
842}