Skip to main content

geometry_algorithm/
rhumb.rs

1//! Named rhumb-line measurement entries.
2//!
3//! Boost.Geometry has no loxodrome entry points. These functions expose the
4//! published constant-bearing formulas through the same default and `_with`
5//! strategy shape as the Boost-origin measurement algorithms.
6
7use geometry_strategy::{
8    AzimuthStrategy, DestinationStrategy, DistanceStrategy, LengthStrategy, Rhumb,
9};
10use geometry_trait::{Geometry, Linestring, Point};
11
12/// Rhumb-line distance using the mean-Earth-radius default.
13#[inline]
14#[must_use]
15pub fn rhumb_distance<A, B>(first: &A, second: &B) -> f64
16where
17    A: Geometry,
18    B: Geometry,
19    Rhumb: DistanceStrategy<A, B, Out = f64>,
20{
21    rhumb_distance_with(first, second, Rhumb::default())
22}
23
24/// Rhumb-line distance using an explicit metric strategy.
25#[inline]
26#[must_use]
27#[allow(
28    clippy::needless_pass_by_value,
29    reason = "rhumb strategies are small Copy radius configurations"
30)]
31pub fn rhumb_distance_with<A, B, S>(first: &A, second: &B, strategy: S) -> S::Out
32where
33    A: Geometry,
34    B: Geometry,
35    S: DistanceStrategy<A, B>,
36{
37    strategy.distance(first, second)
38}
39
40/// Constant compass bearing in radians, clockwise from north.
41#[inline]
42#[must_use]
43pub fn rhumb_azimuth<P1, P2>(first: &P1, second: &P2) -> f64
44where
45    P1: Point,
46    P2: Point,
47    Rhumb: AzimuthStrategy<P1, P2, Out = f64>,
48{
49    rhumb_azimuth_with(first, second, Rhumb::default())
50}
51
52/// Constant compass bearing using an explicit rhumb strategy.
53#[inline]
54#[must_use]
55#[allow(
56    clippy::needless_pass_by_value,
57    reason = "rhumb strategies are small Copy radius configurations"
58)]
59pub fn rhumb_azimuth_with<P1, P2, S>(first: &P1, second: &P2, strategy: S) -> S::Out
60where
61    P1: Point,
62    P2: Point,
63    S: AzimuthStrategy<P1, P2>,
64{
65    strategy.azimuth(first, second)
66}
67
68/// Destination reached along a constant-bearing rhumb line.
69#[inline]
70#[must_use]
71pub fn rhumb_destination<P>(origin: &P, bearing: f64, distance: f64) -> PointOutput<P>
72where
73    P: Point,
74    Rhumb: DestinationStrategy<P>,
75{
76    rhumb_destination_with(origin, bearing, distance, Rhumb::default())
77}
78
79/// Rhumb destination using an explicit radius strategy.
80#[inline]
81#[must_use]
82#[allow(
83    clippy::needless_pass_by_value,
84    reason = "rhumb strategies are small Copy radius configurations"
85)]
86pub fn rhumb_destination_with<P, S>(
87    origin: &P,
88    bearing: f64,
89    distance: f64,
90    strategy: S,
91) -> S::Output
92where
93    P: Point,
94    S: DestinationStrategy<P>,
95{
96    strategy.destination(origin, bearing, distance)
97}
98
99/// Sum rhumb-line distances along a linestring.
100#[inline]
101#[must_use]
102pub fn rhumb_length<L>(line: &L) -> f64
103where
104    L: Linestring,
105    Rhumb: LengthStrategy<L, Out = f64>,
106{
107    rhumb_length_with(line, Rhumb::default())
108}
109
110/// Sum rhumb-line distances using an explicit radius strategy.
111#[inline]
112#[must_use]
113#[allow(
114    clippy::needless_pass_by_value,
115    reason = "rhumb strategies are small Copy radius configurations"
116)]
117pub fn rhumb_length_with<L, S>(line: &L, strategy: S) -> S::Out
118where
119    L: Geometry,
120    S: LengthStrategy<L>,
121{
122    strategy.length(line)
123}
124
125type PointOutput<P> = <Rhumb as DestinationStrategy<P>>::Output;
126
127#[cfg(test)]
128mod tests {
129    use geometry_cs::{Degree, Spherical};
130    use geometry_model::{Linestring, Point2D};
131    use geometry_trait::Point as _;
132
133    use super::*;
134
135    #[test]
136    fn named_entries_share_one_metric() {
137        type P = Point2D<f64, Spherical<Degree>>;
138        let start = P::new(0.0, 0.0);
139        let east = P::new(1.0, 0.0);
140        let distance = rhumb_distance(&start, &east);
141        assert!((rhumb_azimuth(&start, &east) - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
142        let endpoint = rhumb_destination(&start, core::f64::consts::FRAC_PI_2, distance);
143        assert!((endpoint.get::<0>() - 1.0).abs() < 1e-10);
144        let line = Linestring::from_vec(alloc::vec![start, east]);
145        assert!((rhumb_length(&line) - distance).abs() < 1e-9);
146    }
147}