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