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};
119pub use na::Vector3;
120use num_traits::{Float, float::FloatConst};
121use thiserror::Error;
122
123pub const NINETY: f64 = 90.0;
124
125#[allow(clippy::missing_panics_doc)]
129#[must_use]
130pub fn is_valid_latitude<T: Float>(degrees: T) -> bool {
131 let ninety = T::from(NINETY).expect("Could not convert constant to Float");
132 (-ninety..=ninety).contains(°rees)
133}
134
135#[allow(clippy::missing_panics_doc)]
139#[must_use]
140pub fn is_valid_longitude<T: Float>(degrees: T) -> bool {
141 let one_eighty =
142 T::from(angle_sc::ONE_HUNDRED_AND_EIGHTY).expect("Could not convert constant to Float");
143 (-one_eighty..=one_eighty).contains(°rees)
144}
145
146#[derive(Clone, Copy, Debug, Eq, PartialEq)]
148pub struct LatLong<T: Float> {
149 lat: Degrees<T>,
150 lon: Degrees<T>,
151}
152
153impl<T: Float> Validate for LatLong<T> {
154 fn is_valid(&self) -> bool {
159 is_valid_latitude(self.lat.0) && is_valid_longitude(self.lon.0)
160 }
161}
162
163impl<T: Float> LatLong<T> {
164 #[must_use]
165 pub const fn new(lat: Degrees<T>, lon: Degrees<T>) -> Self {
166 Self { lat, lon }
167 }
168
169 #[must_use]
170 pub const fn lat(&self) -> Degrees<T> {
171 self.lat
172 }
173
174 #[must_use]
175 pub const fn lon(&self) -> Degrees<T> {
176 self.lon
177 }
178
179 #[must_use]
186 pub fn is_south_of(&self, a: &Self) -> bool {
187 self.lat.0 < a.lat.0
188 }
189
190 #[must_use]
197 pub fn is_west_of(&self, a: &Self) -> bool {
198 (a.lon() - self.lon).0 < T::zero()
199 }
200}
201
202#[derive(Error, Debug, PartialEq)]
204pub enum LatLongError {
205 #[error("invalid latitude value: `{0}`")]
206 Latitude(f64),
207 #[error("invalid longitude value: `{0}`")]
208 Longitude(f64),
209}
210
211impl<T> TryFrom<(T, T)> for LatLong<T>
212where
213 T: Float,
214 f64: From<T>,
215{
216 type Error = LatLongError;
217
218 fn try_from(lat_long: (T, T)) -> Result<Self, Self::Error> {
222 if !is_valid_latitude(lat_long.0) {
223 Err(LatLongError::Latitude(f64::from(lat_long.0)))
224 } else if !is_valid_longitude(lat_long.1) {
225 Err(LatLongError::Longitude(f64::from(lat_long.1)))
226 } else {
227 Ok(Self::new(
228 Degrees::<T>(lat_long.0),
229 Degrees::<T>(lat_long.1),
230 ))
231 }
232 }
233}
234
235#[must_use]
242pub fn calculate_azimuth_and_distance<T>(a: &LatLong<T>, b: &LatLong<T>) -> (Angle<T>, Radians<T>)
243where
244 T: Float + FloatConst,
245 f64: From<T>,
246{
247 let a_lat = Angle::from(a.lat);
248 let b_lat = Angle::from(b.lat);
249 let delta_long = Angle::from((b.lon, a.lon));
250 (
251 great_circle::calculate_gc_azimuth(a_lat, b_lat, delta_long),
252 great_circle::calculate_gc_distance(a_lat, b_lat, delta_long),
253 )
254}
255
256#[must_use]
264pub fn haversine_distance<T>(a: &LatLong<T>, b: &LatLong<T>) -> Radians<T>
265where
266 T: Float + FloatConst,
267 f64: From<T>,
268{
269 let a_lat = Angle::from(a.lat);
270 let b_lat = Angle::from(b.lat);
271 let delta_lat = Angle::from((b.lat, a.lat));
272 let delta_long = Angle::from(b.lon - a.lon);
273 great_circle::calculate_haversine_distance(a_lat, b_lat, delta_long, delta_lat)
274}
275
276impl<T> From<&LatLong<T>> for Vector3<T>
277where
278 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
279 f64: From<T>,
280{
281 fn from(a: &LatLong<T>) -> Self {
289 vector::to_point(Angle::from(a.lat), Angle::from(a.lon))
290 }
291}
292
293impl<T> From<&Vector3<T>> for LatLong<T>
294where
295 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
296 f64: From<T>,
297{
298 fn from(value: &Vector3<T>) -> Self {
300 Self::new(
301 Degrees::from(vector::latitude(value)),
302 Degrees::from(vector::longitude(value)),
303 )
304 }
305}
306
307#[derive(Clone, Copy, Debug, Eq, PartialEq)]
309pub struct Arc<T: Float + FloatConst> {
310 a: Vector3<T>,
312 pole: Vector3<T>,
314 length: Radians<T>,
316 half_width: Radians<T>,
318}
319
320impl<T> Validate for Arc<T>
321where
322 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
323{
324 fn is_valid(&self) -> bool {
329 vector::is_unit(&self.a)
330 && vector::is_unit(&self.pole)
331 && vector::are_orthogonal(&self.a, &self.pole)
332 && !self.length.0.is_sign_negative()
333 && !self.half_width.0.is_sign_negative()
334 }
335}
336
337impl<T> Arc<T>
338where
339 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
340 f64: From<T>,
341{
342 #[must_use]
349 pub const fn new(
350 a: Vector3<T>,
351 pole: Vector3<T>,
352 length: Radians<T>,
353 half_width: Radians<T>,
354 ) -> Self {
355 Self {
356 a,
357 pole,
358 length,
359 half_width,
360 }
361 }
362
363 #[must_use]
369 pub fn from_lat_lon_azi_length(a: &LatLong<T>, azimuth: Angle<T>, length: Radians<T>) -> Self {
370 Self::new(
371 Vector3::from(a),
372 vector::calculate_pole(Angle::from(a.lat()), Angle::from(a.lon()), azimuth),
373 length,
374 Radians(T::zero()),
375 )
376 }
377
378 #[must_use]
383 pub fn between_positions(a: &LatLong<T>, b: &LatLong<T>) -> Self {
384 let min_value = T::epsilon() + T::epsilon();
385
386 let (azimuth, length) = calculate_azimuth_and_distance(a, b);
387 let a_lat = Angle::from(a.lat());
388 if a_lat.cos().0 < min_value {
390 Self::from_lat_lon_azi_length(&LatLong::new(a.lat(), b.lon()), azimuth, length)
392 } else {
393 Self::from_lat_lon_azi_length(a, azimuth, length)
394 }
395 }
396
397 #[must_use]
401 pub const fn set_half_width(&mut self, half_width: Radians<T>) -> &mut Self {
402 self.half_width = half_width;
403 self
404 }
405
406 #[must_use]
408 pub const fn a(&self) -> Vector3<T> {
409 self.a
410 }
411
412 #[must_use]
414 pub const fn pole(&self) -> Vector3<T> {
415 self.pole
416 }
417
418 #[must_use]
420 pub const fn length(&self) -> Radians<T> {
421 self.length
422 }
423
424 #[must_use]
426 pub const fn half_width(&self) -> Radians<T> {
427 self.half_width
428 }
429
430 #[must_use]
432 pub fn azimuth(&self) -> Angle<T> {
433 vector::calculate_azimuth(&self.a, &self.pole)
434 }
435
436 #[must_use]
438 pub fn direction(&self) -> Vector3<T> {
439 vector::direction(&self.a, &self.pole)
440 }
441
442 #[must_use]
444 pub fn position(&self, distance: Radians<T>) -> Vector3<T> {
445 vector::position(&self.a, &self.direction(), Angle::from(distance))
446 }
447
448 #[must_use]
450 pub fn b(&self) -> Vector3<T> {
451 self.position(self.length)
452 }
453
454 #[must_use]
456 pub fn mid_point(&self) -> Vector3<T> {
457 self.position(self.length.half())
458 }
459
460 #[must_use]
467 pub fn perp_position(&self, point: &Vector3<T>, distance: Radians<T>) -> Vector3<T> {
468 vector::position(point, &self.pole, Angle::from(distance))
469 }
470
471 #[must_use]
477 pub fn angle_position(&self, angle: Angle<T>) -> Vector3<T> {
478 vector::rotate_position(&self.a, &self.pole, angle, Angle::from(self.length))
479 }
480
481 #[must_use]
487 pub fn end_arc(&self, at_b: bool) -> Self {
488 let min_value = T::epsilon() + T::epsilon();
489
490 let p = if at_b { self.b() } else { self.a };
491 let pole = vector::direction(&p, &self.pole);
492 if self.half_width.0 < min_value {
493 Self::new(p, pole, Radians::default(), Radians::default())
494 } else {
495 let a = self.perp_position(&p, self.half_width);
496 Self::new(
497 a,
498 pole,
499 self.half_width + self.half_width,
500 Radians::default(),
501 )
502 }
503 }
504
505 #[must_use]
512 pub fn calculate_atd_and_xtd(&self, point: &Vector3<T>) -> (Radians<T>, Radians<T>) {
513 vector::calculate_atd_and_xtd(&self.a, &self.pole(), point)
514 }
515
516 #[must_use]
522 pub fn shortest_distance(&self, point: &Vector3<T>) -> Radians<T> {
523 let min_value = T::epsilon() + T::epsilon();
524 let two = T::one() + T::one();
525
526 let (atd, xtd) = self.calculate_atd_and_xtd(point);
527 if (-min_value <= atd.0) && (atd.0 <= self.length.0 + two * min_value) {
528 xtd.abs()
530 } else {
531 let atd_centre = atd - self.length.half();
533 let p = if atd_centre.0.is_sign_negative() {
534 self.a
535 } else {
536 self.b()
537 };
538 great_circle::e2gc_distance(vector::distance(&p, point))
539 }
540 }
541}
542
543#[derive(Error, Debug, PartialEq)]
545pub enum ArcError {
546 #[error("positions are too close: `{0}`")]
547 PositionsTooClose(f64),
548 #[error("positions are too far apart: `{0}`")]
549 PositionsTooFar(f64),
550}
551
552impl<T> TryFrom<(&LatLong<T>, &LatLong<T>)> for Arc<T>
553where
554 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
555 f64: From<T>,
556{
557 type Error = ArcError;
558
559 #[allow(clippy::missing_panics_doc)]
563 fn try_from(params: (&LatLong<T>, &LatLong<T>)) -> Result<Self, Self::Error> {
564 let min_angle_multiple = T::from(16384).expect("Could not convert constant to Float");
565 let min_sin_angle = min_angle_multiple * T::epsilon();
566 let min_sq_norm = min_sin_angle * min_sin_angle;
567
568 let a = Vector3::<T>::from(params.0);
570 let b = Vector3::<T>::from(params.1);
571 vector::normalise(&a.cross(&b), min_sq_norm).map_or_else(
573 || {
574 let sq_d = vector::sq_distance(&a, &b);
575 if sq_d < T::one() {
576 Err(ArcError::PositionsTooClose(f64::from(sq_d)))
577 } else {
578 Err(ArcError::PositionsTooFar(f64::from(sq_d)))
579 }
580 },
581 |pole| {
582 Ok(Self::new(
583 a,
584 pole,
585 great_circle::e2gc_distance(vector::distance(&a, &b)),
586 Radians::default(),
587 ))
588 },
589 )
590 }
591}
592
593#[allow(clippy::missing_panics_doc)]
602#[must_use]
603pub fn calculate_intersection_distances<T>(
604 arc_0: &Arc<T>,
605 arc_1: &Arc<T>,
606) -> (Radians<T>, Radians<T>)
607where
608 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
609 f64: From<T>,
610{
611 let min_angle_multiple = T::from(16384).expect("Could not convert constant to Float");
612 let min_sin_angle = min_angle_multiple * T::epsilon();
613 let min_sq_norm = min_sin_angle * min_sin_angle;
614
615 let (distance_0, distance_1, _angle) =
616 vector::intersection::calculate_arc_reference_distances_and_angle(
617 &arc_0.mid_point(),
618 &arc_0.pole(),
619 &arc_1.mid_point(),
620 &arc_1.pole(),
621 min_sq_norm,
622 );
623 (
624 distance_0 + arc_0.length().half(),
625 distance_1 + arc_1.length().half(),
626 )
627}
628
629#[allow(clippy::missing_panics_doc)]
662#[must_use]
663pub fn calculate_intersection_point<T>(arc_0: &Arc<T>, arc_1: &Arc<T>) -> Option<Vector3<T>>
664where
665 T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
666 f64: From<T>,
667{
668 let min_value = T::epsilon() + T::epsilon();
669
670 let min_angle_multiple = T::from(16384).expect("Could not convert constant to Float");
671 let min_sin_angle = min_angle_multiple * T::epsilon();
672 let min_sq_norm = min_sin_angle * min_sin_angle;
673
674 let (point, angle) = vector::intersection::calculate_reference_point_and_angle(
675 &arc_0.mid_point(),
676 &arc_0.pole(),
677 &arc_1.mid_point(),
678 &arc_1.pole(),
679 min_sq_norm,
680 );
681
682 let distance_0 = vector::calculate_great_circle_atd(&arc_0.mid_point(), &arc_0.pole(), &point);
684 let distance_1 = vector::calculate_great_circle_atd(&arc_1.mid_point(), &arc_1.pole(), &point);
685
686 let arcs_are_coincident = angle.sin().0 == T::zero();
687 let arcs_intersect_or_overlap = if arcs_are_coincident {
688 distance_0.abs() + distance_1.abs()
690 <= arc_0.length().half() + arc_1.length().half() + Radians(min_value)
691 } else {
692 (distance_0.abs() <= arc_0.length().half() + Radians(min_value))
694 && distance_1.abs() <= (arc_1.length().half() + Radians(min_value))
695 };
696
697 if arcs_intersect_or_overlap {
698 Some(point)
699 } else {
700 None
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707 use angle_sc::{Degrees, is_within_tolerance};
708
709 #[test]
710 fn test_is_valid_latitude() {
711 assert!(!is_valid_latitude(-90.0001));
713 assert!(is_valid_latitude(-90.0));
715 assert!(is_valid_latitude(90.0));
717 assert!(!is_valid_latitude(90.0001));
719 }
720
721 #[test]
722 fn test_is_valid_longitude() {
723 assert!(!is_valid_longitude(-180.0001));
725 assert!(is_valid_longitude(-180.0));
727 assert!(is_valid_longitude(180.0));
729 assert!(!is_valid_longitude(180.0001));
731 }
732
733 #[test]
734 fn test_latlong_traits() {
735 let a = LatLong::try_from((0.0, 90.0)).unwrap();
736
737 assert!(a.is_valid());
738
739 let a_clone = a.clone();
740 assert!(a_clone == a);
741
742 assert_eq!(Degrees(0.0), a.lat());
743 assert_eq!(Degrees(90.0), a.lon());
744
745 assert!(!a.is_south_of(&a));
746 assert!(!a.is_west_of(&a));
747
748 let b = LatLong::try_from((-10.0, -91.0)).unwrap();
749 assert!(b.is_south_of(&a));
750 assert!(b.is_west_of(&a));
751
752 println!("LatLong: {:?}", a);
753
754 let invalid_lat = LatLong::try_from((91.0, 0.0));
755 assert_eq!(Err(LatLongError::Latitude(91.0)), invalid_lat);
756 println!("invalid_lat: {:?}", invalid_lat);
757
758 let invalid_lon = LatLong::try_from((0.0, 181.0));
759 assert_eq!(Err(LatLongError::Longitude(181.0)), invalid_lon);
760 println!("invalid_lon: {:?}", invalid_lon);
761 }
762
763 #[test]
764 fn test_vector3d_traits() {
765 let a = LatLong::try_from((0.0, 90.0)).unwrap();
766 let point = Vector3::from(&a);
767
768 assert_eq!(0.0, point.x);
769 assert_eq!(1.0, point.y);
770 assert_eq!(0.0, point.z);
771
772 assert_eq!(Degrees(0.0), Degrees::from(vector::latitude(&point)));
773 assert_eq!(Degrees(90.0), Degrees::from(vector::longitude(&point)));
774
775 let result = LatLong::from(&point);
776 assert_eq!(a, result);
777 }
778
779 #[test]
780 fn test_great_circle_90n_0n_0e() {
781 let a = LatLong::new(Degrees(90.0), Degrees(0.0));
782 let b = LatLong::new(Degrees(0.0), Degrees(0.0));
783 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
784
785 assert!(is_within_tolerance(
786 core::f64::consts::FRAC_PI_2,
787 dist.0,
788 f64::EPSILON
789 ));
790 assert_eq!(180.0, Degrees::from(azimuth).0);
791
792 let dist = haversine_distance(&a, &b);
793 assert!(is_within_tolerance(
794 core::f64::consts::FRAC_PI_2,
795 dist.0,
796 f64::EPSILON
797 ));
798 }
799
800 #[test]
801 fn test_great_circle_90s_0n_50e() {
802 let a = LatLong::new(Degrees(-90.0), Degrees(0.0));
803 let b = LatLong::new(Degrees(0.0), Degrees(50.0));
804 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
805
806 assert!(is_within_tolerance(
807 core::f64::consts::FRAC_PI_2,
808 dist.0,
809 f64::EPSILON
810 ));
811 assert_eq!(0.0, Degrees::from(azimuth).0);
812
813 let dist = haversine_distance(&a, &b);
814 assert!(is_within_tolerance(
815 core::f64::consts::FRAC_PI_2,
816 dist.0,
817 f64::EPSILON
818 ));
819 }
820
821 #[test]
822 fn test_great_circle_0n_60e_0n_60w() {
823 let a = LatLong::new(Degrees(0.0), Degrees(60.0));
824 let b = LatLong::new(Degrees(0.0), Degrees(-60.0));
825 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
826
827 assert!(is_within_tolerance(
828 2.0 * core::f64::consts::FRAC_PI_3,
829 dist.0,
830 2.0 * f64::EPSILON
831 ));
832 assert_eq!(-90.0, Degrees::from(azimuth).0);
833
834 let dist = haversine_distance(&a, &b);
835 assert!(is_within_tolerance(
836 2.0 * core::f64::consts::FRAC_PI_3,
837 dist.0,
838 2.0 * f64::EPSILON
839 ));
840 }
841
842 #[test]
843 fn test_arc() {
844 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
846
847 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
849
850 let mut arc = Arc::between_positions(&g_eq, &e_eq);
851 let arc = arc.set_half_width(Radians(0.01));
852 assert!(arc.is_valid());
853 assert_eq!(Radians(0.01), arc.half_width());
854
855 assert_eq!(Vector3::from(&g_eq), arc.a());
856 assert_eq!(Vector3::new(0.0, 0.0, 1.0), arc.pole());
857 assert!(is_within_tolerance(
858 core::f64::consts::FRAC_PI_2,
859 arc.length().0,
860 f64::EPSILON
861 ));
862 assert_eq!(Angle::from(Degrees(90.0)), arc.azimuth());
863 let b = Vector3::from(&e_eq);
864 assert!(is_within_tolerance(
865 0.0,
866 vector::distance(&b, &arc.b()),
867 f64::EPSILON
868 ));
869
870 let mid_point = arc.mid_point();
871 assert_eq!(0.0, mid_point.z);
872 assert!(is_within_tolerance(
873 45.0,
874 Degrees::from(vector::longitude(&mid_point)).0,
875 32.0 * f64::EPSILON
876 ));
877
878 let start_arc = arc.end_arc(false);
879 assert_eq!(0.02, start_arc.length().0);
880
881 let start_arc_a = start_arc.a();
882 assert_eq!(start_arc_a, arc.perp_position(&arc.a(), Radians(0.01)));
883
884 let angle_90 = Angle::from(Degrees(90.0));
885 let pole_0 = Vector3::new(0.0, 0.0, 1.0);
886 assert!(vector::distance(&pole_0, &arc.angle_position(angle_90)) <= f64::EPSILON);
887
888 let end_arc = arc.end_arc(true);
889 assert_eq!(0.02, end_arc.length().0);
890
891 let end_arc_a = end_arc.a();
892 assert_eq!(end_arc_a, arc.perp_position(&arc.b(), Radians(0.01)));
893 }
894
895 #[test]
896 fn test_north_and_south_poles() {
897 let north_pole = LatLong::new(Degrees(90.0), Degrees(0.0));
898 let south_pole = LatLong::new(Degrees(-90.0), Degrees(0.0));
899
900 let (azimuth, distance) = calculate_azimuth_and_distance(&south_pole, &north_pole);
901 assert_eq!(0.0, Degrees::from(azimuth).0);
902 assert_eq!(core::f64::consts::PI, distance.0);
903
904 let (azimuth, distance) = calculate_azimuth_and_distance(&north_pole, &south_pole);
905 assert_eq!(180.0, Degrees::from(azimuth).0);
906 assert_eq!(core::f64::consts::PI, distance.0);
907
908 let e_eq = LatLong::new(Degrees(0.0), Degrees(50.0));
910
911 let arc = Arc::between_positions(&north_pole, &e_eq);
912 assert!(is_within_tolerance(
913 e_eq.lat().0,
914 LatLong::from(&arc.b()).lat().abs().0,
915 1e-13
916 ));
917 assert!(is_within_tolerance(
918 e_eq.lon().0,
919 LatLong::from(&arc.b()).lon().0,
920 50.0 * f64::EPSILON
921 ));
922
923 let arc = Arc::between_positions(&south_pole, &e_eq);
924 assert!(is_within_tolerance(
925 e_eq.lat().0,
926 LatLong::from(&arc.b()).lat().abs().0,
927 1e-13
928 ));
929 assert!(is_within_tolerance(
930 e_eq.lon().0,
931 LatLong::from(&arc.b()).lon().0,
932 50.0 * f64::EPSILON
933 ));
934
935 let w_eq = LatLong::new(Degrees(0.0), Degrees(-140.0));
936
937 let arc = Arc::between_positions(&north_pole, &w_eq);
938 assert!(is_within_tolerance(
939 w_eq.lat().0,
940 LatLong::from(&arc.b()).lat().abs().0,
941 1e-13
942 ));
943 assert!(is_within_tolerance(
944 w_eq.lon().0,
945 LatLong::from(&arc.b()).lon().0,
946 256.0 * f64::EPSILON
947 ));
948
949 let arc = Arc::between_positions(&south_pole, &w_eq);
950 assert!(is_within_tolerance(
951 w_eq.lat().0,
952 LatLong::from(&arc.b()).lat().abs().0,
953 1e-13
954 ));
955 assert!(is_within_tolerance(
956 w_eq.lon().0,
957 LatLong::from(&arc.b()).lon().0,
958 256.0 * f64::EPSILON
959 ));
960
961 let invalid_arc = Arc::try_from((&north_pole, &north_pole));
962 assert_eq!(Err(ArcError::PositionsTooClose(0.0)), invalid_arc);
963 println!("invalid_arc: {:?}", invalid_arc);
964
965 let arc = Arc::between_positions(&north_pole, &north_pole);
966 assert_eq!(north_pole, LatLong::from(&arc.b()));
967
968 let invalid_arc = Arc::try_from((&north_pole, &south_pole));
969 assert_eq!(Err(ArcError::PositionsTooFar(4.0)), invalid_arc);
970 println!("invalid_arc: {:?}", invalid_arc);
971
972 let arc = Arc::between_positions(&north_pole, &south_pole);
973 assert_eq!(south_pole, LatLong::from(&arc.b()));
974
975 let arc = Arc::between_positions(&south_pole, &north_pole);
976 assert_eq!(north_pole, LatLong::from(&arc.b()));
977
978 let arc = Arc::between_positions(&south_pole, &south_pole);
979 assert_eq!(south_pole, LatLong::from(&arc.b()));
980 }
981
982 #[test]
983 fn test_arc_atd_and_xtd() {
984 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
986
987 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
989
990 let arc = Arc::try_from((&g_eq, &e_eq)).unwrap();
991 assert!(arc.is_valid());
992
993 let start_arc = arc.end_arc(false);
994 assert_eq!(0.0, start_arc.length().0);
995
996 let start_arc_a = start_arc.a();
997 assert_eq!(arc.a(), start_arc_a);
998
999 let longitude = Degrees(1.0);
1000
1001 for lat in -83..84 {
1004 let lat = f64::from(lat);
1005 let latitude = Degrees(lat);
1006 let latlong = LatLong::new(latitude, longitude);
1007 let point = Vector3::from(&latlong);
1008
1009 let expected = (lat).to_radians();
1010 let (atd, xtd) = arc.calculate_atd_and_xtd(&point);
1011 assert!(is_within_tolerance(1_f64.to_radians(), atd.0, f64::EPSILON));
1012 assert!(is_within_tolerance(expected, xtd.0, 2.0 * f64::EPSILON));
1013
1014 let d = arc.shortest_distance(&point);
1015 assert!(is_within_tolerance(expected.abs(), d.0, 2.0 * f64::EPSILON));
1016 }
1017
1018 let point = Vector3::from(&g_eq);
1019 let d = arc.shortest_distance(&point);
1020 assert_eq!(0.0, d.0);
1021
1022 let point = Vector3::from(&e_eq);
1023 let d = arc.shortest_distance(&point);
1024 assert_eq!(0.0, d.0);
1025
1026 let latlong = LatLong::new(Degrees(0.0), Degrees(-1.0));
1027 let point = Vector3::from(&latlong);
1028 let d = arc.shortest_distance(&point);
1029 assert!(is_within_tolerance(1_f64.to_radians(), d.0, f64::EPSILON));
1030
1031 let point = -point;
1032 let d = arc.shortest_distance(&point);
1033 assert!(is_within_tolerance(89_f64.to_radians(), d.0, f64::EPSILON));
1034
1035 let latlong = LatLong::new(Degrees(0.0), Degrees(-160.0));
1037 let point = Vector3::from(&latlong);
1038 let d = arc.shortest_distance(&point);
1039 assert_eq!(
1041 great_circle::e2gc_distance(vector::distance(&arc.b(), &point)),
1042 d
1043 );
1044 }
1045
1046 #[test]
1047 fn test_arc_intersection_point() {
1048 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
1052 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
1053 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
1054 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
1055
1056 let arc_0 = Arc::try_from((&istanbul, &washington)).unwrap();
1057 let arc_1 = Arc::try_from((&reyjavik, &accra)).unwrap();
1058
1059 let intersection_point = calculate_intersection_point(&arc_0, &arc_1).unwrap();
1060 let lat_long = LatLong::from(&intersection_point);
1061 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
1063 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
1065
1066 let intersection_point = calculate_intersection_point(&arc_1, &arc_0).unwrap();
1068 let lat_long = LatLong::from(&intersection_point);
1069 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
1071 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
1073 }
1074
1075 #[test]
1076 fn test_arc_intersection_same_great_circles() {
1077 let south_pole_1 = LatLong::new(Degrees(-88.0), Degrees(-180.0));
1078 let south_pole_2 = LatLong::new(Degrees(-87.0), Degrees(0.0));
1079
1080 let arc_0 = Arc::try_from((&south_pole_1, &south_pole_2)).unwrap();
1081
1082 let intersection_lengths = calculate_intersection_distances(&arc_0, &arc_0);
1083 assert_eq!(arc_0.length().half(), intersection_lengths.0);
1084 assert_eq!(arc_0.length().half(), intersection_lengths.1);
1085
1086 let intersection_point = calculate_intersection_point(&arc_0, &arc_0).unwrap();
1087 assert!(is_within_tolerance(
1088 arc_0.length().half().0,
1089 great_circle::e2gc_distance(vector::distance(&arc_0.a(), &intersection_point)).0,
1090 f64::EPSILON
1091 ));
1092
1093 let south_pole_3 = LatLong::new(Degrees(-85.0), Degrees(0.0));
1094 let south_pole_4 = LatLong::new(Degrees(-86.0), Degrees(0.0));
1095 let arc_1 = Arc::try_from((&south_pole_3, &south_pole_4)).unwrap();
1096 let intersection_point = calculate_intersection_point(&arc_0, &arc_1);
1097 assert!(intersection_point.is_none());
1098 }
1099}