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
120pub use angle_sc::{Angle, Degrees, Radians, Validate};
121use thiserror::Error;
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 fn is_valid(&self) -> bool {
152 is_valid_latitude(self.lat.0) && is_valid_longitude(self.lon.0)
153 }
154}
155
156impl LatLong {
157 #[must_use]
158 pub const fn new(lat: Degrees, lon: Degrees) -> Self {
159 Self { lat, lon }
160 }
161
162 #[must_use]
163 pub const fn lat(&self) -> Degrees {
164 self.lat
165 }
166
167 #[must_use]
168 pub const fn lon(&self) -> Degrees {
169 self.lon
170 }
171}
172
173#[derive(Error, Debug, PartialEq)]
175pub enum LatLongError {
176 #[error("invalid latitude value: `{0}`")]
177 Latitude(f64),
178 #[error("invalid longitude value: `{0}`")]
179 Longitude(f64),
180}
181
182impl TryFrom<(f64, f64)> for LatLong {
183 type Error = LatLongError;
184
185 fn try_from(lat_long: (f64, f64)) -> Result<Self, Self::Error> {
189 if !is_valid_latitude(lat_long.0) {
190 Err(LatLongError::Latitude(lat_long.0))
191 } else if !is_valid_longitude(lat_long.1) {
192 Err(LatLongError::Longitude(lat_long.1))
193 } else {
194 Ok(Self::new(Degrees(lat_long.0), Degrees(lat_long.1)))
195 }
196 }
197}
198
199#[must_use]
206pub fn calculate_azimuth_and_distance(a: &LatLong, b: &LatLong) -> (Angle, Radians) {
207 let a_lat = Angle::from(a.lat);
208 let b_lat = Angle::from(b.lat);
209 let delta_long = Angle::from((b.lon, a.lon));
210 (
211 great_circle::calculate_gc_azimuth(a_lat, b_lat, delta_long),
212 great_circle::calculate_gc_distance(a_lat, b_lat, delta_long),
213 )
214}
215
216#[must_use]
224pub fn haversine_distance(a: &LatLong, b: &LatLong) -> Radians {
225 let a_lat = Angle::from(a.lat);
226 let b_lat = Angle::from(b.lat);
227 let delta_lat = Angle::from((b.lat, a.lat));
228 let delta_long = Angle::from(b.lon - a.lon);
229 great_circle::calculate_haversine_distance(a_lat, b_lat, delta_long, delta_lat)
230}
231
232#[allow(clippy::module_name_repetitions)]
234pub type Vector3d = na::Vector3<f64>;
235
236impl From<&LatLong> for Vector3d {
237 fn from(a: &LatLong) -> Self {
245 vector::to_point(Angle::from(a.lat), Angle::from(a.lon))
246 }
247}
248
249impl From<&Vector3d> for LatLong {
250 fn from(value: &Vector3d) -> Self {
252 Self::new(
253 Degrees::from(vector::latitude(value)),
254 Degrees::from(vector::longitude(value)),
255 )
256 }
257}
258
259#[derive(Clone, Copy, Debug, PartialEq)]
261pub struct Arc {
262 a: Vector3d,
264 pole: Vector3d,
266 length: Radians,
268 half_width: Radians,
270}
271
272impl Validate for Arc {
273 fn is_valid(&self) -> bool {
278 vector::is_unit(&self.a)
279 && vector::is_unit(&self.pole)
280 && vector::are_orthogonal(&self.a, &self.pole)
281 && !self.length.0.is_sign_negative()
282 && !self.half_width.0.is_sign_negative()
283 }
284}
285
286impl Arc {
287 #[must_use]
294 pub const fn new(a: Vector3d, pole: Vector3d, length: Radians, half_width: Radians) -> Self {
295 Self {
296 a,
297 pole,
298 length,
299 half_width,
300 }
301 }
302
303 #[must_use]
309 pub fn from_lat_lon_azi_length(a: &LatLong, azimuth: Angle, length: Radians) -> Self {
310 Self::new(
311 Vector3d::from(a),
312 vector::calculate_pole(Angle::from(a.lat()), Angle::from(a.lon()), azimuth),
313 length,
314 Radians(0.0),
315 )
316 }
317
318 #[must_use]
323 pub fn between_positions(a: &LatLong, b: &LatLong) -> Self {
324 let (azimuth, length) = calculate_azimuth_and_distance(a, b);
325 let a_lat = Angle::from(a.lat());
326 if a_lat.cos().0 < great_circle::MIN_VALUE {
328 Self::from_lat_lon_azi_length(&LatLong::new(a.lat(), b.lon()), azimuth, length)
330 } else {
331 Self::from_lat_lon_azi_length(a, azimuth, length)
332 }
333 }
334
335 #[must_use]
339 pub const fn set_half_width(&mut self, half_width: Radians) -> &mut Self {
340 self.half_width = half_width;
341 self
342 }
343
344 #[must_use]
346 pub const fn a(&self) -> Vector3d {
347 self.a
348 }
349
350 #[must_use]
352 pub const fn pole(&self) -> Vector3d {
353 self.pole
354 }
355
356 #[must_use]
358 pub const fn length(&self) -> Radians {
359 self.length
360 }
361
362 #[must_use]
364 pub const fn half_width(&self) -> Radians {
365 self.half_width
366 }
367
368 #[must_use]
370 pub fn azimuth(&self) -> Angle {
371 vector::calculate_azimuth(&self.a, &self.pole)
372 }
373
374 #[must_use]
376 pub fn direction(&self) -> Vector3d {
377 vector::direction(&self.a, &self.pole)
378 }
379
380 #[must_use]
382 pub fn position(&self, distance: Radians) -> Vector3d {
383 vector::position(&self.a, &self.direction(), Angle::from(distance))
384 }
385
386 #[must_use]
388 pub fn b(&self) -> Vector3d {
389 self.position(self.length)
390 }
391
392 #[must_use]
394 pub fn mid_point(&self) -> Vector3d {
395 self.position(Radians(0.5 * self.length.0))
396 }
397
398 #[must_use]
405 pub fn perp_position(&self, point: &Vector3d, distance: Radians) -> Vector3d {
406 vector::position(point, &self.pole, Angle::from(distance))
407 }
408
409 #[must_use]
415 pub fn angle_position(&self, angle: Angle) -> Vector3d {
416 vector::rotate_position(&self.a, &self.pole, angle, Angle::from(self.length))
417 }
418
419 #[must_use]
425 pub fn end_arc(&self, at_b: bool) -> Self {
426 let p = if at_b { self.b() } else { self.a };
427 let pole = vector::direction(&p, &self.pole);
428 if self.half_width.0 < great_circle::MIN_VALUE {
429 Self::new(p, pole, Radians(0.0), Radians(0.0))
430 } else {
431 let a = self.perp_position(&p, self.half_width);
432 Self::new(a, pole, self.half_width + self.half_width, Radians(0.0))
433 }
434 }
435
436 #[must_use]
443 pub fn calculate_atd_and_xtd(&self, point: &Vector3d) -> (Radians, Radians) {
444 vector::calculate_atd_and_xtd(&self.a, &self.pole(), point)
445 }
446}
447
448#[derive(Error, Debug, PartialEq)]
450pub enum ArcError {
451 #[error("positions are too close: `{0}`")]
452 PositionsTooClose(f64),
453 #[error("positions are too far apart: `{0}`")]
454 PositionsTooFar(f64),
455}
456
457impl TryFrom<(&LatLong, &LatLong)> for Arc {
458 type Error = ArcError;
459
460 fn try_from(params: (&LatLong, &LatLong)) -> Result<Self, Self::Error> {
464 let a = Vector3d::from(params.0);
466 let b = Vector3d::from(params.1);
467 vector::normalise(&a.cross(&b), vector::MIN_SQ_NORM).map_or_else(
469 || {
470 let sq_d = vector::sq_distance(&a, &b);
471 if sq_d < 1.0 {
472 Err(ArcError::PositionsTooClose(sq_d))
473 } else {
474 Err(ArcError::PositionsTooFar(sq_d))
475 }
476 },
477 |pole| {
478 Ok(Self::new(
479 a,
480 pole,
481 great_circle::e2gc_distance(vector::distance(&a, &b)),
482 Radians(0.0),
483 ))
484 },
485 )
486 }
487}
488
489#[must_use]
498pub fn calculate_intersection_distances(arc1: &Arc, arc2: &Arc) -> (Radians, Radians) {
499 vector::intersection::calculate_intersection_point_distances(
500 &arc1.a,
501 &arc1.pole,
502 arc1.length(),
503 &arc2.a,
504 &arc2.pole,
505 arc2.length(),
506 &(0.5 * (arc1.mid_point() + arc2.mid_point())),
507 )
508}
509
510#[must_use]
543pub fn calculate_intersection_point(arc1: &Arc, arc2: &Arc) -> Option<Vector3d> {
544 let (distance1, distance2) = calculate_intersection_distances(arc1, arc2);
545
546 if vector::intersection::is_within(distance1.0, arc1.length().0)
548 && vector::intersection::is_within(distance2.0, arc2.length().0)
549 {
550 Some(arc1.position(distance1))
551 } else {
552 None
553 }
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559 use angle_sc::{is_within_tolerance, Degrees};
560
561 #[test]
562 fn test_is_valid_latitude() {
563 assert!(!is_valid_latitude(-90.0001));
565 assert!(is_valid_latitude(-90.0));
567 assert!(is_valid_latitude(90.0));
569 assert!(!is_valid_latitude(90.0001));
571 }
572
573 #[test]
574 fn test_is_valid_longitude() {
575 assert!(!is_valid_longitude(-180.0001));
577 assert!(is_valid_longitude(-180.0));
579 assert!(is_valid_longitude(180.0));
581 assert!(!is_valid_longitude(180.0001));
583 }
584
585 #[test]
586 fn test_latlong_traits() {
587 let a = LatLong::try_from((0.0, 90.0)).unwrap();
588
589 assert!(a.is_valid());
590
591 let a_clone = a.clone();
592 assert!(a_clone == a);
593
594 assert_eq!(Degrees(0.0), a.lat());
595 assert_eq!(Degrees(90.0), a.lon());
596
597 println!("LatLong: {:?}", a);
598
599 let invalid_lat = LatLong::try_from((91.0, 0.0));
600 assert_eq!(Err(LatLongError::Latitude(91.0)), invalid_lat);
601 println!("invalid_lat: {:?}", invalid_lat);
602
603 let invalid_lon = LatLong::try_from((0.0, 181.0));
604 assert_eq!(Err(LatLongError::Longitude(181.0)), invalid_lon);
605 println!("invalid_lon: {:?}", invalid_lon);
606 }
607
608 #[test]
609 fn test_vector3d_traits() {
610 let a = LatLong::try_from((0.0, 90.0)).unwrap();
611 let point = Vector3d::from(&a);
612
613 assert_eq!(0.0, point.x);
614 assert_eq!(1.0, point.y);
615 assert_eq!(0.0, point.z);
616
617 assert_eq!(Degrees(0.0), Degrees::from(vector::latitude(&point)));
618 assert_eq!(Degrees(90.0), Degrees::from(vector::longitude(&point)));
619
620 let result = LatLong::from(&point);
621 assert_eq!(a, result);
622 }
623
624 #[test]
625 fn test_great_circle_90n_0n_0e() {
626 let a = LatLong::new(Degrees(90.0), Degrees(0.0));
627 let b = LatLong::new(Degrees(0.0), Degrees(0.0));
628 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
629
630 assert!(is_within_tolerance(
631 core::f64::consts::FRAC_PI_2,
632 dist.0,
633 f64::EPSILON
634 ));
635 assert_eq!(180.0, Degrees::from(azimuth).0);
636
637 let dist = haversine_distance(&a, &b);
638 assert!(is_within_tolerance(
639 core::f64::consts::FRAC_PI_2,
640 dist.0,
641 f64::EPSILON
642 ));
643 }
644
645 #[test]
646 fn test_great_circle_90s_0n_50e() {
647 let a = LatLong::new(Degrees(-90.0), Degrees(0.0));
648 let b = LatLong::new(Degrees(0.0), Degrees(50.0));
649 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
650
651 assert!(is_within_tolerance(
652 core::f64::consts::FRAC_PI_2,
653 dist.0,
654 f64::EPSILON
655 ));
656 assert_eq!(0.0, Degrees::from(azimuth).0);
657
658 let dist = haversine_distance(&a, &b);
659 assert!(is_within_tolerance(
660 core::f64::consts::FRAC_PI_2,
661 dist.0,
662 f64::EPSILON
663 ));
664 }
665
666 #[test]
667 fn test_great_circle_0n_60e_0n_60w() {
668 let a = LatLong::new(Degrees(0.0), Degrees(60.0));
669 let b = LatLong::new(Degrees(0.0), Degrees(-60.0));
670 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
671
672 assert!(is_within_tolerance(
673 2.0 * core::f64::consts::FRAC_PI_3,
674 dist.0,
675 2.0 * f64::EPSILON
676 ));
677 assert_eq!(-90.0, Degrees::from(azimuth).0);
678
679 let dist = haversine_distance(&a, &b);
680 assert!(is_within_tolerance(
681 2.0 * core::f64::consts::FRAC_PI_3,
682 dist.0,
683 2.0 * f64::EPSILON
684 ));
685 }
686
687 #[test]
688 fn test_arc() {
689 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
691
692 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
694
695 let mut arc = Arc::between_positions(&g_eq, &e_eq);
696 let arc = arc.set_half_width(Radians(0.01));
697 assert!(arc.is_valid());
698 assert_eq!(Radians(0.01), arc.half_width());
699
700 assert_eq!(Vector3d::from(&g_eq), arc.a());
701 assert_eq!(Vector3d::new(0.0, 0.0, 1.0), arc.pole());
702 assert!(is_within_tolerance(
703 core::f64::consts::FRAC_PI_2,
704 arc.length().0,
705 f64::EPSILON
706 ));
707 assert_eq!(Angle::from(Degrees(90.0)), arc.azimuth());
708 let b = Vector3d::from(&e_eq);
709 assert!(is_within_tolerance(
710 0.0,
711 vector::distance(&b, &arc.b()),
712 f64::EPSILON
713 ));
714
715 let mid_point = arc.mid_point();
716 assert_eq!(0.0, mid_point.z);
717 assert!(is_within_tolerance(
718 45.0,
719 Degrees::from(vector::longitude(&mid_point)).0,
720 32.0 * f64::EPSILON
721 ));
722
723 let start_arc = arc.end_arc(false);
724 assert_eq!(0.02, start_arc.length().0);
725
726 let start_arc_a = start_arc.a();
727 assert_eq!(start_arc_a, arc.perp_position(&arc.a(), Radians(0.01)));
728
729 let angle_90 = Angle::from(Degrees(90.0));
730 let pole_0 = Vector3d::new(0.0, 0.0, 1.0);
731 assert!(vector::distance(&pole_0, &arc.angle_position(angle_90)) <= f64::EPSILON);
732
733 let end_arc = arc.end_arc(true);
734 assert_eq!(0.02, end_arc.length().0);
735
736 let end_arc_a = end_arc.a();
737 assert_eq!(end_arc_a, arc.perp_position(&arc.b(), Radians(0.01)));
738 }
739
740 #[test]
741 fn test_north_and_south_poles() {
742 let north_pole = LatLong::new(Degrees(90.0), Degrees(0.0));
743 let south_pole = LatLong::new(Degrees(-90.0), Degrees(0.0));
744
745 let (azimuth, distance) = calculate_azimuth_and_distance(&south_pole, &north_pole);
746 assert_eq!(0.0, Degrees::from(azimuth).0);
747 assert_eq!(core::f64::consts::PI, distance.0);
748
749 let (azimuth, distance) = calculate_azimuth_and_distance(&north_pole, &south_pole);
750 assert_eq!(180.0, Degrees::from(azimuth).0);
751 assert_eq!(core::f64::consts::PI, distance.0);
752
753 let e_eq = LatLong::new(Degrees(0.0), Degrees(50.0));
755
756 let arc = Arc::between_positions(&north_pole, &e_eq);
757 assert!(is_within_tolerance(
758 e_eq.lat().0,
759 LatLong::from(&arc.b()).lat().abs().0,
760 1e-13
761 ));
762 assert!(is_within_tolerance(
763 e_eq.lon().0,
764 LatLong::from(&arc.b()).lon().0,
765 50.0 * f64::EPSILON
766 ));
767
768 let arc = Arc::between_positions(&south_pole, &e_eq);
769 assert!(is_within_tolerance(
770 e_eq.lat().0,
771 LatLong::from(&arc.b()).lat().abs().0,
772 1e-13
773 ));
774 assert!(is_within_tolerance(
775 e_eq.lon().0,
776 LatLong::from(&arc.b()).lon().0,
777 50.0 * f64::EPSILON
778 ));
779
780 let w_eq = LatLong::new(Degrees(0.0), Degrees(-140.0));
781
782 let arc = Arc::between_positions(&north_pole, &w_eq);
783 assert!(is_within_tolerance(
784 w_eq.lat().0,
785 LatLong::from(&arc.b()).lat().abs().0,
786 1e-13
787 ));
788 assert!(is_within_tolerance(
789 w_eq.lon().0,
790 LatLong::from(&arc.b()).lon().0,
791 256.0 * f64::EPSILON
792 ));
793
794 let arc = Arc::between_positions(&south_pole, &w_eq);
795 assert!(is_within_tolerance(
796 w_eq.lat().0,
797 LatLong::from(&arc.b()).lat().abs().0,
798 1e-13
799 ));
800 assert!(is_within_tolerance(
801 w_eq.lon().0,
802 LatLong::from(&arc.b()).lon().0,
803 256.0 * f64::EPSILON
804 ));
805
806 let invalid_arc = Arc::try_from((&north_pole, &north_pole));
807 assert_eq!(Err(ArcError::PositionsTooClose(0.0)), invalid_arc);
808 println!("invalid_arc: {:?}", invalid_arc);
809
810 let arc = Arc::between_positions(&north_pole, &north_pole);
811 assert_eq!(north_pole, LatLong::from(&arc.b()));
812
813 let invalid_arc = Arc::try_from((&north_pole, &south_pole));
814 assert_eq!(Err(ArcError::PositionsTooFar(4.0)), invalid_arc);
815 println!("invalid_arc: {:?}", invalid_arc);
816
817 let arc = Arc::between_positions(&north_pole, &south_pole);
818 assert_eq!(south_pole, LatLong::from(&arc.b()));
819
820 let arc = Arc::between_positions(&south_pole, &north_pole);
821 assert_eq!(north_pole, LatLong::from(&arc.b()));
822
823 let arc = Arc::between_positions(&south_pole, &south_pole);
824 assert_eq!(south_pole, LatLong::from(&arc.b()));
825 }
826
827 #[test]
828 fn test_arc_atd_and_xtd() {
829 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
831
832 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
834
835 let arc = Arc::try_from((&g_eq, &e_eq)).unwrap();
836 assert!(arc.is_valid());
837
838 let start_arc = arc.end_arc(false);
839 assert_eq!(0.0, start_arc.length().0);
840
841 let start_arc_a = start_arc.a();
842 assert_eq!(arc.a(), start_arc_a);
843
844 let longitude = Degrees(1.0);
845
846 for lat in -83..84 {
849 let latitude = Degrees(lat as f64);
850 let latlong = LatLong::new(latitude, longitude);
851 let point = Vector3d::from(&latlong);
852
853 let expected = (lat as f64).to_radians();
854 let (atd, xtd) = arc.calculate_atd_and_xtd(&point);
855 assert!(is_within_tolerance(1_f64.to_radians(), atd.0, f64::EPSILON));
856 assert!(is_within_tolerance(expected, xtd.0, 2.0 * f64::EPSILON));
857 }
858 }
859
860 #[test]
861 fn test_arc_intersection_point() {
862 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
866 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
867 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
868 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
869
870 let arc1 = Arc::try_from((&istanbul, &washington)).unwrap();
871 let arc2 = Arc::try_from((&reyjavik, &accra)).unwrap();
872
873 let intersection_point = calculate_intersection_point(&arc1, &arc2).unwrap();
874 let lat_long = LatLong::from(&intersection_point);
875 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
877 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
879
880 let intersection_point = calculate_intersection_point(&arc2, &arc1).unwrap();
882 let lat_long = LatLong::from(&intersection_point);
883 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
885 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
887 }
888
889 #[test]
890 fn test_arc_intersection_same_great_circles() {
891 let south_pole_1 = LatLong::new(Degrees(-88.0), Degrees(-180.0));
892 let south_pole_2 = LatLong::new(Degrees(-87.0), Degrees(0.0));
893
894 let arc1 = Arc::try_from((&south_pole_1, &south_pole_2)).unwrap();
895
896 let intersection_lengths = calculate_intersection_distances(&arc1, &arc1);
897 assert_eq!(Radians(0.0), intersection_lengths.0);
898 assert_eq!(Radians(0.0), intersection_lengths.1);
899
900 let intersection_point = calculate_intersection_point(&arc1, &arc1).unwrap();
901 assert_eq!(0.0, vector::sq_distance(&arc1.a(), &intersection_point));
902
903 let south_pole_3 = LatLong::new(Degrees(-85.0), Degrees(0.0));
904 let south_pole_4 = LatLong::new(Degrees(-86.0), Degrees(0.0));
905 let arc2 = Arc::try_from((&south_pole_3, &south_pole_4)).unwrap();
906 let intersection_point = calculate_intersection_point(&arc1, &arc2);
907 assert!(intersection_point.is_none());
908 }
909}