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