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