geometry_strategy/
destination.rs1use geometry_cs::{CoordinateSystem, GeographicFamily, SphericalFamily};
8use geometry_trait::Point;
9
10#[cfg(feature = "std")]
11use crate::normalise::{HasAngularUnits, lonlat_radians};
12#[cfg(feature = "std")]
13use geometry_cs::AngleUnit;
14#[cfg(feature = "std")]
15use geometry_model::Point2D;
16
17pub trait DestinationStrategy<P: Point> {
19 type Output: Point;
21
22 fn destination(&self, origin: &P, bearing: f64, distance: f64) -> Self::Output;
25}
26
27pub trait DefaultDestination<Family> {
29 type Strategy: Default;
31}
32
33impl DefaultDestination<SphericalFamily> for SphericalFamily {
34 type Strategy = crate::spherical::Haversine;
35}
36
37impl DefaultDestination<GeographicFamily> for GeographicFamily {
38 type Strategy = crate::geographic::KarneyDirect;
39}
40
41pub type DefaultDestinationStrategy<P> =
43 <<<P as Point>::Cs as CoordinateSystem>::Family as DefaultDestination<
44 <<P as Point>::Cs as CoordinateSystem>::Family,
45 >>::Strategy;
46
47#[cfg(feature = "std")]
48impl<P> DestinationStrategy<P> for crate::spherical::Haversine
49where
50 P: Point<Scalar = f64>,
51 P::Cs: HasAngularUnits,
52 <P::Cs as CoordinateSystem>::Family: geometry_tag::SameAs<SphericalFamily>,
53{
54 type Output = Point2D<f64, P::Cs>;
55
56 fn destination(&self, origin: &P, bearing: f64, distance: f64) -> Self::Output {
57 let (longitude, latitude) = lonlat_radians(origin);
58 let angular_distance = distance / self.radius;
59 let sin_latitude = latitude.sin();
60 let cos_latitude = latitude.cos();
61 let sin_distance = angular_distance.sin();
62 let cos_distance = angular_distance.cos();
63 let latitude2 =
64 (sin_latitude * cos_distance + cos_latitude * sin_distance * bearing.cos()).asin();
65 let longitude2 = longitude
66 + (bearing.sin() * sin_distance * cos_latitude)
67 .atan2(cos_distance - sin_latitude * latitude2.sin());
68 point_from_radians::<P>(normalize_longitude(longitude2), latitude2)
69 }
70}
71
72macro_rules! impl_geographic_destination {
73 ($strategy:ty) => {
74 #[cfg(feature = "std")]
75 impl<P> DestinationStrategy<P> for $strategy
76 where
77 P: Point<Scalar = f64>,
78 P::Cs: HasAngularUnits,
79 <P::Cs as CoordinateSystem>::Family: geometry_tag::SameAs<GeographicFamily>,
80 {
81 type Output = Point2D<f64, P::Cs>;
82
83 fn destination(&self, origin: &P, bearing: f64, distance: f64) -> Self::Output {
84 let (longitude, latitude) = lonlat_radians(origin);
85 let result = self.apply(longitude, latitude, distance, bearing);
86 point_from_radians::<P>(result.lon2, result.lat2)
87 }
88 }
89 };
90}
91
92impl_geographic_destination!(crate::geographic::KarneyDirect);
93impl_geographic_destination!(crate::geographic::ThomasDirect);
94impl_geographic_destination!(crate::geographic::VincentyDirect);
95
96#[cfg(feature = "std")]
97fn point_from_radians<P>(longitude: f64, latitude: f64) -> Point2D<f64, P::Cs>
98where
99 P: Point<Scalar = f64>,
100 P::Cs: HasAngularUnits,
101{
102 type Units<P> = <<P as Point>::Cs as HasAngularUnits>::Units;
103 Point2D::new(
104 Units::<P>::from_radians(longitude),
105 Units::<P>::from_radians(latitude),
106 )
107}
108
109#[cfg(feature = "std")]
110fn normalize_longitude(longitude: f64) -> f64 {
111 (longitude + core::f64::consts::PI).rem_euclid(core::f64::consts::TAU) - core::f64::consts::PI
112}