1#![cfg_attr(not(test), no_std)]
96
97extern crate angle_sc;
98extern crate icao_units;
99extern crate unit_sphere;
100
101pub mod ellipsoid;
102pub mod geodesic;
103pub mod intersection;
104
105pub use angle_sc::{Angle, Degrees, Radians, Validate};
106pub use icao_units::non_si::NauticalMiles;
107pub use icao_units::si::Metres;
108pub use unit_sphere::LatLong;
109
110use angle_sc::trig;
111use once_cell::sync::Lazy;
112use unit_sphere::{Vector3d, great_circle};
113
114#[derive(Clone, Debug, PartialEq)]
116pub struct Ellipsoid {
117 a: Metres,
119 f: f64,
121
122 b: Metres,
124 one_minus_f: f64,
126 recip_one_minus_f: f64,
128 e_2: f64,
130 ep_2: f64,
132 n: f64,
134
135 a3: [f64; 6],
137 c3x: [f64; 15],
139}
140
141impl Ellipsoid {
142 #[must_use]
146 pub fn new(a: Metres, f: f64) -> Self {
147 let one_minus_f = 1.0 - f;
148 let n = ellipsoid::calculate_3rd_flattening(f);
149 Self {
150 a,
151 f,
152 b: ellipsoid::calculate_minor_axis(a, f),
153 one_minus_f,
154 recip_one_minus_f: 1.0 / one_minus_f,
155 e_2: ellipsoid::calculate_sq_eccentricity(f),
156 ep_2: ellipsoid::calculate_sq_2nd_eccentricity(f),
157 n,
158 a3: ellipsoid::coefficients::evaluate_coeffs_a3(n),
159 c3x: ellipsoid::coefficients::evaluate_coeffs_c3x(n),
160 }
161 }
162
163 #[must_use]
165 pub fn wgs84() -> Self {
166 Self::new(ellipsoid::wgs84::A, ellipsoid::wgs84::F)
167 }
168
169 #[must_use]
171 pub const fn a(&self) -> Metres {
172 self.a
173 }
174
175 #[must_use]
177 pub const fn f(&self) -> f64 {
178 self.f
179 }
180
181 #[must_use]
183 pub const fn b(&self) -> Metres {
184 self.b
185 }
186
187 #[must_use]
189 pub const fn one_minus_f(&self) -> f64 {
190 self.one_minus_f
191 }
192
193 #[must_use]
195 pub const fn recip_one_minus_f(&self) -> f64 {
196 self.recip_one_minus_f
197 }
198
199 #[must_use]
201 pub const fn e_2(&self) -> f64 {
202 self.e_2
203 }
204
205 #[must_use]
207 pub const fn ep_2(&self) -> f64 {
208 self.ep_2
209 }
210
211 #[must_use]
213 pub const fn n(&self) -> f64 {
214 self.n
215 }
216
217 #[must_use]
221 pub fn calculate_epsilon(&self, clairaut: trig::UnitNegRange) -> f64 {
222 ellipsoid::calculate_epsilon(clairaut, self.ep_2)
223 }
224
225 #[must_use]
228 pub fn calculate_a3f(&self, eps: f64) -> f64 {
229 ellipsoid::coefficients::evaluate_polynomial(&self.a3, eps)
230 }
231
232 #[must_use]
236 pub fn calculate_a3c(&self, clairaut: trig::UnitNegRange, eps: f64) -> f64 {
237 self.f * clairaut.0 * self.calculate_a3f(eps)
238 }
239
240 #[must_use]
243 pub fn calculate_c3y(&self, eps: f64) -> [f64; 6] {
244 ellipsoid::coefficients::evaluate_coeffs_c3y(&self.c3x, eps)
245 }
246
247 #[must_use]
251 pub fn calculate_parametric_latitude(&self, lat: Angle) -> Angle {
252 ellipsoid::calculate_parametric_latitude(lat, self.one_minus_f)
253 }
254
255 #[must_use]
259 pub fn calculate_geodetic_latitude(&self, beta: Angle) -> Angle {
260 ellipsoid::calculate_geodetic_latitude(beta, self.one_minus_f)
261 }
262
263 #[must_use]
271 pub fn to_arc_point(&self, lat: Angle, lon: Angle) -> Vector3d {
272 let beta = self.calculate_parametric_latitude(lat);
273 unit_sphere::vector::to_point(beta, lon)
274 }
275}
276
277pub static WGS84_ELLIPSOID: Lazy<Ellipsoid> = Lazy::new(Ellipsoid::wgs84);
279
280#[must_use]
309pub fn calculate_azimuths_and_geodesic_length(
310 a: &LatLong,
311 b: &LatLong,
312 tolerance: Radians,
313 ellipsoid: &Ellipsoid,
314) -> (Angle, Metres, Angle) {
315 let (alpha1, arc_length, alpha2, _) =
316 geodesic::calculate_azimuths_arc_length(a, b, tolerance, ellipsoid);
317 let beta1 =
318 ellipsoid::calculate_parametric_latitude(Angle::from(a.lat()), ellipsoid.one_minus_f());
319 (
320 alpha1,
321 geodesic::convert_radians_to_metres(beta1, alpha1, arc_length, ellipsoid),
322 alpha2,
323 )
324}
325
326#[derive(Clone, Debug, PartialEq)]
331pub struct GeodesicSegment<'a> {
332 beta: Angle,
334 lon: Angle,
336 azi: Angle,
338 azi0: Angle,
340 sigma1: Angle,
342 arc_length: Radians,
344 half_width: Metres,
346 eps: f64,
348 a3c: f64,
350 b31: Radians,
352 ellipsoid: &'a Ellipsoid,
354}
355
356impl Validate for GeodesicSegment<'_> {
357 fn is_valid(&self) -> bool {
360 self.beta.cos().0 >= 0.0
361 && (0.0..core::f64::consts::PI).contains(&self.arc_length.0)
362 && self.half_width.0 >= 0.0
363 }
364}
365
366impl<'a> GeodesicSegment<'a> {
367 #[must_use]
375 pub fn new(
376 beta: Angle,
377 lon: Angle,
378 azi: Angle,
379 arc_length: Radians,
380 half_width: Metres,
381 ellipsoid: &'a Ellipsoid,
382 ) -> Self {
383 let clairaut = trig::UnitNegRange(azi.sin().0 * beta.cos().0);
385 let azi0 = Angle::new(clairaut, trig::swap_sin_cos(clairaut));
386
387 let sigma1 = Angle::from_y_x(beta.sin().0, beta.cos().0 * azi.cos().0);
389
390 let eps = ellipsoid.calculate_epsilon(azi0.sin());
392 let c3 = ellipsoid.calculate_c3y(eps);
393 Self {
394 beta,
395 lon,
396 azi,
397 azi0,
398 sigma1,
399 arc_length,
400 half_width,
401 eps,
402 a3c: ellipsoid.calculate_a3c(azi0.sin(), eps),
403 b31: ellipsoid::coefficients::sin_cos_series(&c3, sigma1),
404 ellipsoid,
405 }
406 }
407
408 #[must_use]
416 pub fn from_lat_lon_azi_arc_length_half_width(
417 a: &LatLong,
418 azimuth: Angle,
419 arc_length: Radians,
420 half_width: Metres,
421 ellipsoid: &'a Ellipsoid,
422 ) -> Self {
423 let a_lat = Angle::from(a.lat());
424 let a_lon = Angle::from(a.lon());
425 GeodesicSegment::new(
426 ellipsoid.calculate_parametric_latitude(a_lat),
427 a_lon,
428 azimuth,
429 arc_length,
430 half_width,
431 ellipsoid,
432 )
433 }
434
435 #[must_use]
442 pub fn from_lat_lon_azi_arc_length(
443 a: &LatLong,
444 azimuth: Angle,
445 arc_length: Radians,
446 ellipsoid: &'a Ellipsoid,
447 ) -> Self {
448 GeodesicSegment::from_lat_lon_azi_arc_length_half_width(
449 a,
450 azimuth,
451 arc_length,
452 Metres(0.0),
453 ellipsoid,
454 )
455 }
456
457 #[must_use]
464 pub fn from_lat_lon_azi_length(
465 a: &LatLong,
466 azimuth: Angle,
467 length: Metres,
468 ellipsoid: &'a Ellipsoid,
469 ) -> Self {
470 let mut arc =
471 GeodesicSegment::from_lat_lon_azi_arc_length(a, azimuth, Radians(0.0), ellipsoid);
472 arc.set_arc_length(arc.metres_to_radians(length));
473 arc
474 }
475
476 #[must_use]
483 pub fn between_positions(
484 a: &LatLong,
485 b: &LatLong,
486 half_width: Metres,
487 tolerance: Radians,
488 ellipsoid: &'a Ellipsoid,
489 ) -> Self {
490 let (azimuth, arc_length, _, _) =
491 geodesic::calculate_azimuths_arc_length(a, b, tolerance, ellipsoid);
492 let a_lat = Angle::from(a.lat());
493 if a_lat.cos().0 < great_circle::MIN_VALUE {
495 Self::from_lat_lon_azi_arc_length_half_width(
497 &LatLong::new(a.lat(), b.lon()),
498 azimuth,
499 arc_length,
500 half_width,
501 ellipsoid,
502 )
503 } else {
504 Self::from_lat_lon_azi_arc_length_half_width(
505 a, azimuth, arc_length, half_width, ellipsoid,
506 )
507 }
508 }
509
510 #[must_use]
512 pub const fn beta(&self) -> Angle {
513 self.beta
514 }
515
516 #[must_use]
518 pub const fn lon(&self) -> Angle {
519 self.lon
520 }
521
522 #[must_use]
524 pub const fn azi(&self) -> Angle {
525 self.azi
526 }
527
528 pub const fn set_arc_length(&mut self, arc_length: Radians) -> &mut Self {
531 self.arc_length = arc_length;
532 self
533 }
534
535 #[must_use]
537 pub const fn arc_length(&self) -> Radians {
538 self.arc_length
539 }
540
541 pub const fn set_half_width(&mut self, half_width: Metres) -> &mut Self {
545 self.half_width = half_width;
546 self
547 }
548
549 #[must_use]
551 pub const fn half_width(&self) -> Metres {
552 self.half_width
553 }
554
555 #[must_use]
557 pub const fn ellipsoid(&self) -> &Ellipsoid {
558 self.ellipsoid
559 }
560
561 #[must_use]
563 pub fn a(&self) -> Vector3d {
564 unit_sphere::vector::to_point(self.beta, self.lon)
565 }
566
567 #[must_use]
573 pub fn metres_to_radians(&self, distance: Metres) -> Radians {
574 if distance.0.abs() < great_circle::MIN_VALUE {
575 Radians(0.0)
576 } else {
577 let a1 = ellipsoid::coefficients::evaluate_a1(self.eps) + 1.0;
578 let tau12 = Radians(distance.0 / (self.ellipsoid.b().0 * a1));
579 let c1 = ellipsoid::coefficients::evaluate_coeffs_c1(self.eps);
580 let b11 = ellipsoid::coefficients::sin_cos_series(&c1, self.sigma1);
581 let tau_sum = Angle::from(b11 + tau12);
582 let c1p = ellipsoid::coefficients::evaluate_coeffs_c1p(self.eps);
583 let b12 = ellipsoid::coefficients::sin_cos_series(&c1p, self.sigma1 + tau_sum);
584
585 tau12 + b12 + b11
586 }
587 }
588
589 #[must_use]
591 pub fn length(&self) -> Metres {
592 geodesic::convert_radians_to_metres(self.beta, self.azi, self.arc_length, self.ellipsoid)
593 }
594
595 #[must_use]
600 pub fn arc_beta(&self, sigma: Angle) -> Angle {
601 great_circle::calculate_latitude(self.beta, self.azi, sigma)
602 }
603
604 #[must_use]
609 pub fn arc_latitude(&self, arc_distance: Radians) -> Angle {
610 let sigma = Angle::from(arc_distance);
611 self.ellipsoid
612 .calculate_geodetic_latitude(self.arc_beta(sigma))
613 }
614
615 #[must_use]
620 pub fn latitude(&self, distance: Metres) -> Angle {
621 let arc_distance = self.metres_to_radians(distance);
622 self.arc_latitude(arc_distance)
623 }
624
625 #[must_use]
630 pub fn arc_azimuth(&self, sigma: Angle) -> Angle {
631 const MAX_LAT: f64 = 1.0 - great_circle::MIN_VALUE;
632
633 let sigma_sum = self.sigma1 + sigma;
634 let sin_beta = self.azi0.cos().0 * sigma_sum.sin().0;
635
636 if MAX_LAT < sin_beta {
638 Angle::new(trig::UnitNegRange(0.0), trig::UnitNegRange(-1.0))
639 } else {
640 Angle::from_y_x(self.azi0.sin().0, self.azi0.cos().0 * sigma_sum.cos().0)
641 }
642 }
643
644 #[must_use]
649 pub fn azimuth(&self, distance: Metres) -> Angle {
650 let sigma = Angle::from(self.metres_to_radians(distance));
651 self.arc_azimuth(sigma)
652 }
653
654 #[must_use]
661 pub fn delta_longitude(&self, arc_distance: Radians, sigma: Angle) -> Angle {
662 if arc_distance.abs().0 < great_circle::MIN_VALUE {
663 Angle::default()
664 } else {
665 let sigma_sum = self.sigma1 + sigma;
667
668 let omega12 = Angle::from_y_x(self.azi0.sin().0 * sigma_sum.sin().0, sigma_sum.cos().0)
670 - Angle::from_y_x(
671 self.azi0.sin().0 * self.beta.sin().0,
672 self.beta.cos().0 * self.azi.cos().0,
673 );
674
675 let c3 = self.ellipsoid.calculate_c3y(self.eps);
676 let b32 = ellipsoid::coefficients::sin_cos_series(&c3, sigma_sum);
677
678 omega12 - Angle::from(Radians(self.a3c * (arc_distance.0 + (b32.0 - self.b31.0))))
679 }
680 }
681
682 #[must_use]
688 pub fn arc_longitude(&self, arc_distance: Radians) -> Angle {
689 let sigma = Angle::from(arc_distance);
690 self.lon + self.delta_longitude(arc_distance, sigma)
691 }
692
693 #[must_use]
698 pub fn longitude(&self, distance: Metres) -> Angle {
699 self.arc_longitude(self.metres_to_radians(distance))
700 }
701
702 #[must_use]
708 pub fn arc_beta_long(&self, arc_distance: Radians) -> (Angle, Angle) {
709 let sigma = Angle::from(arc_distance);
710 (
711 self.arc_beta(sigma),
712 self.lon + self.delta_longitude(arc_distance, sigma),
713 )
714 }
715
716 #[must_use]
722 pub fn arc_lat_long(&self, arc_distance: Radians) -> LatLong {
723 let (beta, lon) = self.arc_beta_long(arc_distance);
724 LatLong::new(
725 angle_sc::Degrees::from(self.ellipsoid.calculate_geodetic_latitude(beta)),
726 angle_sc::Degrees::from(lon),
727 )
728 }
729
730 #[must_use]
735 pub fn lat_long(&self, distance: Metres) -> LatLong {
736 let arc_distance = self.metres_to_radians(distance);
737 self.arc_lat_long(arc_distance)
738 }
739
740 #[must_use]
746 pub fn arc_angles(&self, arc_distance: Radians) -> (Angle, Angle, Angle) {
747 let sigma = Angle::from(arc_distance);
748 let beta: Angle = self.arc_beta(sigma);
749 let lon = self.lon + self.delta_longitude(arc_distance, sigma);
750 let azimuth = self.arc_azimuth(sigma);
751
752 (beta, lon, azimuth)
753 }
754
755 #[must_use]
760 pub fn arc_point(&self, arc_distance: Radians) -> Vector3d {
761 if arc_distance.abs().0 < great_circle::MIN_VALUE {
762 unit_sphere::vector::to_point(self.beta, self.lon)
763 } else {
764 let (beta, lon) = self.arc_beta_long(arc_distance);
765 unit_sphere::vector::to_point(beta, lon)
766 }
767 }
768
769 #[must_use]
773 pub fn mid_point(&self) -> Vector3d {
774 self.arc_point(self.metres_to_radians(self.length().half()))
775 }
776
777 #[must_use]
782 pub fn arc_pole(&self, arc_distance: Radians) -> Vector3d {
783 if self.azi0.sin().abs().0 < great_circle::MIN_VALUE {
785 unit_sphere::vector::calculate_pole(self.beta, self.lon, self.azi)
786 } else {
787 let (beta, lon, azimuth) = self.arc_angles(arc_distance);
788 unit_sphere::vector::calculate_pole(beta, lon, azimuth)
789 }
790 }
791
792 #[must_use]
797 pub fn arc_point_and_pole(&self, arc_distance: Radians) -> (Vector3d, Vector3d) {
798 let (beta, lon, azimuth) = self.arc_angles(arc_distance);
799
800 let pole = if self.azi0.sin().abs().0 < great_circle::MIN_VALUE {
802 unit_sphere::vector::calculate_pole(self.beta, self.lon, self.azi)
803 } else {
804 unit_sphere::vector::calculate_pole(beta, lon, azimuth)
805 };
806
807 (unit_sphere::vector::to_point(beta, lon), pole)
808 }
809
810 #[must_use]
814 pub fn reverse(&self) -> GeodesicSegment<'_> {
815 let sigma = Angle::from(self.arc_length);
816 let mut segment = GeodesicSegment::new(
817 self.arc_beta(sigma),
818 self.lon + self.delta_longitude(self.arc_length, sigma),
819 self.arc_azimuth(sigma).opposite(),
820 self.arc_length,
821 self.half_width,
822 self.ellipsoid,
823 );
824 segment.set_half_width(self.half_width);
825 segment.clone()
826 }
827
828 #[allow(clippy::similar_names)]
835 #[must_use]
836 pub fn calculate_sphere_atd_and_xtd(
837 &self,
838 beta: Angle,
839 lon: Angle,
840 precision: Radians,
841 ) -> (Radians, Radians, u32) {
842 const MAX_ITERATIONS: u32 = 10;
843
844 let point = unit_sphere::vector::to_point(beta, lon);
846
847 let (a, pole) = self.arc_point_and_pole(Radians(0.0));
849 let gc_d =
850 unit_sphere::great_circle::e2gc_distance(unit_sphere::vector::distance(&a, &point));
851
852 if gc_d < precision {
854 (Radians(0.0), Radians(0.0), 0)
855 } else {
856 let (mut atd, mut xtd) = unit_sphere::vector::calculate_atd_and_xtd(&a, &pole, &point);
858 let mut iterations = 1;
859 while iterations < MAX_ITERATIONS {
860 let (beta_x, lon_x, azi_x) = self.arc_angles(atd);
862
863 let (azi_p, length, _, _) = geodesic::aux_sphere_azimuths_length(
865 beta_x,
866 beta,
867 lon - lon_x,
868 Radians(great_circle::MIN_VALUE),
869 self.ellipsoid,
870 );
871 let delta_azi = azi_x - azi_p;
872 let delta_atd = trig::spherical_cosine_rule(delta_azi.cos(), length);
873 atd += delta_atd;
874 xtd = length;
875
876 if delta_atd.abs().0 < precision.0 {
877 break;
878 }
879
880 iterations += 1;
881 }
882 xtd = if xtd < precision {
884 Radians(0.0)
885 } else {
886 let pole = self.arc_pole(atd);
887 let sign = pole.dot(&point);
888 Radians(xtd.0.copysign(sign))
889 };
890 (atd, xtd, iterations)
891 }
892 }
893
894 #[allow(clippy::similar_names)]
902 #[must_use]
903 pub fn calculate_sphere_shortest_distance(
904 &self,
905 beta: Angle,
906 lon: Angle,
907 precision: Radians,
908 ) -> Metres {
909 let (atd, xtd, _) = self.calculate_sphere_atd_and_xtd(beta, lon, precision);
910
911 if (-precision <= atd) && (atd <= self.arc_length + precision) {
913 if xtd.abs() < precision {
914 Metres(0.0)
915 } else {
916 let (beta_x, _lon, azi) = self.arc_angles(atd);
918 let alpha = azi.quarter_turn_ccw();
919 let distance =
920 geodesic::convert_radians_to_metres(beta_x, alpha, xtd, self.ellipsoid);
921 Metres(distance.0.abs())
923 }
924 } else {
925 let atd_centre = atd - self.arc_length.half();
927 if atd_centre.0.is_sign_negative() {
928 let delta_long = lon - self.lon;
930 let (alpha, distance, _, _) = geodesic::aux_sphere_azimuths_length(
931 self.beta,
932 beta,
933 delta_long,
934 precision,
935 self.ellipsoid,
936 );
937 geodesic::convert_radians_to_metres(self.beta, alpha, distance, self.ellipsoid)
938 } else {
939 let (arc_beta, arc_lon, _azi) = self.arc_angles(self.arc_length());
941 let delta_long = lon - arc_lon;
942 let (alpha, distance, _, _) = geodesic::aux_sphere_azimuths_length(
943 arc_beta,
944 beta,
945 delta_long,
946 precision,
947 self.ellipsoid,
948 );
949 geodesic::convert_radians_to_metres(arc_beta, alpha, distance, self.ellipsoid)
950 }
951 }
952 }
953
954 #[allow(clippy::similar_names)]
1000 #[must_use]
1001 pub fn calculate_atd_and_xtd(
1002 &self,
1003 position: &LatLong,
1004 precision: Metres,
1005 ) -> (Metres, Metres, u32) {
1006 let precision = Radians(precision.0 / self.ellipsoid.a().0);
1008
1009 let beta = self
1011 .ellipsoid
1012 .calculate_parametric_latitude(Angle::from(position.lat()));
1013 let lon = Angle::from(position.lon());
1014
1015 let (atd, xtd, iterations) = self.calculate_sphere_atd_and_xtd(beta, lon, precision);
1016
1017 let atd_angle = Angle::from(atd);
1019 let beta = self.arc_beta(atd_angle);
1020 let alpha = self.arc_azimuth(atd_angle).quarter_turn_ccw();
1021 (
1022 geodesic::convert_radians_to_metres(self.beta, self.azi, atd, self.ellipsoid),
1023 geodesic::convert_radians_to_metres(beta, alpha, xtd, self.ellipsoid),
1024 iterations,
1025 )
1026 }
1027
1028 #[allow(clippy::similar_names)]
1035 #[must_use]
1036 pub fn shortest_distance(&self, position: &LatLong, precision: Metres) -> Metres {
1037 let beta = self
1039 .ellipsoid
1040 .calculate_parametric_latitude(Angle::from(position.lat()));
1041 let lon = Angle::from(position.lon());
1042 let precision = Radians(precision.0 / self.ellipsoid.a().0);
1044 self.calculate_sphere_shortest_distance(beta, lon, precision)
1045 }
1046}
1047
1048impl From<(&LatLong, Angle, Radians)> for GeodesicSegment<'_> {
1049 fn from(params: (&LatLong, Angle, Radians)) -> Self {
1056 GeodesicSegment::from_lat_lon_azi_arc_length(params.0, params.1, params.2, &WGS84_ELLIPSOID)
1057 }
1058}
1059
1060impl From<(&LatLong, Angle, Metres)> for GeodesicSegment<'_> {
1061 fn from(params: (&LatLong, Angle, Metres)) -> Self {
1068 GeodesicSegment::from_lat_lon_azi_length(params.0, params.1, params.2, &WGS84_ELLIPSOID)
1069 }
1070}
1071
1072impl From<(&LatLong, &LatLong)> for GeodesicSegment<'_> {
1073 fn from(params: (&LatLong, &LatLong)) -> Self {
1078 Self::between_positions(
1079 params.0,
1080 params.1,
1081 Metres(0.0),
1082 Radians(great_circle::MIN_VALUE),
1083 &WGS84_ELLIPSOID,
1084 )
1085 }
1086}
1087
1088#[must_use]
1101pub fn calculate_intersection_distances(
1102 g_0: &GeodesicSegment,
1103 g_1: &GeodesicSegment,
1104 precision: Metres,
1105) -> (Radians, Radians) {
1106 let precision = Radians(precision.0 / g_0.ellipsoid().a().0);
1107 let (distance1, distance2, _, _) =
1108 intersection::calculate_arc_reference_distances_and_angle(g_0, g_1, precision);
1109 (
1110 distance1 + g_0.arc_length().half(),
1111 distance2 + g_1.arc_length().half(),
1112 )
1113}
1114
1115#[must_use]
1150pub fn calculate_intersection_point(
1151 g_0: &GeodesicSegment,
1152 g_1: &GeodesicSegment,
1153 precision: Metres,
1154) -> Option<LatLong> {
1155 let precision = Radians(precision.0 / g_0.ellipsoid().a().0);
1156 let (distance1, distance2, angle, _) =
1157 intersection::calculate_arc_reference_distances_and_angle(g_0, g_1, precision);
1158
1159 let segments_are_coincident = angle.sin().0 == 0.0;
1160 let segments_intersect_or_overlap = if segments_are_coincident {
1161 distance1.abs() + distance2.abs()
1163 <= g_0.arc_length().half() + g_1.arc_length().half() + precision
1164 } else {
1165 (distance1.abs() <= g_0.arc_length().half() + precision)
1167 && distance2.abs() <= (g_1.arc_length().half() + precision)
1168 };
1169
1170 if segments_intersect_or_overlap {
1171 let distance = (distance1 + g_0.arc_length().half()).clamp(g_0.arc_length());
1172 Some(g_0.arc_lat_long(distance))
1173 } else {
1174 None
1175 }
1176}
1177
1178#[cfg(test)]
1179mod tests {
1180 use super::*;
1181 use angle_sc::is_within_tolerance;
1182 use core::mem::size_of;
1183 use unit_sphere::{LatLong, great_circle};
1184
1185 #[test]
1186 fn test_ellipsoid_wgs84() {
1187 let geoid = Ellipsoid::wgs84();
1188 assert_eq!(ellipsoid::wgs84::A, geoid.a());
1189 assert_eq!(ellipsoid::wgs84::F, geoid.f());
1190 assert_eq!(
1191 ellipsoid::calculate_minor_axis(ellipsoid::wgs84::A, ellipsoid::wgs84::F),
1192 geoid.b()
1193 );
1194 assert_eq!(1.0 - ellipsoid::wgs84::F, geoid.one_minus_f());
1195 assert_eq!(1.0 / (1.0 - ellipsoid::wgs84::F), geoid.recip_one_minus_f());
1196 assert_eq!(
1197 ellipsoid::calculate_sq_eccentricity(ellipsoid::wgs84::F),
1198 geoid.e_2()
1199 );
1200 assert_eq!(
1201 ellipsoid::calculate_sq_2nd_eccentricity(ellipsoid::wgs84::F),
1202 geoid.ep_2()
1203 );
1204 assert_eq!(
1205 ellipsoid::calculate_3rd_flattening(ellipsoid::wgs84::F),
1206 geoid.n()
1207 );
1208
1209 let point = geoid.to_arc_point(Angle::from(Degrees(45.0)), Angle::from(Degrees(45.0)));
1210 assert!(is_within_tolerance(
1211 44.903787849420226,
1212 Degrees::from(unit_sphere::vector::latitude(&point)).0,
1213 f64::EPSILON
1214 ));
1215 assert_eq!(
1216 45.0,
1217 Degrees::from(unit_sphere::vector::longitude(&point)).0
1218 );
1219 }
1220
1221 #[test]
1222 fn test_ellipsoid_traits() {
1223 let geoid = Ellipsoid::wgs84();
1224
1225 let geoid_clone = geoid.clone();
1226 assert!(geoid_clone == geoid);
1227
1228 println!("Ellipsoid: {:?}", geoid);
1229 }
1230
1231 #[test]
1232 fn test_calculate_azimuth_arc_length_normal_05() {
1233 let latlon1 = LatLong::new(Degrees(5.421025561218), Degrees(0.0));
1240 let latlon2 = LatLong::new(
1241 Degrees(3.027329237478900117),
1242 Degrees(109.666857465735641205),
1243 );
1244
1245 let result = calculate_azimuths_and_geodesic_length(
1246 &latlon1,
1247 &latlon2,
1248 Radians(great_circle::MIN_VALUE),
1249 &WGS84_ELLIPSOID,
1250 );
1251 assert_eq!(84.846843174846, Degrees::from(result.0).0);
1252 assert!(is_within_tolerance(12161089.9991805, (result.1).0, 1e-8));
1253 }
1255
1256 #[test]
1257 fn test_calculate_azimuth_arc_length_nearly_antipodal_1() {
1258 let latlon1 = LatLong::new(Degrees(8.226828747671), Degrees(0.0));
1264 let latlon2 = LatLong::new(
1265 Degrees(-8.516119211674268968),
1266 Degrees(178.688979582629224039),
1267 );
1268
1269 let result = calculate_azimuths_and_geodesic_length(
1270 &latlon1,
1271 &latlon2,
1272 Radians(great_circle::MIN_VALUE),
1273 &WGS84_ELLIPSOID,
1274 );
1275 assert!(is_within_tolerance(
1276 111.1269645725,
1277 Degrees::from(result.0).0,
1278 1e-9
1279 ));
1280 assert!(is_within_tolerance(19886305.6710041, (result.1).0, 1e-8));
1281 }
1282
1283 #[test]
1284 fn test_calculate_azimuth_arc_length_nearly_antipodal_2() {
1285 let latlon1 = LatLong::new(Degrees(0.322440123063), Degrees(0.0));
1291 let latlon2 = LatLong::new(
1292 Degrees(-0.367465171996537868),
1293 Degrees(179.160624688175359763),
1294 );
1295
1296 let result = calculate_azimuths_and_geodesic_length(
1297 &latlon1,
1298 &latlon2,
1299 Radians(great_circle::MIN_VALUE),
1300 &WGS84_ELLIPSOID,
1301 );
1302 assert!(is_within_tolerance(
1303 100.319048368176,
1304 Degrees::from(result.0).0,
1305 1e-9
1306 ));
1307 assert!(is_within_tolerance(19943611.6727803, (result.1).0, 1e-8));
1308 }
1309
1310 #[test]
1311 fn test_calculate_azimuth_and_geodesic_length_karney() {
1312 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
1313 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
1314 let (azimuth, length, end_azimuth) = calculate_azimuths_and_geodesic_length(
1315 &istanbul,
1316 &washington,
1317 Radians(great_circle::MIN_VALUE),
1318 &WGS84_ELLIPSOID,
1319 );
1320
1321 let azimuth_degrees = Degrees::from(azimuth);
1322 assert_eq!(-50.69375304113997, azimuth_degrees.0);
1323 assert_eq!(8339863.136005359, length.0);
1324
1325 println!("Istanbul-Washington azimuth: {:?}", azimuth_degrees.0);
1326
1327 let distance_nm = NauticalMiles::from(length);
1328 println!("Istanbul-Washington distance: {:?}", distance_nm);
1329
1330 let azimuth_degrees = Degrees::from(end_azimuth.opposite());
1331 assert_eq!(47.735339288362425, azimuth_degrees.0);
1332 println!("Washington-Istanbul azimuth: {:?}", azimuth_degrees.0);
1333 }
1334
1335 #[test]
1336 fn test_geodesicarc_direct_constructors() {
1337 let length = Metres(9_000_000.0);
1338 let arc_length = Radians(core::f64::consts::FRAC_PI_2);
1339
1340 assert_eq!(128, size_of::<GeodesicSegment>());
1342
1343 let a = LatLong::new(Degrees(45.0), Degrees(45.0));
1344
1345 for i in -180..180 {
1347 let azi = i as f64;
1348 let azimuth = Angle::from(Degrees(azi));
1349
1350 let geodesic1 = GeodesicSegment::from((&a, azimuth, length));
1351 assert!(geodesic1.is_valid());
1352 assert_eq!(Metres(0.0), geodesic1.half_width());
1353 let azi0 = geodesic1.azimuth(Metres(0.0));
1354 assert!(is_within_tolerance(
1355 Radians::from(azimuth).0,
1356 Radians::from(azi0).0,
1357 2.0 * f64::EPSILON
1358 ));
1359
1360 let len0 = geodesic1.length();
1361 assert!(is_within_tolerance(length.0, len0.0, 1.0e-8));
1362
1363 let geodesic2 = GeodesicSegment::from((&a, azimuth, arc_length));
1364 assert!(geodesic2.is_valid());
1365 assert_eq!(Metres(0.0), geodesic2.half_width());
1366 let azi0 = geodesic2.azimuth(Metres(0.0));
1367 assert!(is_within_tolerance(
1368 Radians::from(azimuth).0,
1369 Radians::from(azi0).0,
1370 2.0 * f64::EPSILON
1371 ));
1372
1373 let len0 = geodesic2.arc_length();
1374 assert!(is_within_tolerance(arc_length.0, len0.0, 1.0e-8));
1375 }
1376 }
1377
1378 #[test]
1379 fn test_geodesicarc_between_positions() {
1380 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
1381 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
1382
1383 let tolerance = Radians(great_circle::MIN_VALUE);
1384
1385 let g_0 = GeodesicSegment::between_positions(
1386 &istanbul,
1387 &washington,
1388 Metres(0.0),
1389 tolerance,
1390 &WGS84_ELLIPSOID,
1391 );
1392 assert!(g_0.is_valid());
1393 assert_eq!(Metres(0.0), g_0.half_width());
1394
1395 let end_azimuth = Degrees::from(g_0.azimuth(g_0.length()));
1396 assert_eq!(-132.2646607116376, end_azimuth.0);
1397
1398 let mut g1_clone = g_0.clone();
1399 assert_eq!(g1_clone, g_0);
1400
1401 let g1_clone = g1_clone.set_arc_length(Radians(1.0));
1402 assert_eq!(Radians(1.0), g1_clone.arc_length());
1403 println!("GeodesicSegment: {:?}", &g1_clone);
1404
1405 let g1_clone = g1_clone.set_half_width(Metres(2.0));
1406 assert_eq!(Metres(2.0), g1_clone.half_width());
1407
1408 assert!(is_within_tolerance(
1410 42.0,
1411 Degrees::from(WGS84_ELLIPSOID.calculate_geodetic_latitude(g_0.beta())).0,
1412 32.0 * f64::EPSILON
1413 ));
1414 assert!(is_within_tolerance(
1415 29.0,
1416 Degrees::from(g_0.lon()).0,
1417 16.0 * f64::EPSILON
1418 ));
1419
1420 let lat_long: LatLong = g_0.lat_long(Metres(0.0));
1421 assert!(is_within_tolerance(
1423 istanbul.lat().0,
1424 lat_long.lat().0,
1425 64.0 * f64::EPSILON
1426 ));
1427 assert!(is_within_tolerance(
1428 istanbul.lon().0,
1429 lat_long.lon().0,
1430 32.0 * f64::EPSILON
1431 ));
1432
1433 let start_point = g_0.arc_point(Radians(0.0));
1435 assert!(is_within_tolerance(
1436 istanbul.lat().0,
1437 Degrees::from(
1438 g_0.ellipsoid()
1439 .calculate_geodetic_latitude(unit_sphere::vector::latitude(&start_point))
1440 )
1441 .0,
1442 64.0 * f64::EPSILON
1443 ));
1444 assert!(is_within_tolerance(
1445 istanbul.lon().0,
1446 Degrees::from(unit_sphere::vector::longitude(&start_point)).0,
1447 16.0 * f64::EPSILON
1448 ));
1449
1450 let precision = Metres(1e-3);
1451 let (atd, xtd, iterations) = g_0.calculate_atd_and_xtd(&istanbul, precision);
1452 println!("calculate_atd_and_xtd iterations: {:?}", iterations);
1453 assert_eq!(0.0, atd.0);
1454 assert_eq!(0.0, xtd.0);
1455
1456 let distance = g_0.shortest_distance(&istanbul, precision);
1457 assert_eq!(0.0, distance.0);
1458
1459 let arc_length = g_0.arc_length();
1461 assert_eq!(1.309412846249522, arc_length.0);
1462
1463 let length = g_0.length();
1464 assert_eq!(8339863.136005359, length.0);
1465
1466 assert!(is_within_tolerance(
1468 39.0,
1469 Degrees::from(g_0.latitude(length)).0,
1470 32.0 * f64::EPSILON
1471 ));
1472 assert!(is_within_tolerance(
1473 -77.0,
1474 Degrees::from(g_0.longitude(length)).0,
1475 256.0 * f64::EPSILON
1476 ));
1477
1478 let half_length = length.half();
1480 let mid_position = g_0.lat_long(half_length);
1481
1482 assert!(is_within_tolerance(
1483 54.86379153725445,
1484 Degrees::from(mid_position.lat()).0,
1485 64.0 * f64::EPSILON
1486 ));
1487
1488 assert!(is_within_tolerance(
1489 -25.694568908316413,
1490 Degrees::from(mid_position.lon()).0,
1491 128.0 * f64::EPSILON
1492 ));
1493
1494 let mid_length = g_0.metres_to_radians(half_length);
1495 assert_eq!(0.654673165141749, mid_length.0);
1496 let mid_point = g_0.mid_point();
1497 let mid_beta = unit_sphere::vector::latitude(&mid_point);
1498 let mid_lat = g_0.ellipsoid().calculate_geodetic_latitude(mid_beta);
1499 assert!(is_within_tolerance(
1500 54.86379153725445,
1501 Degrees::from(mid_lat).0,
1502 64.0 * f64::EPSILON
1503 ));
1504
1505 let mid_lon = unit_sphere::vector::longitude(&mid_point);
1506 assert!(is_within_tolerance(
1507 -25.694568908316413,
1508 Degrees::from(mid_lon).0,
1509 128.0 * f64::EPSILON
1510 ));
1511
1512 let precision_r = Radians(1e-3 / WGS84_ELLIPSOID.a().0);
1513 let (atd, xtd, iterations) =
1514 g_0.calculate_sphere_atd_and_xtd(mid_beta, mid_lon, precision_r);
1515 assert!(is_within_tolerance(mid_length.0, atd.0, f64::EPSILON));
1516 assert_eq!(0.0, xtd.0);
1517 println!("calculate_sphere_atd_and_xtd iterations: {:?}", iterations);
1518
1519 let (atd, xtd, iterations) = g_0.calculate_atd_and_xtd(&mid_position, precision);
1520 assert!(is_within_tolerance(half_length.0, atd.0, 1e-3));
1521 assert!(xtd.0.abs() < 1e-3);
1522 println!("calculate_atd_and_xtd iterations: {:?}", iterations);
1523
1524 let distance = g_0.shortest_distance(&mid_position, precision);
1525 assert_eq!(0.0, distance.0);
1526
1527 let g_1 = g_0.reverse();
1528 assert_eq!(g_0.arc_length(), g_1.arc_length());
1529 assert_eq!(g_0.length(), g_1.length());
1530 assert_eq!(
1531 washington.lat().0,
1532 Degrees::from(g_1.arc_latitude(Radians::default())).0
1533 );
1534 assert_eq!(
1535 washington.lon().0,
1536 Degrees::from(g_1.arc_longitude(Radians(0.0))).0
1537 );
1538 assert!(is_within_tolerance(
1539 istanbul.lat().0,
1540 Degrees::from(g_1.arc_latitude(g_1.arc_length())).0,
1541 64.0 * f64::EPSILON
1542 ));
1543 assert!(is_within_tolerance(
1544 istanbul.lon().0,
1545 Degrees::from(g_1.arc_longitude(g_1.arc_length())).0,
1546 64.0 * f64::EPSILON
1547 ));
1548 }
1549
1550 #[test]
1551 fn test_meridonal_geodesicarc() {
1552 let a = LatLong::new(Degrees(45.0), Degrees(0.0));
1554 let b = LatLong::new(Degrees(45.0), Degrees(180.0));
1555 let g_0 = GeodesicSegment::from((&a, &b));
1556 let pole0 = g_0.arc_pole(Radians(0.0));
1557
1558 let mid_length = g_0.arc_length().half();
1560 let azimuth = Degrees::from(g_0.arc_azimuth(Angle::from(mid_length)));
1561 assert_eq!(180.0, azimuth.0);
1562
1563 let (point1, pole1) = g_0.arc_point_and_pole(mid_length);
1565 assert_eq!(Vector3d::new(0.5 * f64::EPSILON, 0.0, 1.0), point1);
1566 assert_eq!(pole0, pole1);
1567 }
1568
1569 #[test]
1570 fn test_geodesicarc_90n_0n_0e() {
1571 let a = LatLong::new(Degrees(90.0), Degrees(0.0));
1572 let b = LatLong::new(Degrees(0.0), Degrees(0.0));
1573 let g_0 = GeodesicSegment::from((&a, &b));
1574
1575 assert!(is_within_tolerance(
1576 core::f64::consts::FRAC_PI_2,
1577 g_0.arc_length().0,
1578 f64::EPSILON
1579 ));
1580 assert_eq!(180.0, Degrees::from(g_0.azi()).0);
1581 }
1582
1583 #[test]
1584 fn test_geodesicarc_90s_0n_50e() {
1585 let a = LatLong::new(Degrees(-90.0), Degrees(0.0));
1586 let b = LatLong::new(Degrees(0.0), Degrees(50.0));
1587 let g_0 = GeodesicSegment::from((&a, &b));
1588
1589 assert!(is_within_tolerance(
1590 core::f64::consts::FRAC_PI_2,
1591 g_0.arc_length().0,
1592 f64::EPSILON
1593 ));
1594 assert_eq!(0.0, Degrees::from(g_0.azi()).0);
1595 }
1596
1597 #[test]
1598 fn test_calculate_atd_and_xtd() {
1599 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
1602 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
1603 let g_0 = GeodesicSegment::from((&istanbul, &washington));
1604
1605 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
1606
1607 let precision = Metres(1e-3);
1609
1610 let (atd, xtd, iterations) = g_0.calculate_atd_and_xtd(&reyjavik, precision);
1611 assert!(is_within_tolerance(3928788.572, atd.0, precision.0));
1612 assert!(is_within_tolerance(-1010585.9988368, xtd.0, precision.0));
1613 println!("calculate_atd_and_xtd iterations: {:?}", iterations);
1614
1615 let position = g_0.lat_long(atd);
1618 assert!(is_within_tolerance(
1619 54.92853149711691,
1620 Degrees::from(position.lat()).0,
1621 128.0 * f64::EPSILON
1622 ));
1623 assert!(is_within_tolerance(
1624 -21.93729106604878,
1625 Degrees::from(position.lon()).0,
1626 2048.0 * f64::EPSILON
1627 ));
1628
1629 let azimuth_1 = g_0.azimuth(atd);
1631 let g_1 = GeodesicSegment::from((&position, &reyjavik));
1632 let azimuth_2 = g_1.azi();
1633 let delta_azimuth = azimuth_2 - azimuth_1;
1634 assert!(is_within_tolerance(
1635 core::f64::consts::FRAC_PI_2,
1636 Radians::from(delta_azimuth).0,
1637 128.0 * f64::EPSILON
1638 ));
1639
1640 let g_0 = GeodesicSegment::from((&washington, &istanbul));
1642 let (atd, xtd, iterations) = g_0.calculate_atd_and_xtd(&reyjavik, precision);
1643 assert!(is_within_tolerance(
1644 g_0.length().0 - 3928788.572,
1645 atd.0,
1646 precision.0
1647 ));
1648 assert!(is_within_tolerance(1010585.9988368, xtd.0, 1e-3));
1649 println!("calculate_atd_and_xtd iterations: {:?}", iterations);
1650
1651 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
1652 let distance = g_0.shortest_distance(&reyjavik, precision);
1653 assert!(is_within_tolerance(
1654 1010585.998836817,
1655 distance.0,
1656 precision.0
1657 ));
1658
1659 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
1660 let distance = g_0.shortest_distance(&accra, precision);
1661 assert!(is_within_tolerance(
1662 4891211.398445355,
1663 distance.0,
1664 precision.0
1665 ));
1666
1667 let chicago = LatLong::new(Degrees(42.0), Degrees(-88.0));
1668 let distance = g_0.shortest_distance(&chicago, precision);
1669 assert!(is_within_tolerance(
1670 989277.1859906457,
1671 distance.0,
1672 precision.0
1673 ));
1674
1675 let singapore = LatLong::new(Degrees(1.0), Degrees(104.0));
1676 let distance = g_0.shortest_distance(&singapore, precision);
1677 assert!(is_within_tolerance(
1678 8699538.22763653,
1679 distance.0,
1680 precision.0
1681 ));
1682 }
1683
1684 #[test]
1685 fn test_intersection_point_distance() {
1686 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
1689 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
1690 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
1691 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
1692
1693 let g_0 = GeodesicSegment::from((&istanbul, &washington));
1694 let g_1 = GeodesicSegment::from((&reyjavik, &accra));
1695
1696 let (d1, d2) = calculate_intersection_distances(&g_0, &g_1, Metres(1e-3));
1697 assert!(is_within_tolerance(0.5423772210673643, d1.0, 1e-6));
1698 assert!(is_within_tolerance(0.17504915893226164, d2.0, 1e-6));
1699
1700 let result = calculate_intersection_point(&g_0, &g_1, Metres(1e-3));
1701 let lat_lon = result.unwrap();
1702
1703 assert!(is_within_tolerance(54.7170296089477, lat_lon.lat().0, 1e-6));
1704 assert!(is_within_tolerance(
1705 -14.56385574430775,
1706 lat_lon.lon().0,
1707 1e-6
1708 ));
1709
1710 let result = calculate_intersection_point(&g_1, &g_0, Metres(1e-3));
1712 let lat_lon = result.unwrap();
1713
1714 assert!(is_within_tolerance(54.7170296089477, lat_lon.lat().0, 1e-6));
1715 assert!(is_within_tolerance(
1716 -14.56385574430775,
1717 lat_lon.lon().0,
1718 1e-6
1719 ));
1720 }
1721
1722 #[test]
1723 fn test_intersection_point_non_intersecting() {
1724 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
1725 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
1726 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
1727 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
1728
1729 let g_0 = GeodesicSegment::from((&istanbul, &accra));
1730 let g_1 = GeodesicSegment::from((&reyjavik, &washington));
1731
1732 let result = calculate_intersection_point(&g_0, &g_1, Metres(1e-3));
1733 assert!(result.is_none());
1734 }
1735
1736 #[test]
1737 fn test_intersection_coincident_geodesic_paths() {
1738 let south_pole_1 = LatLong::new(Degrees(-88.0), Degrees(-180.0));
1739 let south_pole_2 = LatLong::new(Degrees(-87.0), Degrees(0.0));
1740
1741 let g_0 = GeodesicSegment::from((&south_pole_1, &south_pole_2));
1742
1743 let intersection_lengths = calculate_intersection_distances(&g_0, &g_0, Metres(1e-3));
1744 assert_eq!(g_0.arc_length().half(), intersection_lengths.0);
1745 assert_eq!(g_0.arc_length().half(), intersection_lengths.1);
1746
1747 let intersection_point = calculate_intersection_point(&g_0, &g_0, Metres(1e-3));
1748 let lat_lon = intersection_point.unwrap();
1749 assert!(is_within_tolerance(-89.5, lat_lon.lat().0, 1e-5));
1750 assert_eq!(0.0, lat_lon.lon().0);
1751
1752 let south_pole_3 = LatLong::new(Degrees(-85.0), Degrees(0.0));
1753 let south_pole_4 = LatLong::new(Degrees(-86.0), Degrees(0.0));
1754 let g_1 = GeodesicSegment::from((&south_pole_3, &south_pole_4));
1755 let intersection_point = calculate_intersection_point(&g_0, &g_1, Metres(1e-3));
1756 assert!(intersection_point.is_none());
1757 }
1758}