Skip to main content

geometry_algorithm/
destination.rs

1//! Point at a bearing and distance from an angular-coordinate point.
2//!
3//! The public entry wraps the direct geodesic formulas ported from Boost's
4//! `formulas/*_direct.hpp` and selects a default by coordinate-system family.
5
6use geometry_cs::CoordinateSystem;
7use geometry_strategy::{DefaultDestination, DefaultDestinationStrategy, DestinationStrategy};
8use geometry_trait::Point;
9
10type Family<P> = <<P as Point>::Cs as CoordinateSystem>::Family;
11
12/// Compute the destination using the point's coordinate-system default.
13///
14/// `bearing` is in radians, clockwise from north. `distance` uses the default
15/// strategy's radius or spheroid units. The result uses the origin point's
16/// coordinate system and angular unit.
17#[inline]
18#[must_use]
19pub fn destination<P>(
20    origin: &P,
21    bearing: f64,
22    distance: f64,
23) -> <DefaultDestinationStrategy<P> as DestinationStrategy<P>>::Output
24where
25    P: Point,
26    Family<P>: DefaultDestination<Family<P>>,
27    DefaultDestinationStrategy<P>: DestinationStrategy<P> + Default,
28{
29    DefaultDestinationStrategy::<P>::default().destination(origin, bearing, distance)
30}
31
32/// Compute the destination using an explicitly supplied direct strategy.
33#[inline]
34#[must_use]
35#[allow(
36    clippy::needless_pass_by_value,
37    reason = "direct strategies are small Copy configuration values, matching other _with entries"
38)]
39pub fn destination_with<P, S>(origin: &P, bearing: f64, distance: f64, strategy: S) -> S::Output
40where
41    P: Point,
42    S: DestinationStrategy<P>,
43{
44    strategy.destination(origin, bearing, distance)
45}
46
47#[cfg(all(test, feature = "std"))]
48mod tests {
49    use super::{destination, destination_with};
50    use geometry_cs::{Degree, Geographic, Spherical};
51    use geometry_model::Point2D;
52    use geometry_strategy::{Haversine, VincentyDirect};
53    use geometry_trait::Point as _;
54
55    #[test]
56    fn spherical_and_geographic_defaults_return_input_units() {
57        type SphericalPoint = Point2D<f64, Spherical<Degree>>;
58        type GeographicPoint = Point2D<f64, Geographic<Degree>>;
59
60        let spherical = destination(
61            &SphericalPoint::new(0.0, 0.0),
62            core::f64::consts::FRAC_PI_2,
63            Haversine::EARTH.radius,
64        );
65        assert!((spherical.get::<0>().to_radians() - 1.0).abs() < 1e-12);
66
67        let geographic = destination_with(
68            &GeographicPoint::new(0.0, 0.0),
69            core::f64::consts::FRAC_PI_2,
70            100_000.0,
71            VincentyDirect::WGS84,
72        );
73        assert!((geographic.get::<0>() - 0.898_315_284_1).abs() < 1e-6);
74    }
75}