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_west_of(a: &Vector3d, b: &Vector3d) -> bool {
164 b.xy().perp(&a.xy()) <= -f64::EPSILON
166}
167
168#[must_use]
178pub fn calculate_pole(lat: Angle, lon: Angle, azi: Angle) -> Vector3d {
179 let x = trig::UnitNegRange::clamp(
180 lon.sin().0 * azi.cos().0 - lat.sin().0 * lon.cos().0 * azi.sin().0,
181 );
182 let y = trig::UnitNegRange::clamp(
183 0.0 - lon.cos().0 * azi.cos().0 - lat.sin().0 * lon.sin().0 * azi.sin().0,
184 );
185 let z = trig::UnitNegRange(lat.cos().0 * azi.sin().0);
186
187 Vector3d::new(x.0, y.0, z.0)
188}
189
190#[must_use]
197pub fn calculate_azimuth(point: &Vector3d, pole: &Vector3d) -> Angle {
198 const MAX_LAT: f64 = 1.0 - great_circle::MIN_VALUE;
199
200 let sin_lat = point.z;
201 if MAX_LAT <= libm::fabs(sin_lat) {
203 return if sin_lat.is_sign_negative() {
204 Angle::default()
205 } else {
206 Angle::default().opposite()
207 };
208 }
209
210 Angle::from_y_x(pole.z, pole.xy().perp(&point.xy()))
211}
212
213#[must_use]
224pub fn calculate_direction(lat: Angle, lon: Angle, azi: Angle) -> Vector3d {
225 let x = trig::UnitNegRange::clamp(
226 0.0 - lat.sin().0 * lon.cos().0 * azi.cos().0 - lon.sin().0 * azi.sin().0,
227 );
228 let y = trig::UnitNegRange::clamp(
229 0.0 - lat.sin().0 * lon.sin().0 * azi.cos().0 + lon.cos().0 * azi.sin().0,
230 );
231 let z = trig::UnitNegRange(lat.cos().0 * azi.cos().0);
232
233 Vector3d::new(x.0, y.0, z.0)
234}
235
236#[must_use]
243pub fn direction(a: &Vector3d, pole: &Vector3d) -> Vector3d {
244 pole.cross(a)
245}
246
247#[must_use]
255pub fn position(a: &Vector3d, dir: &Vector3d, distance: Angle) -> Vector3d {
256 distance.cos().0 * a + distance.sin().0 * dir
257}
258
259#[must_use]
268pub fn rotate(dir: &Vector3d, pole: &Vector3d, angle: Angle) -> Vector3d {
269 position(dir, pole, angle)
270}
271
272#[must_use]
281pub fn rotate_position(a: &Vector3d, pole: &Vector3d, angle: Angle, radius: Angle) -> Vector3d {
282 position(a, &rotate(&direction(a, pole), pole, angle), radius)
283}
284
285#[must_use]
293fn sin_xtd(pole: &Vector3d, point: &Vector3d) -> trig::UnitNegRange {
294 trig::UnitNegRange::clamp(pole.dot(point))
295}
296
297#[must_use]
304pub fn cross_track_distance(pole: &Vector3d, point: &Vector3d) -> Radians {
305 let sin_d = sin_xtd(pole, point);
306 if libm::fabs(sin_d.0) < f64::EPSILON {
307 Radians(0.0)
308 } else {
309 Radians(libm::asin(sin_d.0))
310 }
311}
312
313#[must_use]
321pub fn sq_cross_track_distance(pole: &Vector3d, point: &Vector3d) -> f64 {
322 let sin_d = sin_xtd(pole, point);
323 if libm::fabs(sin_d.0) < f64::EPSILON {
324 0.0
325 } else {
326 2.0 * (1.0 - trig::swap_sin_cos(sin_d).0)
327 }
328}
329
330#[must_use]
338fn calculate_point_on_plane(pole: &Vector3d, point: &Vector3d) -> Vector3d {
339 let t = sin_xtd(pole, point);
340 point - pole * t.0
341}
342
343#[must_use]
354pub fn sin_atd(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> trig::UnitNegRange {
355 trig::UnitNegRange::clamp(pole.cross(a).dot(point))
356}
357
358#[must_use]
367pub fn calculate_great_circle_atd(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> Radians {
368 let sq_atd = sq_distance(a, point);
369 if sq_atd < MIN_SQ_DISTANCE {
370 Radians(0.0)
371 } else {
372 Radians(libm::copysign(
373 great_circle::e2gc_distance(libm::sqrt(sq_atd)).0,
374 sin_atd(a, pole, point).0,
375 ))
376 }
377}
378
379#[must_use]
388pub fn along_track_distance(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> Radians {
389 let plane_point = calculate_point_on_plane(pole, point);
390 normalise(&plane_point, MIN_SQ_NORM).map_or_else(
391 || Radians(0.0), |c| calculate_great_circle_atd(a, pole, &c),
393 )
394}
395
396#[must_use]
406pub fn sq_along_track_distance(a: &Vector3d, pole: &Vector3d, point: &Vector3d) -> f64 {
407 let plane_point = calculate_point_on_plane(pole, point);
408 normalise(&plane_point, MIN_SQ_NORM).map_or_else(
409 || 0.0, |c| {
411 let sq_d = sq_distance(a, &(c));
412 if sq_d < MIN_SQ_DISTANCE {
413 0.0
414 } else {
415 sq_d
416 }
417 },
418 )
419}
420
421#[allow(clippy::similar_names)]
430#[must_use]
431pub fn calculate_atd_and_xtd(a: &Vector3d, pole: &Vector3d, p: &Vector3d) -> (Radians, Radians) {
432 let mut atd = Radians(0.0);
433 let mut xtd = Radians(0.0);
434
435 let sq_d = sq_distance(a, p);
436 if sq_d >= MIN_SQ_DISTANCE {
437 let sin_xtd = sin_xtd(pole, p).0;
439 if libm::fabs(sin_xtd) >= f64::EPSILON {
440 xtd = Radians(libm::asin(sin_xtd));
441 }
442
443 let plane_point = p - pole * sin_xtd;
445 atd = normalise(&plane_point, MIN_SQ_NORM).map_or_else(
446 || Radians(0.0), |c| calculate_great_circle_atd(a, pole, &c),
448 );
449 }
450
451 (atd, xtd)
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457 use crate::LatLong;
458 use angle_sc::{is_within_tolerance, Degrees, Radians};
459
460 #[test]
461 fn test_normalise() {
462 let zero = Vector3d::new(0.0, 0.0, 0.0);
463 assert!(normalise(&zero, MIN_SQ_NORM).is_none());
464
465 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
467 assert!(normalise(&g_eq, MIN_SQ_NORM).is_some());
468
469 let too_small = Vector3d::new(16383.0 * f64::EPSILON, 0.0, 0.0);
471 assert!(normalise(&too_small, MIN_SQ_NORM).is_none());
472
473 assert_eq!(
474 2.0844083160439303e-10,
475 libm::asin(MIN_SIN_ANGLE).to_degrees()
476 );
477
478 let small = Vector3d::new(MIN_SIN_ANGLE, 0.0, 0.0);
480 let result = normalise(&small, MIN_SQ_NORM);
481 assert!(result.is_some());
482
483 assert!(is_unit(&result.unwrap()));
484 assert_eq!(result.unwrap(), g_eq);
485 }
486
487 #[test]
488 fn test_point_lat_longs() {
489 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(180.0));
491 let point_south = Vector3d::from(&lat_lon_south);
492
493 assert_eq!(Degrees(-90.0), Degrees::from(latitude(&point_south)));
494 assert_eq!(Degrees(0.0), Degrees::from(longitude(&point_south)));
495
496 let result = LatLong::from(&point_south);
497 assert_eq!(-90.0, result.lat().0);
498 assert_eq!(0.0, result.lon().0);
500
501 let lat_lon_0_0 = LatLong::new(Degrees(0.0), Degrees(0.0));
503 let point_0 = Vector3d::from(&lat_lon_0_0);
504 assert!(is_unit(&point_0));
505 assert_eq!(lat_lon_0_0, LatLong::from(&point_0));
506
507 let lat_lon_0_180 = LatLong::new(Degrees(0.0), Degrees(180.0));
509 let point_1 = Vector3d::from(&lat_lon_0_180);
510 assert!(is_unit(&point_1));
511 assert_eq!(false, is_west_of(&point_0, &point_1));
512 assert_eq!(
513 Radians(core::f64::consts::PI),
514 Radians::from(delta_longitude(&point_0, &point_1)).abs()
515 );
516
517 let lat_lon_0_m180 = LatLong::new(Degrees(0.0), Degrees(-180.0));
518 let point_2 = Vector3d::from(&lat_lon_0_m180);
519 assert!(is_unit(&point_2));
520 assert_eq!(lat_lon_0_180, LatLong::from(&point_2));
522
523 assert_eq!(false, is_west_of(&point_0, &point_2));
524 assert_eq!(
525 -core::f64::consts::PI,
526 Radians::from(delta_longitude(&point_0, &point_2)).0
527 );
528
529 let lat_lon_0_r3 = LatLong::new(Degrees(0.0), Degrees(3.0_f64.to_degrees()));
530 let point_3 = Vector3d::from(&lat_lon_0_r3);
531 assert!(is_unit(&point_3));
532 let result = LatLong::from(&point_3);
533 assert_eq!(0.0, result.lat().0);
534 assert_eq!(
535 3.0_f64,
536 Radians::from(delta_longitude(&point_3, &point_0)).0
537 );
538 assert_eq!(3.0_f64.to_degrees(), result.lon().0);
539 assert!(is_west_of(&point_0, &point_3));
540 assert_eq!(-3.0, Radians::from(delta_longitude(&point_0, &point_3)).0);
541
542 assert_eq!(false, is_west_of(&point_1, &point_3));
543 assert_eq!(
544 core::f64::consts::PI - 3.0,
545 Radians::from(delta_longitude(&point_1, &point_3)).0
546 );
547
548 let lat_lon_0_mr3 = LatLong::new(Degrees(0.0), Degrees(-3.0_f64.to_degrees()));
549 let point_4 = Vector3d::from(&lat_lon_0_mr3);
550 assert!(is_unit(&point_4));
551 assert_eq!(3.0, Radians::from(delta_longitude(&point_0, &point_4)).0);
552
553 let result = LatLong::from(&point_4);
554 assert_eq!(0.0, result.lat().0);
555 assert_eq!(-3.0_f64.to_degrees(), result.lon().0);
556 assert!(is_west_of(&point_1, &point_4));
557 assert_eq!(
558 3.0 - core::f64::consts::PI,
559 Radians::from(delta_longitude(&point_1, &point_4)).0
560 );
561 }
562
563 #[test]
564 fn test_point_distance() {
565 let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(0.0));
566 let south_pole = Vector3d::from(&lat_lon_south);
567
568 let lat_lon_north = LatLong::new(Degrees(90.0), Degrees(0.0));
569 let north_pole = Vector3d::from(&lat_lon_north);
570
571 assert_eq!(0.0, sq_distance(&south_pole, &south_pole));
572 assert_eq!(0.0, sq_distance(&north_pole, &north_pole));
573 assert_eq!(4.0, sq_distance(&south_pole, &north_pole));
574
575 assert_eq!(0.0, distance(&south_pole, &south_pole));
576 assert_eq!(0.0, distance(&north_pole, &north_pole));
577 assert_eq!(2.0, distance(&south_pole, &north_pole));
578
579 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
581
582 let idl_eq = Vector3d::new(-1.0, 0.0, 0.0);
584
585 assert_eq!(0.0, sq_distance(&g_eq, &g_eq));
586 assert_eq!(0.0, sq_distance(&idl_eq, &idl_eq));
587 assert_eq!(4.0, sq_distance(&g_eq, &idl_eq));
588
589 assert_eq!(0.0, distance(&g_eq, &g_eq));
590 assert_eq!(0.0, distance(&idl_eq, &idl_eq));
591 assert_eq!(2.0, distance(&g_eq, &idl_eq));
592 }
593
594 #[test]
595 fn test_calculate_azimuth_at_poles() {
596 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
598 let south_pole = Vector3d::new(0.0, 0.0, -1.0);
599 let result = calculate_azimuth(&south_pole, &g_eq);
600 assert_eq!(Angle::default(), result);
601
602 let north_pole = Vector3d::new(0.0, 0.0, 1.0);
603 let result = calculate_azimuth(&north_pole, &g_eq);
604 assert_eq!(Angle::default().opposite(), result);
605 }
606
607 #[test]
608 fn test_calculate_pole_azimuth_and_direction() {
609 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
611
612 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
614
615 let w_eq = Vector3d::new(0.0, -1.0, 0.0);
617
618 let angle_90 = Angle::from(Degrees(90.0));
619 let pole_a = calculate_pole(
620 Angle::from(Degrees(0.0)),
621 Angle::from(Degrees(0.0)),
622 angle_90,
623 );
624 assert!(are_orthogonal(&g_eq, &pole_a));
625
626 let dir_a = calculate_direction(
627 Angle::from(Degrees(0.0)),
628 Angle::from(Degrees(0.0)),
629 angle_90,
630 );
631 assert!(are_orthogonal(&g_eq, &dir_a));
632 assert!(are_orthogonal(&pole_a, &dir_a));
633 assert_eq!(dir_a, direction(&g_eq, &pole_a));
634
635 let north_pole = Vector3d::new(0.0, 0.0, 1.0);
636 assert_eq!(north_pole, pole_a);
637
638 let result = g_eq.cross(&e_eq);
639 assert_eq!(north_pole, result);
640
641 let result = calculate_azimuth(&g_eq, &pole_a);
642 assert_eq!(angle_90, result);
643
644 let pole_b = calculate_pole(
645 Angle::from(Degrees(0.0)),
646 Angle::from(Degrees(0.0)),
647 -angle_90,
648 );
649 assert!(are_orthogonal(&g_eq, &pole_b));
650
651 let dir_b = calculate_direction(
652 Angle::from(Degrees(0.0)),
653 Angle::from(Degrees(0.0)),
654 -angle_90,
655 );
656 assert!(are_orthogonal(&g_eq, &dir_b));
657 assert!(are_orthogonal(&pole_b, &dir_b));
658 assert_eq!(dir_b, direction(&g_eq, &pole_b));
659
660 let south_pole = Vector3d::new(0.0, 0.0, -1.0);
661 assert_eq!(south_pole, pole_b);
662
663 let result = g_eq.cross(&w_eq);
664 assert_eq!(south_pole, result);
665
666 let result = calculate_azimuth(&g_eq, &pole_b);
667 assert_eq!(-angle_90, result);
668 }
669
670 #[test]
671 fn test_calculate_position() {
672 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
674
675 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
677
678 let pole_0 = g_eq.cross(&e_eq);
679
680 let angle_90 = Angle::from(Degrees(90.0));
681
682 let pos_1 = position(&g_eq, &direction(&g_eq, &pole_0), angle_90);
683 assert_eq!(e_eq, pos_1);
684
685 let pos_2 = rotate_position(&g_eq, &pole_0, Angle::default(), angle_90);
686 assert_eq!(e_eq, pos_2);
687
688 let pos_3 = rotate_position(&g_eq, &pole_0, angle_90, angle_90);
689 assert_eq!(pole_0, pos_3);
690 }
691
692 #[test]
693 fn test_calculate_cross_track_distance_and_square() {
694 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
696
697 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
699
700 let pole_0 = g_eq.cross(&e_eq);
701
702 let longitude = Degrees(1.0);
703
704 for lat in -89..90 {
705 let latitude = Degrees(lat as f64);
706 let latlong = LatLong::new(latitude, longitude);
707 let point = Vector3d::from(&latlong);
708
709 let expected = (lat as f64).to_radians();
710 let xtd = cross_track_distance(&pole_0, &point);
711 let tolerance = if (-83..84).contains(&lat) {
713 2.0 * f64::EPSILON
714 } else {
715 32.0 * f64::EPSILON
716 };
717 assert!(is_within_tolerance(expected, xtd.0, tolerance));
718
719 let expected = great_circle::gc2e_distance(Radians(expected));
720 let expected = expected * expected;
721 let xtd2 = sq_cross_track_distance(&pole_0, &point);
722 let tolerance = if (-83..84).contains(&lat) {
724 4.0 * f64::EPSILON
725 } else {
726 64.0 * f64::EPSILON
727 };
728 assert!(is_within_tolerance(expected, xtd2, tolerance));
729 }
730 }
731
732 #[test]
733 fn test_calculate_along_track_distance_and_square() {
734 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
736
737 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
739
740 let pole_0 = g_eq.cross(&e_eq);
741
742 let latitude = Degrees(1.0);
744
745 for lon in -179..180 {
746 let longitude = Degrees(lon as f64);
747 let latlong = LatLong::new(latitude, longitude);
748 let point = Vector3d::from(&latlong);
749
750 let expected = (lon as f64).to_radians();
751 let atd = along_track_distance(&g_eq, &pole_0, &point);
752 let tolerance = if (-153..154).contains(&lon) {
754 2.0 * f64::EPSILON
755 } else {
756 32.0 * f64::EPSILON
757 };
758 assert!(is_within_tolerance(expected, atd.0, tolerance));
759
760 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
761 assert!(is_within_tolerance(expected, atd.0, tolerance));
762 assert!(is_within_tolerance(1_f64.to_radians(), xtd.0, f64::EPSILON));
763
764 let expected = great_circle::gc2e_distance(Radians(expected));
765 let expected = expected * expected;
766 let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
767 let tolerance = if (-86..87).contains(&lon) {
769 2.0 * f64::EPSILON
770 } else {
771 32.0 * f64::EPSILON
772 };
773 assert!(is_within_tolerance(expected, atd2, tolerance));
774 }
775 }
776
777 #[test]
778 fn test_special_cases() {
779 let g_eq = Vector3d::new(1.0, 0.0, 0.0);
781
782 let e_eq = Vector3d::new(0.0, 1.0, 0.0);
784
785 let pole_0 = g_eq.cross(&e_eq);
786
787 assert_eq!(0.0, along_track_distance(&g_eq, &pole_0, &pole_0).0);
789 assert_eq!(0.0, sq_along_track_distance(&g_eq, &pole_0, &pole_0));
790
791 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &g_eq);
792 assert_eq!(0.0, atd.0);
793 assert_eq!(0.0, xtd.0);
794
795 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &pole_0);
796 assert_eq!(0.0, atd.0);
797 assert_eq!(core::f64::consts::FRAC_PI_2, xtd.0);
798
799 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &-pole_0);
800 assert_eq!(0.0, atd.0);
801 assert_eq!(-core::f64::consts::FRAC_PI_2, xtd.0);
802
803 let near_north_pole = LatLong::new(Degrees(89.99999), Degrees(0.0));
805 let p = Vector3d::from(&near_north_pole);
806 let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &p);
807 assert_eq!(0.0, atd.0);
808 assert!(is_within_tolerance(
809 core::f64::consts::FRAC_PI_2,
810 xtd.0,
811 0.000001
812 ));
813 }
814}