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 );
551 (
552 distance_0 + arc_0.length().half(),
553 distance_1 + arc_1.length().half(),
554 )
555}
556
557#[must_use]
590pub fn calculate_intersection_point(arc_0: &Arc, arc_1: &Arc) -> Option<Vector3d> {
591 let (point, angle) = vector::intersection::calculate_reference_point_and_angle(
592 &arc_0.mid_point(),
593 &arc_0.pole(),
594 &arc_1.mid_point(),
595 &arc_1.pole(),
596 );
597
598 let distance_0 = vector::calculate_great_circle_atd(&arc_0.mid_point(), &arc_0.pole(), &point);
600 let distance_1 = vector::calculate_great_circle_atd(&arc_1.mid_point(), &arc_1.pole(), &point);
601
602 let arcs_are_coincident = angle.sin().0 == 0.0;
603 let arcs_intersect_or_overlap = if arcs_are_coincident {
604 distance_0.abs() + distance_1.abs()
606 <= arc_0.length().half() + arc_1.length().half() + Radians(great_circle::MIN_VALUE)
607 } else {
608 (distance_0.abs() <= arc_0.length().half() + Radians(great_circle::MIN_VALUE))
610 && distance_1.abs() <= (arc_1.length().half() + Radians(great_circle::MIN_VALUE))
611 };
612
613 if arcs_intersect_or_overlap {
614 Some(point)
615 } else {
616 None
617 }
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623 use angle_sc::{Degrees, is_within_tolerance};
624
625 #[test]
626 fn test_is_valid_latitude() {
627 assert!(!is_valid_latitude(-90.0001));
629 assert!(is_valid_latitude(-90.0));
631 assert!(is_valid_latitude(90.0));
633 assert!(!is_valid_latitude(90.0001));
635 }
636
637 #[test]
638 fn test_is_valid_longitude() {
639 assert!(!is_valid_longitude(-180.0001));
641 assert!(is_valid_longitude(-180.0));
643 assert!(is_valid_longitude(180.0));
645 assert!(!is_valid_longitude(180.0001));
647 }
648
649 #[test]
650 fn test_latlong_traits() {
651 let a = LatLong::try_from((0.0, 90.0)).unwrap();
652
653 assert!(a.is_valid());
654
655 let a_clone = a.clone();
656 assert!(a_clone == a);
657
658 assert_eq!(Degrees(0.0), a.lat());
659 assert_eq!(Degrees(90.0), a.lon());
660
661 assert!(!a.is_south_of(&a));
662 assert!(!a.is_west_of(&a));
663
664 let b = LatLong::try_from((-10.0, -91.0)).unwrap();
665 assert!(b.is_south_of(&a));
666 assert!(b.is_west_of(&a));
667
668 println!("LatLong: {:?}", a);
669
670 let invalid_lat = LatLong::try_from((91.0, 0.0));
671 assert_eq!(Err(LatLongError::Latitude(91.0)), invalid_lat);
672 println!("invalid_lat: {:?}", invalid_lat);
673
674 let invalid_lon = LatLong::try_from((0.0, 181.0));
675 assert_eq!(Err(LatLongError::Longitude(181.0)), invalid_lon);
676 println!("invalid_lon: {:?}", invalid_lon);
677 }
678
679 #[test]
680 fn test_vector3d_traits() {
681 let a = LatLong::try_from((0.0, 90.0)).unwrap();
682 let point = Vector3d::from(&a);
683
684 assert_eq!(0.0, point.x);
685 assert_eq!(1.0, point.y);
686 assert_eq!(0.0, point.z);
687
688 assert_eq!(Degrees(0.0), Degrees::from(vector::latitude(&point)));
689 assert_eq!(Degrees(90.0), Degrees::from(vector::longitude(&point)));
690
691 let result = LatLong::from(&point);
692 assert_eq!(a, result);
693 }
694
695 #[test]
696 fn test_great_circle_90n_0n_0e() {
697 let a = LatLong::new(Degrees(90.0), Degrees(0.0));
698 let b = LatLong::new(Degrees(0.0), Degrees(0.0));
699 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
700
701 assert!(is_within_tolerance(
702 core::f64::consts::FRAC_PI_2,
703 dist.0,
704 f64::EPSILON
705 ));
706 assert_eq!(180.0, Degrees::from(azimuth).0);
707
708 let dist = haversine_distance(&a, &b);
709 assert!(is_within_tolerance(
710 core::f64::consts::FRAC_PI_2,
711 dist.0,
712 f64::EPSILON
713 ));
714 }
715
716 #[test]
717 fn test_great_circle_90s_0n_50e() {
718 let a = LatLong::new(Degrees(-90.0), Degrees(0.0));
719 let b = LatLong::new(Degrees(0.0), Degrees(50.0));
720 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
721
722 assert!(is_within_tolerance(
723 core::f64::consts::FRAC_PI_2,
724 dist.0,
725 f64::EPSILON
726 ));
727 assert_eq!(0.0, Degrees::from(azimuth).0);
728
729 let dist = haversine_distance(&a, &b);
730 assert!(is_within_tolerance(
731 core::f64::consts::FRAC_PI_2,
732 dist.0,
733 f64::EPSILON
734 ));
735 }
736
737 #[test]
738 fn test_great_circle_0n_60e_0n_60w() {
739 let a = LatLong::new(Degrees(0.0), Degrees(60.0));
740 let b = LatLong::new(Degrees(0.0), Degrees(-60.0));
741 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
742
743 assert!(is_within_tolerance(
744 2.0 * core::f64::consts::FRAC_PI_3,
745 dist.0,
746 2.0 * f64::EPSILON
747 ));
748 assert_eq!(-90.0, Degrees::from(azimuth).0);
749
750 let dist = haversine_distance(&a, &b);
751 assert!(is_within_tolerance(
752 2.0 * core::f64::consts::FRAC_PI_3,
753 dist.0,
754 2.0 * f64::EPSILON
755 ));
756 }
757
758 #[test]
759 fn test_arc() {
760 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
762
763 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
765
766 let mut arc = Arc::between_positions(&g_eq, &e_eq);
767 let arc = arc.set_half_width(Radians(0.01));
768 assert!(arc.is_valid());
769 assert_eq!(Radians(0.01), arc.half_width());
770
771 assert_eq!(Vector3d::from(&g_eq), arc.a());
772 assert_eq!(Vector3d::new(0.0, 0.0, 1.0), arc.pole());
773 assert!(is_within_tolerance(
774 core::f64::consts::FRAC_PI_2,
775 arc.length().0,
776 f64::EPSILON
777 ));
778 assert_eq!(Angle::from(Degrees(90.0)), arc.azimuth());
779 let b = Vector3d::from(&e_eq);
780 assert!(is_within_tolerance(
781 0.0,
782 vector::distance(&b, &arc.b()),
783 f64::EPSILON
784 ));
785
786 let mid_point = arc.mid_point();
787 assert_eq!(0.0, mid_point.z);
788 assert!(is_within_tolerance(
789 45.0,
790 Degrees::from(vector::longitude(&mid_point)).0,
791 32.0 * f64::EPSILON
792 ));
793
794 let start_arc = arc.end_arc(false);
795 assert_eq!(0.02, start_arc.length().0);
796
797 let start_arc_a = start_arc.a();
798 assert_eq!(start_arc_a, arc.perp_position(&arc.a(), Radians(0.01)));
799
800 let angle_90 = Angle::from(Degrees(90.0));
801 let pole_0 = Vector3d::new(0.0, 0.0, 1.0);
802 assert!(vector::distance(&pole_0, &arc.angle_position(angle_90)) <= f64::EPSILON);
803
804 let end_arc = arc.end_arc(true);
805 assert_eq!(0.02, end_arc.length().0);
806
807 let end_arc_a = end_arc.a();
808 assert_eq!(end_arc_a, arc.perp_position(&arc.b(), Radians(0.01)));
809 }
810
811 #[test]
812 fn test_north_and_south_poles() {
813 let north_pole = LatLong::new(Degrees(90.0), Degrees(0.0));
814 let south_pole = LatLong::new(Degrees(-90.0), Degrees(0.0));
815
816 let (azimuth, distance) = calculate_azimuth_and_distance(&south_pole, &north_pole);
817 assert_eq!(0.0, Degrees::from(azimuth).0);
818 assert_eq!(core::f64::consts::PI, distance.0);
819
820 let (azimuth, distance) = calculate_azimuth_and_distance(&north_pole, &south_pole);
821 assert_eq!(180.0, Degrees::from(azimuth).0);
822 assert_eq!(core::f64::consts::PI, distance.0);
823
824 let e_eq = LatLong::new(Degrees(0.0), Degrees(50.0));
826
827 let arc = Arc::between_positions(&north_pole, &e_eq);
828 assert!(is_within_tolerance(
829 e_eq.lat().0,
830 LatLong::from(&arc.b()).lat().abs().0,
831 1e-13
832 ));
833 assert!(is_within_tolerance(
834 e_eq.lon().0,
835 LatLong::from(&arc.b()).lon().0,
836 50.0 * f64::EPSILON
837 ));
838
839 let arc = Arc::between_positions(&south_pole, &e_eq);
840 assert!(is_within_tolerance(
841 e_eq.lat().0,
842 LatLong::from(&arc.b()).lat().abs().0,
843 1e-13
844 ));
845 assert!(is_within_tolerance(
846 e_eq.lon().0,
847 LatLong::from(&arc.b()).lon().0,
848 50.0 * f64::EPSILON
849 ));
850
851 let w_eq = LatLong::new(Degrees(0.0), Degrees(-140.0));
852
853 let arc = Arc::between_positions(&north_pole, &w_eq);
854 assert!(is_within_tolerance(
855 w_eq.lat().0,
856 LatLong::from(&arc.b()).lat().abs().0,
857 1e-13
858 ));
859 assert!(is_within_tolerance(
860 w_eq.lon().0,
861 LatLong::from(&arc.b()).lon().0,
862 256.0 * f64::EPSILON
863 ));
864
865 let arc = Arc::between_positions(&south_pole, &w_eq);
866 assert!(is_within_tolerance(
867 w_eq.lat().0,
868 LatLong::from(&arc.b()).lat().abs().0,
869 1e-13
870 ));
871 assert!(is_within_tolerance(
872 w_eq.lon().0,
873 LatLong::from(&arc.b()).lon().0,
874 256.0 * f64::EPSILON
875 ));
876
877 let invalid_arc = Arc::try_from((&north_pole, &north_pole));
878 assert_eq!(Err(ArcError::PositionsTooClose(0.0)), invalid_arc);
879 println!("invalid_arc: {:?}", invalid_arc);
880
881 let arc = Arc::between_positions(&north_pole, &north_pole);
882 assert_eq!(north_pole, LatLong::from(&arc.b()));
883
884 let invalid_arc = Arc::try_from((&north_pole, &south_pole));
885 assert_eq!(Err(ArcError::PositionsTooFar(4.0)), invalid_arc);
886 println!("invalid_arc: {:?}", invalid_arc);
887
888 let arc = Arc::between_positions(&north_pole, &south_pole);
889 assert_eq!(south_pole, LatLong::from(&arc.b()));
890
891 let arc = Arc::between_positions(&south_pole, &north_pole);
892 assert_eq!(north_pole, LatLong::from(&arc.b()));
893
894 let arc = Arc::between_positions(&south_pole, &south_pole);
895 assert_eq!(south_pole, LatLong::from(&arc.b()));
896 }
897
898 #[test]
899 fn test_arc_atd_and_xtd() {
900 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
902
903 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
905
906 let arc = Arc::try_from((&g_eq, &e_eq)).unwrap();
907 assert!(arc.is_valid());
908
909 let start_arc = arc.end_arc(false);
910 assert_eq!(0.0, start_arc.length().0);
911
912 let start_arc_a = start_arc.a();
913 assert_eq!(arc.a(), start_arc_a);
914
915 let longitude = Degrees(1.0);
916
917 for lat in -83..84 {
920 let latitude = Degrees(lat as f64);
921 let latlong = LatLong::new(latitude, longitude);
922 let point = Vector3d::from(&latlong);
923
924 let expected = (lat as f64).to_radians();
925 let (atd, xtd) = arc.calculate_atd_and_xtd(&point);
926 assert!(is_within_tolerance(1_f64.to_radians(), atd.0, f64::EPSILON));
927 assert!(is_within_tolerance(expected, xtd.0, 2.0 * f64::EPSILON));
928
929 let d = arc.shortest_distance(&point);
930 assert!(is_within_tolerance(expected.abs(), d.0, 2.0 * f64::EPSILON));
931 }
932
933 let point = Vector3d::from(&g_eq);
934 let d = arc.shortest_distance(&point);
935 assert_eq!(0.0, d.0);
936
937 let point = Vector3d::from(&e_eq);
938 let d = arc.shortest_distance(&point);
939 assert_eq!(0.0, d.0);
940
941 let latlong = LatLong::new(Degrees(0.0), Degrees(-1.0));
942 let point = Vector3d::from(&latlong);
943 let d = arc.shortest_distance(&point);
944 assert!(is_within_tolerance(1_f64.to_radians(), d.0, f64::EPSILON));
945
946 let point = -point;
947 let d = arc.shortest_distance(&point);
948 assert!(is_within_tolerance(89_f64.to_radians(), d.0, f64::EPSILON));
949
950 let latlong = LatLong::new(Degrees(0.0), Degrees(-160.0));
952 let point = Vector3d::from(&latlong);
953 let d = arc.shortest_distance(&point);
954 assert_eq!(
956 great_circle::e2gc_distance(vector::distance(&arc.b(), &point)),
957 d
958 );
959 }
960
961 #[test]
962 fn test_arc_intersection_point() {
963 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
967 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
968 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
969 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
970
971 let arc_0 = Arc::try_from((&istanbul, &washington)).unwrap();
972 let arc_1 = Arc::try_from((&reyjavik, &accra)).unwrap();
973
974 let intersection_point = calculate_intersection_point(&arc_0, &arc_1).unwrap();
975 let lat_long = LatLong::from(&intersection_point);
976 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
978 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
980
981 let intersection_point = calculate_intersection_point(&arc_1, &arc_0).unwrap();
983 let lat_long = LatLong::from(&intersection_point);
984 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
986 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
988 }
989
990 #[test]
991 fn test_arc_intersection_same_great_circles() {
992 let south_pole_1 = LatLong::new(Degrees(-88.0), Degrees(-180.0));
993 let south_pole_2 = LatLong::new(Degrees(-87.0), Degrees(0.0));
994
995 let arc_0 = Arc::try_from((&south_pole_1, &south_pole_2)).unwrap();
996
997 let intersection_lengths = calculate_intersection_distances(&arc_0, &arc_0);
998 assert_eq!(arc_0.length().half(), intersection_lengths.0);
999 assert_eq!(arc_0.length().half(), intersection_lengths.1);
1000
1001 let intersection_point = calculate_intersection_point(&arc_0, &arc_0).unwrap();
1002 assert!(is_within_tolerance(
1003 arc_0.length().half().0,
1004 great_circle::e2gc_distance(vector::distance(&arc_0.a(), &intersection_point)).0,
1005 f64::EPSILON
1006 ));
1007
1008 let south_pole_3 = LatLong::new(Degrees(-85.0), Degrees(0.0));
1009 let south_pole_4 = LatLong::new(Degrees(-86.0), Degrees(0.0));
1010 let arc_1 = Arc::try_from((&south_pole_3, &south_pole_4)).unwrap();
1011 let intersection_point = calculate_intersection_point(&arc_0, &arc_1);
1012 assert!(intersection_point.is_none());
1013 }
1014}