1#![cfg_attr(not(test), no_std)]
113
114extern crate angle_sc;
115extern crate nalgebra as na;
116
117pub mod great_circle;
118pub mod vector;
119
120pub use angle_sc::{Angle, Degrees, Radians, Validate};
121use thiserror::Error;
122
123#[must_use]
127pub fn is_valid_latitude(degrees: f64) -> bool {
128 (-90.0..=90.0).contains(°rees)
129}
130
131#[must_use]
135pub fn is_valid_longitude(degrees: f64) -> bool {
136 (-180.0..=180.0).contains(°rees)
137}
138
139#[derive(Clone, Copy, Debug, PartialEq)]
141pub struct LatLong {
142 lat: Degrees,
143 lon: Degrees,
144}
145
146impl Validate for LatLong {
147 fn is_valid(&self) -> bool {
152 is_valid_latitude(self.lat.0) && is_valid_longitude(self.lon.0)
153 }
154}
155
156impl LatLong {
157 #[must_use]
158 pub const fn new(lat: Degrees, lon: Degrees) -> Self {
159 Self { lat, lon }
160 }
161
162 #[must_use]
163 pub const fn lat(&self) -> Degrees {
164 self.lat
165 }
166
167 #[must_use]
168 pub const fn lon(&self) -> Degrees {
169 self.lon
170 }
171
172 #[must_use]
179 pub fn is_south_of(&self, a: &Self) -> bool {
180 self.lat.0 < a.lat.0
181 }
182
183 #[must_use]
190 pub fn is_west_of(&self, a: &Self) -> bool {
191 (a.lon() - self.lon).0 < 0.0
192 }
193}
194
195#[derive(Error, Debug, PartialEq)]
197pub enum LatLongError {
198 #[error("invalid latitude value: `{0}`")]
199 Latitude(f64),
200 #[error("invalid longitude value: `{0}`")]
201 Longitude(f64),
202}
203
204impl TryFrom<(f64, f64)> for LatLong {
205 type Error = LatLongError;
206
207 fn try_from(lat_long: (f64, f64)) -> Result<Self, Self::Error> {
211 if !is_valid_latitude(lat_long.0) {
212 Err(LatLongError::Latitude(lat_long.0))
213 } else if !is_valid_longitude(lat_long.1) {
214 Err(LatLongError::Longitude(lat_long.1))
215 } else {
216 Ok(Self::new(Degrees(lat_long.0), Degrees(lat_long.1)))
217 }
218 }
219}
220
221#[must_use]
228pub fn calculate_azimuth_and_distance(a: &LatLong, b: &LatLong) -> (Angle, Radians) {
229 let a_lat = Angle::from(a.lat);
230 let b_lat = Angle::from(b.lat);
231 let delta_long = Angle::from((b.lon, a.lon));
232 (
233 great_circle::calculate_gc_azimuth(a_lat, b_lat, delta_long),
234 great_circle::calculate_gc_distance(a_lat, b_lat, delta_long),
235 )
236}
237
238#[must_use]
246pub fn haversine_distance(a: &LatLong, b: &LatLong) -> Radians {
247 let a_lat = Angle::from(a.lat);
248 let b_lat = Angle::from(b.lat);
249 let delta_lat = Angle::from((b.lat, a.lat));
250 let delta_long = Angle::from(b.lon - a.lon);
251 great_circle::calculate_haversine_distance(a_lat, b_lat, delta_long, delta_lat)
252}
253
254#[allow(clippy::module_name_repetitions)]
256pub type Vector3d = na::Vector3<f64>;
257
258impl From<&LatLong> for Vector3d {
259 fn from(a: &LatLong) -> Self {
267 vector::to_point(Angle::from(a.lat), Angle::from(a.lon))
268 }
269}
270
271impl From<&Vector3d> for LatLong {
272 fn from(value: &Vector3d) -> Self {
274 Self::new(
275 Degrees::from(vector::latitude(value)),
276 Degrees::from(vector::longitude(value)),
277 )
278 }
279}
280
281#[derive(Clone, Copy, Debug, PartialEq)]
283pub struct Arc {
284 a: Vector3d,
286 pole: Vector3d,
288 length: Radians,
290 half_width: Radians,
292}
293
294impl Validate for Arc {
295 fn is_valid(&self) -> bool {
300 vector::is_unit(&self.a)
301 && vector::is_unit(&self.pole)
302 && vector::are_orthogonal(&self.a, &self.pole)
303 && !self.length.0.is_sign_negative()
304 && !self.half_width.0.is_sign_negative()
305 }
306}
307
308impl Arc {
309 #[must_use]
316 pub const fn new(a: Vector3d, pole: Vector3d, length: Radians, half_width: Radians) -> Self {
317 Self {
318 a,
319 pole,
320 length,
321 half_width,
322 }
323 }
324
325 #[must_use]
331 pub fn from_lat_lon_azi_length(a: &LatLong, azimuth: Angle, length: Radians) -> Self {
332 Self::new(
333 Vector3d::from(a),
334 vector::calculate_pole(Angle::from(a.lat()), Angle::from(a.lon()), azimuth),
335 length,
336 Radians(0.0),
337 )
338 }
339
340 #[must_use]
345 pub fn between_positions(a: &LatLong, b: &LatLong) -> Self {
346 let (azimuth, length) = calculate_azimuth_and_distance(a, b);
347 let a_lat = Angle::from(a.lat());
348 if a_lat.cos().0 < great_circle::MIN_VALUE {
350 Self::from_lat_lon_azi_length(&LatLong::new(a.lat(), b.lon()), azimuth, length)
352 } else {
353 Self::from_lat_lon_azi_length(a, azimuth, length)
354 }
355 }
356
357 #[must_use]
361 pub const fn set_half_width(&mut self, half_width: Radians) -> &mut Self {
362 self.half_width = half_width;
363 self
364 }
365
366 #[must_use]
368 pub const fn a(&self) -> Vector3d {
369 self.a
370 }
371
372 #[must_use]
374 pub const fn pole(&self) -> Vector3d {
375 self.pole
376 }
377
378 #[must_use]
380 pub const fn length(&self) -> Radians {
381 self.length
382 }
383
384 #[must_use]
386 pub const fn half_width(&self) -> Radians {
387 self.half_width
388 }
389
390 #[must_use]
392 pub fn azimuth(&self) -> Angle {
393 vector::calculate_azimuth(&self.a, &self.pole)
394 }
395
396 #[must_use]
398 pub fn direction(&self) -> Vector3d {
399 vector::direction(&self.a, &self.pole)
400 }
401
402 #[must_use]
404 pub fn position(&self, distance: Radians) -> Vector3d {
405 vector::position(&self.a, &self.direction(), Angle::from(distance))
406 }
407
408 #[must_use]
410 pub fn b(&self) -> Vector3d {
411 self.position(self.length)
412 }
413
414 #[must_use]
416 pub fn mid_point(&self) -> Vector3d {
417 self.position(self.length.half())
418 }
419
420 #[must_use]
427 pub fn perp_position(&self, point: &Vector3d, distance: Radians) -> Vector3d {
428 vector::position(point, &self.pole, Angle::from(distance))
429 }
430
431 #[must_use]
437 pub fn angle_position(&self, angle: Angle) -> Vector3d {
438 vector::rotate_position(&self.a, &self.pole, angle, Angle::from(self.length))
439 }
440
441 #[must_use]
447 pub fn end_arc(&self, at_b: bool) -> Self {
448 let p = if at_b { self.b() } else { self.a };
449 let pole = vector::direction(&p, &self.pole);
450 if self.half_width.0 < great_circle::MIN_VALUE {
451 Self::new(p, pole, Radians(0.0), Radians(0.0))
452 } else {
453 let a = self.perp_position(&p, self.half_width);
454 Self::new(a, pole, self.half_width + self.half_width, Radians(0.0))
455 }
456 }
457
458 #[must_use]
465 pub fn calculate_atd_and_xtd(&self, point: &Vector3d) -> (Radians, Radians) {
466 vector::calculate_atd_and_xtd(&self.a, &self.pole(), point)
467 }
468
469 #[must_use]
475 pub fn shortest_distance(&self, point: &Vector3d) -> Radians {
476 let (atd, xtd) = self.calculate_atd_and_xtd(point);
477 if vector::intersection::is_alongside(atd, self.length, Radians(4.0 * f64::EPSILON)) {
478 xtd.abs()
479 } else {
480 let atd_centre = atd - self.length.half();
482 let p = if atd_centre.0.is_sign_negative() {
483 self.a
484 } else {
485 self.b()
486 };
487 great_circle::e2gc_distance(vector::distance(&p, point))
488 }
489 }
490}
491
492#[derive(Error, Debug, PartialEq)]
494pub enum ArcError {
495 #[error("positions are too close: `{0}`")]
496 PositionsTooClose(f64),
497 #[error("positions are too far apart: `{0}`")]
498 PositionsTooFar(f64),
499}
500
501impl TryFrom<(&LatLong, &LatLong)> for Arc {
502 type Error = ArcError;
503
504 fn try_from(params: (&LatLong, &LatLong)) -> Result<Self, Self::Error> {
508 let a = Vector3d::from(params.0);
510 let b = Vector3d::from(params.1);
511 vector::normalise(&a.cross(&b), vector::MIN_SQ_NORM).map_or_else(
513 || {
514 let sq_d = vector::sq_distance(&a, &b);
515 if sq_d < 1.0 {
516 Err(ArcError::PositionsTooClose(sq_d))
517 } else {
518 Err(ArcError::PositionsTooFar(sq_d))
519 }
520 },
521 |pole| {
522 Ok(Self::new(
523 a,
524 pole,
525 great_circle::e2gc_distance(vector::distance(&a, &b)),
526 Radians(0.0),
527 ))
528 },
529 )
530 }
531}
532
533#[must_use]
542pub fn calculate_intersection_distances(arc_0: &Arc, arc_1: &Arc) -> (Radians, Radians) {
543 vector::intersection::calculate_intersection_point_distances(
544 &arc_0.a,
545 &arc_0.pole,
546 arc_0.length(),
547 &arc_1.a,
548 &arc_1.pole,
549 arc_1.length(),
550 &(0.5 * (arc_0.mid_point() + arc_1.mid_point())),
551 )
552}
553
554#[must_use]
587pub fn calculate_intersection_point(arc_0: &Arc, arc_1: &Arc) -> Option<Vector3d> {
588 let (distance1, distance2) = calculate_intersection_distances(arc_0, arc_1);
589
590 if vector::intersection::is_alongside(distance1, arc_0.length(), Radians(4.0 * f64::EPSILON))
592 && vector::intersection::is_alongside(
593 distance2,
594 arc_1.length(),
595 Radians(4.0 * f64::EPSILON),
596 )
597 {
598 Some(arc_0.position(distance1.clamp(arc_0.length())))
599 } else {
600 None
601 }
602}
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607 use angle_sc::{Degrees, is_within_tolerance};
608
609 #[test]
610 fn test_is_valid_latitude() {
611 assert!(!is_valid_latitude(-90.0001));
613 assert!(is_valid_latitude(-90.0));
615 assert!(is_valid_latitude(90.0));
617 assert!(!is_valid_latitude(90.0001));
619 }
620
621 #[test]
622 fn test_is_valid_longitude() {
623 assert!(!is_valid_longitude(-180.0001));
625 assert!(is_valid_longitude(-180.0));
627 assert!(is_valid_longitude(180.0));
629 assert!(!is_valid_longitude(180.0001));
631 }
632
633 #[test]
634 fn test_latlong_traits() {
635 let a = LatLong::try_from((0.0, 90.0)).unwrap();
636
637 assert!(a.is_valid());
638
639 let a_clone = a.clone();
640 assert!(a_clone == a);
641
642 assert_eq!(Degrees(0.0), a.lat());
643 assert_eq!(Degrees(90.0), a.lon());
644
645 assert!(!a.is_south_of(&a));
646 assert!(!a.is_west_of(&a));
647
648 let b = LatLong::try_from((-10.0, -91.0)).unwrap();
649 assert!(b.is_south_of(&a));
650 assert!(b.is_west_of(&a));
651
652 println!("LatLong: {:?}", a);
653
654 let invalid_lat = LatLong::try_from((91.0, 0.0));
655 assert_eq!(Err(LatLongError::Latitude(91.0)), invalid_lat);
656 println!("invalid_lat: {:?}", invalid_lat);
657
658 let invalid_lon = LatLong::try_from((0.0, 181.0));
659 assert_eq!(Err(LatLongError::Longitude(181.0)), invalid_lon);
660 println!("invalid_lon: {:?}", invalid_lon);
661 }
662
663 #[test]
664 fn test_vector3d_traits() {
665 let a = LatLong::try_from((0.0, 90.0)).unwrap();
666 let point = Vector3d::from(&a);
667
668 assert_eq!(0.0, point.x);
669 assert_eq!(1.0, point.y);
670 assert_eq!(0.0, point.z);
671
672 assert_eq!(Degrees(0.0), Degrees::from(vector::latitude(&point)));
673 assert_eq!(Degrees(90.0), Degrees::from(vector::longitude(&point)));
674
675 let result = LatLong::from(&point);
676 assert_eq!(a, result);
677 }
678
679 #[test]
680 fn test_great_circle_90n_0n_0e() {
681 let a = LatLong::new(Degrees(90.0), Degrees(0.0));
682 let b = LatLong::new(Degrees(0.0), Degrees(0.0));
683 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
684
685 assert!(is_within_tolerance(
686 core::f64::consts::FRAC_PI_2,
687 dist.0,
688 f64::EPSILON
689 ));
690 assert_eq!(180.0, Degrees::from(azimuth).0);
691
692 let dist = haversine_distance(&a, &b);
693 assert!(is_within_tolerance(
694 core::f64::consts::FRAC_PI_2,
695 dist.0,
696 f64::EPSILON
697 ));
698 }
699
700 #[test]
701 fn test_great_circle_90s_0n_50e() {
702 let a = LatLong::new(Degrees(-90.0), Degrees(0.0));
703 let b = LatLong::new(Degrees(0.0), Degrees(50.0));
704 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
705
706 assert!(is_within_tolerance(
707 core::f64::consts::FRAC_PI_2,
708 dist.0,
709 f64::EPSILON
710 ));
711 assert_eq!(0.0, Degrees::from(azimuth).0);
712
713 let dist = haversine_distance(&a, &b);
714 assert!(is_within_tolerance(
715 core::f64::consts::FRAC_PI_2,
716 dist.0,
717 f64::EPSILON
718 ));
719 }
720
721 #[test]
722 fn test_great_circle_0n_60e_0n_60w() {
723 let a = LatLong::new(Degrees(0.0), Degrees(60.0));
724 let b = LatLong::new(Degrees(0.0), Degrees(-60.0));
725 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
726
727 assert!(is_within_tolerance(
728 2.0 * core::f64::consts::FRAC_PI_3,
729 dist.0,
730 2.0 * f64::EPSILON
731 ));
732 assert_eq!(-90.0, Degrees::from(azimuth).0);
733
734 let dist = haversine_distance(&a, &b);
735 assert!(is_within_tolerance(
736 2.0 * core::f64::consts::FRAC_PI_3,
737 dist.0,
738 2.0 * f64::EPSILON
739 ));
740 }
741
742 #[test]
743 fn test_arc() {
744 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
746
747 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
749
750 let mut arc = Arc::between_positions(&g_eq, &e_eq);
751 let arc = arc.set_half_width(Radians(0.01));
752 assert!(arc.is_valid());
753 assert_eq!(Radians(0.01), arc.half_width());
754
755 assert_eq!(Vector3d::from(&g_eq), arc.a());
756 assert_eq!(Vector3d::new(0.0, 0.0, 1.0), arc.pole());
757 assert!(is_within_tolerance(
758 core::f64::consts::FRAC_PI_2,
759 arc.length().0,
760 f64::EPSILON
761 ));
762 assert_eq!(Angle::from(Degrees(90.0)), arc.azimuth());
763 let b = Vector3d::from(&e_eq);
764 assert!(is_within_tolerance(
765 0.0,
766 vector::distance(&b, &arc.b()),
767 f64::EPSILON
768 ));
769
770 let mid_point = arc.mid_point();
771 assert_eq!(0.0, mid_point.z);
772 assert!(is_within_tolerance(
773 45.0,
774 Degrees::from(vector::longitude(&mid_point)).0,
775 32.0 * f64::EPSILON
776 ));
777
778 let start_arc = arc.end_arc(false);
779 assert_eq!(0.02, start_arc.length().0);
780
781 let start_arc_a = start_arc.a();
782 assert_eq!(start_arc_a, arc.perp_position(&arc.a(), Radians(0.01)));
783
784 let angle_90 = Angle::from(Degrees(90.0));
785 let pole_0 = Vector3d::new(0.0, 0.0, 1.0);
786 assert!(vector::distance(&pole_0, &arc.angle_position(angle_90)) <= f64::EPSILON);
787
788 let end_arc = arc.end_arc(true);
789 assert_eq!(0.02, end_arc.length().0);
790
791 let end_arc_a = end_arc.a();
792 assert_eq!(end_arc_a, arc.perp_position(&arc.b(), Radians(0.01)));
793 }
794
795 #[test]
796 fn test_north_and_south_poles() {
797 let north_pole = LatLong::new(Degrees(90.0), Degrees(0.0));
798 let south_pole = LatLong::new(Degrees(-90.0), Degrees(0.0));
799
800 let (azimuth, distance) = calculate_azimuth_and_distance(&south_pole, &north_pole);
801 assert_eq!(0.0, Degrees::from(azimuth).0);
802 assert_eq!(core::f64::consts::PI, distance.0);
803
804 let (azimuth, distance) = calculate_azimuth_and_distance(&north_pole, &south_pole);
805 assert_eq!(180.0, Degrees::from(azimuth).0);
806 assert_eq!(core::f64::consts::PI, distance.0);
807
808 let e_eq = LatLong::new(Degrees(0.0), Degrees(50.0));
810
811 let arc = Arc::between_positions(&north_pole, &e_eq);
812 assert!(is_within_tolerance(
813 e_eq.lat().0,
814 LatLong::from(&arc.b()).lat().abs().0,
815 1e-13
816 ));
817 assert!(is_within_tolerance(
818 e_eq.lon().0,
819 LatLong::from(&arc.b()).lon().0,
820 50.0 * f64::EPSILON
821 ));
822
823 let arc = Arc::between_positions(&south_pole, &e_eq);
824 assert!(is_within_tolerance(
825 e_eq.lat().0,
826 LatLong::from(&arc.b()).lat().abs().0,
827 1e-13
828 ));
829 assert!(is_within_tolerance(
830 e_eq.lon().0,
831 LatLong::from(&arc.b()).lon().0,
832 50.0 * f64::EPSILON
833 ));
834
835 let w_eq = LatLong::new(Degrees(0.0), Degrees(-140.0));
836
837 let arc = Arc::between_positions(&north_pole, &w_eq);
838 assert!(is_within_tolerance(
839 w_eq.lat().0,
840 LatLong::from(&arc.b()).lat().abs().0,
841 1e-13
842 ));
843 assert!(is_within_tolerance(
844 w_eq.lon().0,
845 LatLong::from(&arc.b()).lon().0,
846 256.0 * f64::EPSILON
847 ));
848
849 let arc = Arc::between_positions(&south_pole, &w_eq);
850 assert!(is_within_tolerance(
851 w_eq.lat().0,
852 LatLong::from(&arc.b()).lat().abs().0,
853 1e-13
854 ));
855 assert!(is_within_tolerance(
856 w_eq.lon().0,
857 LatLong::from(&arc.b()).lon().0,
858 256.0 * f64::EPSILON
859 ));
860
861 let invalid_arc = Arc::try_from((&north_pole, &north_pole));
862 assert_eq!(Err(ArcError::PositionsTooClose(0.0)), invalid_arc);
863 println!("invalid_arc: {:?}", invalid_arc);
864
865 let arc = Arc::between_positions(&north_pole, &north_pole);
866 assert_eq!(north_pole, LatLong::from(&arc.b()));
867
868 let invalid_arc = Arc::try_from((&north_pole, &south_pole));
869 assert_eq!(Err(ArcError::PositionsTooFar(4.0)), invalid_arc);
870 println!("invalid_arc: {:?}", invalid_arc);
871
872 let arc = Arc::between_positions(&north_pole, &south_pole);
873 assert_eq!(south_pole, LatLong::from(&arc.b()));
874
875 let arc = Arc::between_positions(&south_pole, &north_pole);
876 assert_eq!(north_pole, LatLong::from(&arc.b()));
877
878 let arc = Arc::between_positions(&south_pole, &south_pole);
879 assert_eq!(south_pole, LatLong::from(&arc.b()));
880 }
881
882 #[test]
883 fn test_arc_atd_and_xtd() {
884 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
886
887 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
889
890 let arc = Arc::try_from((&g_eq, &e_eq)).unwrap();
891 assert!(arc.is_valid());
892
893 let start_arc = arc.end_arc(false);
894 assert_eq!(0.0, start_arc.length().0);
895
896 let start_arc_a = start_arc.a();
897 assert_eq!(arc.a(), start_arc_a);
898
899 let longitude = Degrees(1.0);
900
901 for lat in -83..84 {
904 let latitude = Degrees(lat as f64);
905 let latlong = LatLong::new(latitude, longitude);
906 let point = Vector3d::from(&latlong);
907
908 let expected = (lat as f64).to_radians();
909 let (atd, xtd) = arc.calculate_atd_and_xtd(&point);
910 assert!(is_within_tolerance(1_f64.to_radians(), atd.0, f64::EPSILON));
911 assert!(is_within_tolerance(expected, xtd.0, 2.0 * f64::EPSILON));
912
913 let d = arc.shortest_distance(&point);
914 assert!(is_within_tolerance(expected.abs(), d.0, 2.0 * f64::EPSILON));
915 }
916
917 let point = Vector3d::from(&g_eq);
918 let d = arc.shortest_distance(&point);
919 assert_eq!(0.0, d.0);
920
921 let point = Vector3d::from(&e_eq);
922 let d = arc.shortest_distance(&point);
923 assert_eq!(0.0, d.0);
924
925 let latlong = LatLong::new(Degrees(0.0), Degrees(-1.0));
926 let point = Vector3d::from(&latlong);
927 let d = arc.shortest_distance(&point);
928 assert!(is_within_tolerance(1_f64.to_radians(), d.0, f64::EPSILON));
929
930 let point = -point;
931 let d = arc.shortest_distance(&point);
932 assert!(is_within_tolerance(89_f64.to_radians(), d.0, f64::EPSILON));
933
934 let latlong = LatLong::new(Degrees(0.0), Degrees(-160.0));
936 let point = Vector3d::from(&latlong);
937 let d = arc.shortest_distance(&point);
938 assert_eq!(
940 great_circle::e2gc_distance(vector::distance(&arc.b(), &point)),
941 d
942 );
943 }
944
945 #[test]
946 fn test_arc_intersection_point() {
947 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
951 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
952 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
953 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
954
955 let arc_0 = Arc::try_from((&istanbul, &washington)).unwrap();
956 let arc_1 = Arc::try_from((&reyjavik, &accra)).unwrap();
957
958 let intersection_point = calculate_intersection_point(&arc_0, &arc_1).unwrap();
959 let lat_long = LatLong::from(&intersection_point);
960 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
962 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
964
965 let intersection_point = calculate_intersection_point(&arc_1, &arc_0).unwrap();
967 let lat_long = LatLong::from(&intersection_point);
968 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
970 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
972 }
973
974 #[test]
975 fn test_arc_intersection_same_great_circles() {
976 let south_pole_1 = LatLong::new(Degrees(-88.0), Degrees(-180.0));
977 let south_pole_2 = LatLong::new(Degrees(-87.0), Degrees(0.0));
978
979 let arc_0 = Arc::try_from((&south_pole_1, &south_pole_2)).unwrap();
980
981 let intersection_lengths = calculate_intersection_distances(&arc_0, &arc_0);
982 assert_eq!(Radians(0.0), intersection_lengths.0);
983 assert_eq!(Radians(0.0), intersection_lengths.1);
984
985 let intersection_point = calculate_intersection_point(&arc_0, &arc_0).unwrap();
986 assert_eq!(0.0, vector::sq_distance(&arc_0.a(), &intersection_point));
987
988 let south_pole_3 = LatLong::new(Degrees(-85.0), Degrees(0.0));
989 let south_pole_4 = LatLong::new(Degrees(-86.0), Degrees(0.0));
990 let arc_1 = Arc::try_from((&south_pole_3, &south_pole_4)).unwrap();
991 let intersection_point = calculate_intersection_point(&arc_0, &arc_1);
992 assert!(intersection_point.is_none());
993 }
994}