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::trig;
121pub use angle_sc::{Angle, Degrees, Radians, Validate};
122
123#[must_use]
127pub fn is_valid_latitude(degrees: f64) -> bool {
128 (-90.0..=90.0).contains(°rees)
129}
130
131#[must_use]
135pub fn is_valid_longitude(degrees: f64) -> bool {
136 (-180.0..=180.0).contains(°rees)
137}
138
139#[derive(Clone, Copy, Debug, PartialEq)]
141pub struct LatLong {
142 lat: Degrees,
143 lon: Degrees,
144}
145
146impl Validate for LatLong {
147 #[must_use]
152 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
174impl TryFrom<(f64, f64)> for LatLong {
175 type Error = &'static str;
176
177 fn try_from(lat_long: (f64, f64)) -> Result<Self, Self::Error> {
181 if !is_valid_latitude(lat_long.0) {
182 Err("invalid latitude")
183 } else if !is_valid_longitude(lat_long.1) {
184 Err("invalid longitude")
185 } else {
186 Ok(Self::new(Degrees(lat_long.0), Degrees(lat_long.1)))
187 }
188 }
189}
190
191#[must_use]
198pub fn calculate_azimuth_and_distance(a: &LatLong, b: &LatLong) -> (Angle, Radians) {
199 let a_lat = Angle::from(a.lat);
200 let b_lat = Angle::from(b.lat);
201 let delta_long = Angle::from((b.lon, a.lon));
202 (
203 great_circle::calculate_gc_azimuth(a_lat, b_lat, delta_long),
204 great_circle::calculate_gc_distance(a_lat, b_lat, delta_long),
205 )
206}
207
208#[must_use]
216pub fn haversine_distance(a: &LatLong, b: &LatLong) -> Radians {
217 let a_lat = Angle::from(a.lat);
218 let b_lat = Angle::from(b.lat);
219 let delta_lat = Angle::from((b.lat, a.lat));
220 let delta_long = Angle::from(b.lon - a.lon);
221 great_circle::calculate_haversine_distance(a_lat, b_lat, delta_long, delta_lat)
222}
223
224#[allow(clippy::module_name_repetitions)]
226pub type Vector3d = na::Vector3<f64>;
227
228impl From<&LatLong> for Vector3d {
229 #[must_use]
237 fn from(a: &LatLong) -> Self {
238 vector::to_point(Angle::from(a.lat), Angle::from(a.lon))
239 }
240}
241
242#[must_use]
244pub fn latitude(a: &Vector3d) -> Angle {
245 let sin_a = trig::UnitNegRange(a.z);
246 Angle::new(sin_a, trig::swap_sin_cos(sin_a))
247}
248
249#[must_use]
251pub fn longitude(a: &Vector3d) -> Angle {
252 Angle::from_y_x(a.y, a.x)
253}
254
255impl From<&Vector3d> for LatLong {
256 #[must_use]
258 fn from(value: &Vector3d) -> Self {
259 Self::new(
260 Degrees::from(latitude(value)),
261 Degrees::from(longitude(value)),
262 )
263 }
264}
265
266#[derive(Clone, Copy, Debug, PartialEq)]
268pub struct Arc {
269 a: Vector3d,
271 pole: Vector3d,
273 length: Radians,
275 half_width: Radians,
277}
278
279impl Validate for Arc {
280 fn is_valid(&self) -> bool {
285 vector::is_unit(&self.a)
286 && vector::is_unit(&self.pole)
287 && vector::are_orthogonal(&self.a, &self.pole)
288 && !self.length.0.is_sign_negative()
289 && !self.half_width.0.is_sign_negative()
290 }
291}
292
293impl Arc {
294 #[must_use]
301 pub const fn new(a: Vector3d, pole: Vector3d, length: Radians, half_width: Radians) -> Self {
302 Self {
303 a,
304 pole,
305 length,
306 half_width,
307 }
308 }
309
310 #[must_use]
316 pub fn from_lat_lon_azi_length(a: &LatLong, azimuth: Angle, length: Radians) -> Self {
317 Self::new(
318 Vector3d::from(a),
319 vector::calculate_pole(Angle::from(a.lat()), Angle::from(a.lon()), azimuth),
320 length,
321 Radians(0.0),
322 )
323 }
324
325 #[must_use]
330 pub fn between_positions(a: &LatLong, b: &LatLong) -> Self {
331 let (azimuth, length) = calculate_azimuth_and_distance(a, b);
332 let a_lat = Angle::from(a.lat());
333 if a_lat.cos().0 < great_circle::MIN_VALUE {
335 Self::from_lat_lon_azi_length(&LatLong::new(a.lat(), b.lon()), azimuth, length)
337 } else {
338 Self::from_lat_lon_azi_length(a, azimuth, length)
339 }
340 }
341
342 #[must_use]
346 pub const fn set_half_width(&mut self, half_width: Radians) -> &mut Self {
347 self.half_width = half_width;
348 self
349 }
350
351 #[must_use]
353 pub const fn a(&self) -> Vector3d {
354 self.a
355 }
356
357 #[must_use]
359 pub const fn pole(&self) -> Vector3d {
360 self.pole
361 }
362
363 #[must_use]
365 pub const fn length(&self) -> Radians {
366 self.length
367 }
368
369 #[must_use]
371 pub const fn half_width(&self) -> Radians {
372 self.half_width
373 }
374
375 #[must_use]
377 pub fn azimuth(&self) -> Angle {
378 vector::calculate_azimuth(&self.a, &self.pole)
379 }
380
381 #[must_use]
383 pub fn direction(&self) -> Vector3d {
384 vector::direction(&self.a, &self.pole)
385 }
386
387 #[must_use]
389 pub fn position(&self, distance: Radians) -> Vector3d {
390 vector::position(&self.a, &self.direction(), Angle::from(distance))
391 }
392
393 #[must_use]
395 pub fn b(&self) -> Vector3d {
396 self.position(self.length)
397 }
398
399 #[must_use]
401 pub fn mid_point(&self) -> Vector3d {
402 self.position(Radians(0.5 * self.length.0))
403 }
404
405 #[must_use]
412 pub fn perp_position(&self, point: &Vector3d, distance: Radians) -> Vector3d {
413 vector::position(point, &self.pole, Angle::from(distance))
414 }
415
416 #[must_use]
422 pub fn angle_position(&self, angle: Angle) -> Vector3d {
423 vector::rotate_position(&self.a, &self.pole, angle, Angle::from(self.length))
424 }
425
426 #[must_use]
432 pub fn end_arc(&self, at_b: bool) -> Self {
433 let p = if at_b { self.b() } else { self.a };
434 let pole = vector::direction(&p, &self.pole);
435 if self.half_width.0 < great_circle::MIN_VALUE {
436 Self::new(p, pole, Radians(0.0), Radians(0.0))
437 } else {
438 let a = self.perp_position(&p, self.half_width);
439 Self::new(a, pole, self.half_width + self.half_width, Radians(0.0))
440 }
441 }
442
443 #[must_use]
450 pub fn calculate_atd_and_xtd(&self, point: &Vector3d) -> (Radians, Radians) {
451 vector::calculate_atd_and_xtd(&self.a, &self.pole(), point)
452 }
453}
454
455impl TryFrom<(&LatLong, &LatLong)> for Arc {
456 type Error = &'static str;
457
458 fn try_from(params: (&LatLong, &LatLong)) -> Result<Self, Self::Error> {
462 let a = Vector3d::from(params.0);
464 let b = Vector3d::from(params.1);
465 vector::normalise(&a.cross(&b), vector::MIN_SQ_NORM).map_or_else(
467 || {
468 if vector::sq_distance(&a, &b) < 1.0 {
469 Err("Positions are too close")
470 } else {
471 Err("Positions are antipodal")
472 }
473 },
474 |pole| {
475 Ok(Self::new(
476 a,
477 pole,
478 great_circle::e2gc_distance(vector::distance(&a, &b)),
479 Radians(0.0),
480 ))
481 },
482 )
483 }
484}
485
486#[must_use]
495pub fn calculate_intersection_distances(arc1: &Arc, arc2: &Arc) -> (Radians, Radians) {
496 vector::intersection::calculate_intersection_point_distances(
497 &arc1.a,
498 &arc1.pole,
499 arc1.length(),
500 &arc2.a,
501 &arc2.pole,
502 arc2.length(),
503 &(0.5 * (arc1.mid_point() + arc2.mid_point())),
504 )
505}
506
507#[must_use]
540pub fn calculate_intersection_point(arc1: &Arc, arc2: &Arc) -> Option<Vector3d> {
541 let (distance1, distance2) = calculate_intersection_distances(arc1, arc2);
542
543 if vector::intersection::is_within(distance1.0, arc1.length().0)
545 && vector::intersection::is_within(distance2.0, arc2.length().0)
546 {
547 Some(arc1.position(distance1))
548 } else {
549 None
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556 use angle_sc::{is_within_tolerance, Degrees};
557
558 #[test]
559 fn test_is_valid_latitude() {
560 assert!(!is_valid_latitude(-90.0001));
562 assert!(is_valid_latitude(-90.0));
564 assert!(is_valid_latitude(90.0));
566 assert!(!is_valid_latitude(90.0001));
568 }
569
570 #[test]
571 fn test_is_valid_longitude() {
572 assert!(!is_valid_longitude(-180.0001));
574 assert!(is_valid_longitude(-180.0));
576 assert!(is_valid_longitude(180.0));
578 assert!(!is_valid_longitude(180.0001));
580 }
581
582 #[test]
583 fn test_latlong_traits() {
584 let a = LatLong::try_from((0.0, 90.0)).unwrap();
585
586 assert!(a.is_valid());
587
588 let a_clone = a.clone();
589 assert!(a_clone == a);
590
591 assert_eq!(Degrees(0.0), a.lat());
592 assert_eq!(Degrees(90.0), a.lon());
593
594 print!("LatLong: {:?}", a);
595
596 let invalid_lat = LatLong::try_from((91.0, 0.0));
597 assert_eq!(Err("invalid latitude"), invalid_lat);
598
599 let invalid_lon = LatLong::try_from((0.0, 181.0));
600 assert_eq!(Err("invalid longitude"), invalid_lon);
601 }
602
603 #[test]
604 fn test_vector3d_traits() {
605 let a = LatLong::try_from((0.0, 90.0)).unwrap();
606 let point = Vector3d::from(&a);
607
608 assert_eq!(0.0, point.x);
609 assert_eq!(1.0, point.y);
610 assert_eq!(0.0, point.z);
611
612 assert_eq!(Degrees(0.0), Degrees::from(latitude(&point)));
613 assert_eq!(Degrees(90.0), Degrees::from(longitude(&point)));
614
615 let result = LatLong::from(&point);
616 assert_eq!(a, result);
617 }
618
619 #[test]
620 fn test_great_circle_90n_0n_0e() {
621 let a = LatLong::new(Degrees(90.0), Degrees(0.0));
622 let b = LatLong::new(Degrees(0.0), Degrees(0.0));
623 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
624
625 assert!(is_within_tolerance(
626 core::f64::consts::FRAC_PI_2,
627 dist.0,
628 f64::EPSILON
629 ));
630 assert_eq!(180.0, Degrees::from(azimuth).0);
631
632 let dist = haversine_distance(&a, &b);
633 assert!(is_within_tolerance(
634 core::f64::consts::FRAC_PI_2,
635 dist.0,
636 f64::EPSILON
637 ));
638 }
639
640 #[test]
641 fn test_great_circle_90s_0n_50e() {
642 let a = LatLong::new(Degrees(-90.0), Degrees(0.0));
643 let b = LatLong::new(Degrees(0.0), Degrees(50.0));
644 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
645
646 assert!(is_within_tolerance(
647 core::f64::consts::FRAC_PI_2,
648 dist.0,
649 f64::EPSILON
650 ));
651 assert_eq!(0.0, Degrees::from(azimuth).0);
652
653 let dist = haversine_distance(&a, &b);
654 assert!(is_within_tolerance(
655 core::f64::consts::FRAC_PI_2,
656 dist.0,
657 f64::EPSILON
658 ));
659 }
660
661 #[test]
662 fn test_great_circle_0n_60e_0n_60w() {
663 let a = LatLong::new(Degrees(0.0), Degrees(60.0));
664 let b = LatLong::new(Degrees(0.0), Degrees(-60.0));
665 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
666
667 assert!(is_within_tolerance(
668 2.0 * core::f64::consts::FRAC_PI_3,
669 dist.0,
670 2.0 * f64::EPSILON
671 ));
672 assert_eq!(-90.0, Degrees::from(azimuth).0);
673
674 let dist = haversine_distance(&a, &b);
675 assert!(is_within_tolerance(
676 2.0 * core::f64::consts::FRAC_PI_3,
677 dist.0,
678 2.0 * f64::EPSILON
679 ));
680 }
681
682 #[test]
683 fn test_arc() {
684 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
686
687 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
689
690 let mut arc = Arc::between_positions(&g_eq, &e_eq);
691 let arc = arc.set_half_width(Radians(0.01));
692 assert!(arc.is_valid());
693 assert_eq!(Radians(0.01), arc.half_width());
694
695 assert_eq!(Vector3d::from(&g_eq), arc.a());
696 assert_eq!(Vector3d::new(0.0, 0.0, 1.0), arc.pole());
697 assert!(is_within_tolerance(
698 core::f64::consts::FRAC_PI_2,
699 arc.length().0,
700 f64::EPSILON
701 ));
702 assert_eq!(Angle::from(Degrees(90.0)), arc.azimuth());
703 let b = Vector3d::from(&e_eq);
704 assert!(is_within_tolerance(
705 0.0,
706 vector::distance(&b, &arc.b()),
707 f64::EPSILON
708 ));
709
710 let mid_point = arc.mid_point();
711 assert_eq!(0.0, mid_point.z);
712 assert!(is_within_tolerance(
713 45.0,
714 Degrees::from(longitude(&mid_point)).0,
715 32.0 * f64::EPSILON
716 ));
717
718 let start_arc = arc.end_arc(false);
719 assert_eq!(0.02, start_arc.length().0);
720
721 let start_arc_a = start_arc.a();
722 assert_eq!(start_arc_a, arc.perp_position(&arc.a(), Radians(0.01)));
723
724 let angle_90 = Angle::from(Degrees(90.0));
725 let pole_0 = Vector3d::new(0.0, 0.0, 1.0);
726 assert!(vector::distance(&pole_0, &arc.angle_position(angle_90)) <= f64::EPSILON);
727
728 let end_arc = arc.end_arc(true);
729 assert_eq!(0.02, end_arc.length().0);
730
731 let end_arc_a = end_arc.a();
732 assert_eq!(end_arc_a, arc.perp_position(&arc.b(), Radians(0.01)));
733 }
734
735 #[test]
736 fn test_north_and_south_poles() {
737 let north_pole = LatLong::new(Degrees(90.0), Degrees(0.0));
738 let south_pole = LatLong::new(Degrees(-90.0), Degrees(0.0));
739
740 let (azimuth, distance) = calculate_azimuth_and_distance(&south_pole, &north_pole);
741 assert_eq!(0.0, Degrees::from(azimuth).0);
742 assert_eq!(core::f64::consts::PI, distance.0);
743
744 let (azimuth, distance) = calculate_azimuth_and_distance(&north_pole, &south_pole);
745 assert_eq!(180.0, Degrees::from(azimuth).0);
746 assert_eq!(core::f64::consts::PI, distance.0);
747
748 let e_eq = LatLong::new(Degrees(0.0), Degrees(50.0));
750
751 let arc = Arc::between_positions(&north_pole, &e_eq);
752 assert!(is_within_tolerance(
753 e_eq.lat().0,
754 LatLong::from(&arc.b()).lat().abs().0,
755 1e-13
756 ));
757 assert!(is_within_tolerance(
758 e_eq.lon().0,
759 LatLong::from(&arc.b()).lon().0,
760 50.0 * f64::EPSILON
761 ));
762
763 let arc = Arc::between_positions(&south_pole, &e_eq);
764 assert!(is_within_tolerance(
765 e_eq.lat().0,
766 LatLong::from(&arc.b()).lat().abs().0,
767 1e-13
768 ));
769 assert!(is_within_tolerance(
770 e_eq.lon().0,
771 LatLong::from(&arc.b()).lon().0,
772 50.0 * f64::EPSILON
773 ));
774
775 let w_eq = LatLong::new(Degrees(0.0), Degrees(-140.0));
776
777 let arc = Arc::between_positions(&north_pole, &w_eq);
778 assert!(is_within_tolerance(
779 w_eq.lat().0,
780 LatLong::from(&arc.b()).lat().abs().0,
781 1e-13
782 ));
783 assert!(is_within_tolerance(
784 w_eq.lon().0,
785 LatLong::from(&arc.b()).lon().0,
786 256.0 * f64::EPSILON
787 ));
788
789 let arc = Arc::between_positions(&south_pole, &w_eq);
790 assert!(is_within_tolerance(
791 w_eq.lat().0,
792 LatLong::from(&arc.b()).lat().abs().0,
793 1e-13
794 ));
795 assert!(is_within_tolerance(
796 w_eq.lon().0,
797 LatLong::from(&arc.b()).lon().0,
798 256.0 * f64::EPSILON
799 ));
800
801 let invalid_arc = Arc::try_from((&north_pole, &north_pole));
802 assert_eq!(Err("Positions are too close"), invalid_arc);
803
804 let arc = Arc::between_positions(&north_pole, &north_pole);
805 assert_eq!(north_pole, LatLong::from(&arc.b()));
806
807 let invalid_arc = Arc::try_from((&north_pole, &south_pole));
808 assert_eq!(Err("Positions are antipodal"), invalid_arc);
809
810 let arc = Arc::between_positions(&north_pole, &south_pole);
811 assert_eq!(south_pole, LatLong::from(&arc.b()));
812
813 let arc = Arc::between_positions(&south_pole, &north_pole);
814 assert_eq!(north_pole, LatLong::from(&arc.b()));
815
816 let arc = Arc::between_positions(&south_pole, &south_pole);
817 assert_eq!(south_pole, LatLong::from(&arc.b()));
818 }
819
820 #[test]
821 fn test_arc_atd_and_xtd() {
822 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
824
825 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
827
828 let arc = Arc::try_from((&g_eq, &e_eq)).unwrap();
829 assert!(arc.is_valid());
830
831 let start_arc = arc.end_arc(false);
832 assert_eq!(0.0, start_arc.length().0);
833
834 let start_arc_a = start_arc.a();
835 assert_eq!(arc.a(), start_arc_a);
836
837 let longitude = Degrees(1.0);
838
839 for lat in -83..84 {
842 let latitude = Degrees(lat as f64);
843 let latlong = LatLong::new(latitude, longitude);
844 let point = Vector3d::from(&latlong);
845
846 let expected = (lat as f64).to_radians();
847 let (atd, xtd) = arc.calculate_atd_and_xtd(&point);
848 assert!(is_within_tolerance(1_f64.to_radians(), atd.0, f64::EPSILON));
849 assert!(is_within_tolerance(expected, xtd.0, 2.0 * f64::EPSILON));
850 }
851 }
852
853 #[test]
854 fn test_arc_intersection_point() {
855 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
859 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
860 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
861 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
862
863 let arc1 = Arc::try_from((&istanbul, &washington)).unwrap();
864 let arc2 = Arc::try_from((&reyjavik, &accra)).unwrap();
865
866 let intersection_point = calculate_intersection_point(&arc1, &arc2).unwrap();
867 let lat_long = LatLong::from(&intersection_point);
868 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
870 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
872
873 let intersection_point = calculate_intersection_point(&arc2, &arc1).unwrap();
875 let lat_long = LatLong::from(&intersection_point);
876 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
878 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
880 }
881
882 #[test]
883 fn test_arc_intersection_same_great_circles() {
884 let south_pole_1 = LatLong::new(Degrees(-88.0), Degrees(-180.0));
885 let south_pole_2 = LatLong::new(Degrees(-87.0), Degrees(0.0));
886
887 let arc1 = Arc::try_from((&south_pole_1, &south_pole_2)).unwrap();
888
889 let intersection_lengths = calculate_intersection_distances(&arc1, &arc1);
890 assert_eq!(Radians(0.0), intersection_lengths.0);
891 assert_eq!(Radians(0.0), intersection_lengths.1);
892
893 let intersection_point = calculate_intersection_point(&arc1, &arc1).unwrap();
894 assert_eq!(0.0, vector::sq_distance(&arc1.a(), &intersection_point));
895
896 let south_pole_3 = LatLong::new(Degrees(-85.0), Degrees(0.0));
897 let south_pole_4 = LatLong::new(Degrees(-86.0), Degrees(0.0));
898 let arc2 = Arc::try_from((&south_pole_3, &south_pole_4)).unwrap();
899 let intersection_point = calculate_intersection_point(&arc1, &arc2);
900 assert!(intersection_point.is_none());
901 }
902}