1use crate::{Vector3d, great_circle};
28use angle_sc::{Angle, Radians, trig};
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, a.x.hypot(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 <= sin_lat.abs() {
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 sin_d.0.abs() < f64::EPSILON {
331 Radians(0.0)
332 } else {
333 Radians(sin_d.0.asin())
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 sin_d.0.abs() < 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(
397 great_circle::e2gc_distance(sq_atd.sqrt())
398 .0
399 .copysign(sin_atd(a, pole, point).0),
400 )
401 }
407}
408
409#[must_use]
418pub fn along_track_distance(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> Radians {
419 let plane_point = calculate_point_on_plane(pole, point);
420 normalise(&plane_point, MIN_SQ_NORM).map_or_else(
421 || Radians(0.0), |c| calculate_great_circle_atd(a, pole, &c),
423 )
424}
425
426#[must_use]
436pub fn sq_along_track_distance(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> f64 {
437 let plane_point = calculate_point_on_plane(pole, point);
438 normalise(&plane_point, MIN_SQ_NORM).map_or_else(
439 || 0.0, |c| {
441 let sq_d = sq_distance(a, &(c));
442 if sq_d < MIN_SQ_DISTANCE { 0.0 } else { sq_d }
443 },
444 )
445}
446
447#[allow(clippy::similar_names)]
456#[must_use]
457pub fn calculate_atd_and_xtd(a: &Vector3d, pole: &Vector3d, p: &Vector3d) -> (Radians, Radians) {
458 let mut atd = Radians(0.0);
459 let mut xtd = Radians(0.0);
460
461 let sq_d = sq_distance(a, p);
462 if sq_d >= MIN_SQ_DISTANCE {
463 let sin_xtd = sin_xtd(pole, p).0;
465 if sin_xtd.abs() >= f64::EPSILON {
466 xtd = Radians(sin_xtd.asin());
467 }
468
469 let plane_point = p - pole * sin_xtd;
471 atd = normalise(&plane_point, MIN_SQ_NORM).map_or_else(
472 || Radians(0.0), |c| calculate_great_circle_atd(a, pole, &c),
474 );
475 }
476
477 (atd, xtd)
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483 use crate::LatLong;
484 use angle_sc::{Degrees, Radians, is_within_tolerance};
485
486 #[test]
487 fn test_normalise() {
488 let zero = Vector3d::new(0.0, 0.0, 0.0);
489 assert!(normalise(&zero, MIN_SQ_NORM).is_none());
490
491 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
493 assert!(normalise(&g_eq, MIN_SQ_NORM).is_some());
494
495 let too_small = Vector3d::new(16383.0 * f64::EPSILON, 0.0, 0.0);
497 assert!(normalise(&too_small, MIN_SQ_NORM).is_none());
498
499 assert_eq!(2.0844083160439303e-10, MIN_SIN_ANGLE.to_degrees().asin());
500
501 let small = Vector3d::new(MIN_SIN_ANGLE, 0.0, 0.0);
503 let result = normalise(&small, MIN_SQ_NORM);
504 assert!(result.is_some());
505
506 assert!(is_unit(&result.unwrap()));
507 assert_eq!(result.unwrap(), g_eq);
508 }
509
510 #[test]
511 fn test_point_lat_longs() {
512 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(180.0));
514 let point_south = Vector3d::from(&lat_lon_south);
515
516 assert_eq!(Degrees(-90.0), Degrees::from(latitude(&point_south)));
517 assert_eq!(Degrees(0.0), Degrees::from(longitude(&point_south)));
518
519 let result = LatLong::from(&point_south);
520 assert_eq!(-90.0, result.lat().0);
521 assert_eq!(0.0, result.lon().0);
523
524 let lat_lon_0_0 = LatLong::new(Degrees(0.0), Degrees(0.0));
526 let point_0 = Vector3d::from(&lat_lon_0_0);
527 assert!(is_unit(&point_0));
528 assert_eq!(lat_lon_0_0, LatLong::from(&point_0));
529
530 let lat_lon_0_180 = LatLong::new(Degrees(0.0), Degrees(180.0));
532 let point_1 = Vector3d::from(&lat_lon_0_180);
533 assert!(is_unit(&point_1));
534 assert_eq!(false, is_west_of(&point_0, &point_1));
535 assert_eq!(
536 Radians(core::f64::consts::PI),
537 Radians::from(delta_longitude(&point_0, &point_1)).abs()
538 );
539
540 let lat_lon_0_m180 = LatLong::new(Degrees(0.0), Degrees(-180.0));
541 let point_2 = Vector3d::from(&lat_lon_0_m180);
542 assert!(is_unit(&point_2));
543 assert_eq!(lat_lon_0_180, LatLong::from(&point_2));
545
546 assert_eq!(false, is_west_of(&point_0, &point_2));
547 assert_eq!(
548 -core::f64::consts::PI,
549 Radians::from(delta_longitude(&point_0, &point_2)).0
550 );
551
552 let lat_lon_0_r3 = LatLong::new(Degrees(0.0), Degrees(3.0_f64.to_degrees()));
553 let point_3 = Vector3d::from(&lat_lon_0_r3);
554 assert!(is_unit(&point_3));
555 let result = LatLong::from(&point_3);
556 assert_eq!(0.0, result.lat().0);
557 assert_eq!(
558 3.0_f64,
559 Radians::from(delta_longitude(&point_3, &point_0)).0
560 );
561 assert_eq!(3.0_f64.to_degrees(), result.lon().0);
562 assert!(is_west_of(&point_0, &point_3));
563 assert_eq!(-3.0, Radians::from(delta_longitude(&point_0, &point_3)).0);
564
565 assert_eq!(false, is_west_of(&point_1, &point_3));
566 assert_eq!(
567 core::f64::consts::PI - 3.0,
568 Radians::from(delta_longitude(&point_1, &point_3)).0
569 );
570
571 let lat_lon_0_mr3 = LatLong::new(Degrees(0.0), Degrees(-3.0_f64.to_degrees()));
572 let point_4 = Vector3d::from(&lat_lon_0_mr3);
573 assert!(is_unit(&point_4));
574 assert_eq!(3.0, Radians::from(delta_longitude(&point_0, &point_4)).0);
575
576 let result = LatLong::from(&point_4);
577 assert_eq!(0.0, result.lat().0);
578 assert_eq!(-3.0_f64.to_degrees(), result.lon().0);
579 assert!(is_west_of(&point_1, &point_4));
580 assert_eq!(
581 3.0 - core::f64::consts::PI,
582 Radians::from(delta_longitude(&point_1, &point_4)).0
583 );
584 }
585
586 #[test]
587 fn test_point_distance() {
588 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(0.0));
589 let south_pole = Vector3d::from(&lat_lon_south);
590
591 let lat_lon_north = LatLong::new(Degrees(90.0), Degrees(0.0));
592 let north_pole = Vector3d::from(&lat_lon_north);
593
594 assert_eq!(0.0, sq_distance(&south_pole, &south_pole));
595 assert_eq!(0.0, sq_distance(&north_pole, &north_pole));
596 assert_eq!(4.0, sq_distance(&south_pole, &north_pole));
597
598 assert_eq!(0.0, distance(&south_pole, &south_pole));
599 assert_eq!(0.0, distance(&north_pole, &north_pole));
600 assert_eq!(2.0, distance(&south_pole, &north_pole));
601
602 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
604
605 let idl_eq = Vector3d::new(-1.0, 0.0, 0.0);
607
608 assert_eq!(0.0, sq_distance(&g_eq, &g_eq));
609 assert_eq!(0.0, sq_distance(&idl_eq, &idl_eq));
610 assert_eq!(4.0, sq_distance(&g_eq, &idl_eq));
611
612 assert_eq!(0.0, distance(&g_eq, &g_eq));
613 assert_eq!(0.0, distance(&idl_eq, &idl_eq));
614 assert_eq!(2.0, distance(&g_eq, &idl_eq));
615 }
616
617 #[test]
618 fn test_calculate_azimuth_at_poles() {
619 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
621 let south_pole = Vector3d::new(0.0, 0.0, -1.0);
622 let result = calculate_azimuth(&south_pole, &g_eq);
623 assert_eq!(Angle::default(), result);
624
625 let north_pole = Vector3d::new(0.0, 0.0, 1.0);
626 let result = calculate_azimuth(&north_pole, &g_eq);
627 assert_eq!(Angle::default().opposite(), result);
628 }
629
630 #[test]
631 fn test_calculate_pole_azimuth_and_direction() {
632 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
634
635 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
637
638 let w_eq = Vector3d::new(0.0, -1.0, 0.0);
640
641 let angle_90 = Angle::from(Degrees(90.0));
642 let pole_a = calculate_pole(
643 Angle::from(Degrees(0.0)),
644 Angle::from(Degrees(0.0)),
645 angle_90,
646 );
647 assert!(are_orthogonal(&g_eq, &pole_a));
648
649 let dir_a = calculate_direction(
650 Angle::from(Degrees(0.0)),
651 Angle::from(Degrees(0.0)),
652 angle_90,
653 );
654 assert!(are_orthogonal(&g_eq, &dir_a));
655 assert!(are_orthogonal(&pole_a, &dir_a));
656 assert_eq!(dir_a, direction(&g_eq, &pole_a));
657
658 let north_pole = Vector3d::new(0.0, 0.0, 1.0);
659 assert_eq!(north_pole, pole_a);
660
661 let result = g_eq.cross(&e_eq);
662 assert_eq!(north_pole, result);
663
664 let result = calculate_azimuth(&g_eq, &pole_a);
665 assert_eq!(angle_90, result);
666
667 let pole_b = calculate_pole(
668 Angle::from(Degrees(0.0)),
669 Angle::from(Degrees(0.0)),
670 -angle_90,
671 );
672 assert!(are_orthogonal(&g_eq, &pole_b));
673
674 let dir_b = calculate_direction(
675 Angle::from(Degrees(0.0)),
676 Angle::from(Degrees(0.0)),
677 -angle_90,
678 );
679 assert!(are_orthogonal(&g_eq, &dir_b));
680 assert!(are_orthogonal(&pole_b, &dir_b));
681 assert_eq!(dir_b, direction(&g_eq, &pole_b));
682
683 let south_pole = Vector3d::new(0.0, 0.0, -1.0);
684 assert_eq!(south_pole, pole_b);
685
686 let result = g_eq.cross(&w_eq);
687 assert_eq!(south_pole, result);
688
689 let result = calculate_azimuth(&g_eq, &pole_b);
690 assert_eq!(-angle_90, result);
691 }
692
693 #[test]
694 fn test_calculate_position() {
695 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
697
698 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
700
701 let pole_0 = g_eq.cross(&e_eq);
702
703 let angle_90 = Angle::from(Degrees(90.0));
704
705 let pos_1 = position(&g_eq, &direction(&g_eq, &pole_0), angle_90);
706 assert_eq!(e_eq, pos_1);
707
708 let pos_2 = rotate_position(&g_eq, &pole_0, Angle::default(), angle_90);
709 assert_eq!(e_eq, pos_2);
710
711 let pos_3 = rotate_position(&g_eq, &pole_0, angle_90, angle_90);
712 assert_eq!(pole_0, pos_3);
713 }
714
715 #[test]
716 fn test_calculate_cross_track_distance_and_square() {
717 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
719
720 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
722
723 let pole_0 = g_eq.cross(&e_eq);
724
725 let longitude = Degrees(1.0);
726
727 for lat in -89..90 {
728 let latitude = Degrees(lat as f64);
729 let latlong = LatLong::new(latitude, longitude);
730 let point = Vector3d::from(&latlong);
731
732 assert_eq!(lat < 0, is_south_of(&point, &g_eq));
733 assert_eq!(lat >= 0, !is_south_of(&point, &e_eq));
734 assert_eq!(lat < 0, is_right_of(&pole_0, &point));
735
736 let expected = (lat as f64).to_radians();
737 let xtd = cross_track_distance(&pole_0, &point);
738 let tolerance = if (-83..84).contains(&lat) {
740 2.0 * f64::EPSILON
741 } else {
742 32.0 * f64::EPSILON
743 };
744 assert!(is_within_tolerance(expected, xtd.0, tolerance));
745
746 let expected = great_circle::gc2e_distance(Radians(expected));
747 let expected = expected * expected;
748 let xtd2 = sq_cross_track_distance(&pole_0, &point);
749 let tolerance = if (-83..84).contains(&lat) {
751 4.0 * f64::EPSILON
752 } else {
753 64.0 * f64::EPSILON
754 };
755 assert!(is_within_tolerance(expected, xtd2, tolerance));
756 }
757 }
758
759 #[test]
760 fn test_calculate_along_track_distance_and_square() {
761 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
763
764 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
766
767 let pole_0 = g_eq.cross(&e_eq);
768
769 let latitude = Degrees(1.0);
771
772 for lon in -179..180 {
773 let longitude = Degrees(lon as f64);
774 let latlong = LatLong::new(latitude, longitude);
775 let point = Vector3d::from(&latlong);
776
777 let expected = (lon as f64).to_radians();
778 let atd = along_track_distance(&g_eq, &pole_0, &point);
779 let tolerance = if (-153..154).contains(&lon) {
781 4.0 * f64::EPSILON
782 } else {
783 32.0 * f64::EPSILON
784 };
785 assert!(is_within_tolerance(expected, atd.0, tolerance));
786
787 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
788 assert!(is_within_tolerance(expected, atd.0, tolerance));
789 assert!(is_within_tolerance(1_f64.to_radians(), xtd.0, f64::EPSILON));
790
791 let expected = great_circle::gc2e_distance(Radians(expected));
792 let expected = expected * expected;
793 let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
794 let tolerance = if (-86..87).contains(&lon) {
796 2.0 * f64::EPSILON
797 } else {
798 32.0 * f64::EPSILON
799 };
800 assert!(is_within_tolerance(expected, atd2, tolerance));
801 }
802 }
803
804 #[test]
805 fn test_special_cases() {
806 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
808
809 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
811
812 let pole_0 = g_eq.cross(&e_eq);
813
814 assert_eq!(0.0, along_track_distance(&g_eq, &pole_0, &pole_0).0);
816 assert_eq!(0.0, sq_along_track_distance(&g_eq, &pole_0, &pole_0));
817
818 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &g_eq);
819 assert_eq!(0.0, atd.0);
820 assert_eq!(0.0, xtd.0);
821
822 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &pole_0);
823 assert_eq!(0.0, atd.0);
824 assert_eq!(core::f64::consts::FRAC_PI_2, xtd.0);
825
826 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &-pole_0);
827 assert_eq!(0.0, atd.0);
828 assert_eq!(-core::f64::consts::FRAC_PI_2, xtd.0);
829
830 let near_north_pole = LatLong::new(Degrees(89.99999), Degrees(0.0));
832 let p = Vector3d::from(&near_north_pole);
833 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &p);
834 assert_eq!(0.0, atd.0);
835 assert!(is_within_tolerance(
836 core::f64::consts::FRAC_PI_2,
837 xtd.0,
838 0.000001
839 ));
840 }
841}