Skip to content

Commit 8ca9bb7

Browse files
committed
Rhumb no longer goes past pole
1 parent b1e550a commit 8ca9bb7

File tree

4 files changed

+31
-26
lines changed

4 files changed

+31
-26
lines changed

geo/src/algorithm/rhumb/bearing.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ mod test {
8383
#[test]
8484
fn consistent_with_destination() {
8585
let p_1 = point!(x: 9.177789688110352f64, y: 48.776781529534965);
86-
let p_2 = p_1.rhumb_destination(45., 10000.);
86+
let p_2 = p_1.rhumb_destination(45., 10000.).unwrap();
8787

8888
let b_1 = p_1.rhumb_bearing(p_2);
8989
assert_relative_eq!(b_1, 45., epsilon = 1.0e-6);

geo/src/algorithm/rhumb/destination.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ pub trait RhumbDestination<T: CoordFloat> {
1212
/// Returns the destination Point having travelled the given distance along a [rhumb line]
1313
/// from the origin Point with the given bearing
1414
///
15+
/// A rhumb line has a finite length, so if the path distance exceeds the location of the pole,
16+
/// the result is None.
17+
///
1518
/// # Units
1619
///
1720
/// - `bearing`: degrees, zero degrees is north
@@ -24,18 +27,18 @@ pub trait RhumbDestination<T: CoordFloat> {
2427
/// use geo::Point;
2528
///
2629
/// let p_1 = Point::new(9.177789688110352, 48.776781529534965);
27-
/// let p_2 = p_1.rhumb_destination(45., 10000.);
30+
/// let p_2 = p_1.rhumb_destination(45., 10000.).unwrap();
2831
/// assert_eq!(p_2, Point::new(9.274348757829898, 48.84037308229984))
2932
/// ```
3033
/// [rhumb line]: https://en.wikipedia.org/wiki/Rhumb_line
31-
fn rhumb_destination(&self, bearing: T, distance: T) -> Point<T>;
34+
fn rhumb_destination(&self, bearing: T, distance: T) -> Option<Point<T>>;
3235
}
3336

3437
impl<T> RhumbDestination<T> for Point<T>
3538
where
3639
T: CoordFloat + FromPrimitive,
3740
{
38-
fn rhumb_destination(&self, bearing: T, distance: T) -> Point<T> {
41+
fn rhumb_destination(&self, bearing: T, distance: T) -> Option<Point<T>> {
3942
let delta = distance / T::from(MEAN_EARTH_RADIUS).unwrap(); // angular distance in radians
4043
let lambda1 = self.x().to_radians();
4144
let phi1 = self.y().to_radians();
@@ -49,32 +52,38 @@ where
4952
mod test {
5053
use super::*;
5154
use crate::RhumbDistance;
52-
use num_traits::pow;
5355

5456
#[test]
5557
fn returns_a_new_point() {
5658
let p_1 = Point::new(9.177789688110352, 48.776781529534965);
57-
let p_2 = p_1.rhumb_destination(45., 10000.);
59+
let p_2 = p_1.rhumb_destination(45., 10000.).unwrap();
5860
assert_eq!(p_2, Point::new(9.274348757829898, 48.84037308229984));
5961
let distance = p_1.rhumb_distance(&p_2);
60-
assert_relative_eq!(distance, 10000., epsilon = 1.0e-6)
62+
assert_relative_eq!(distance, 10000., epsilon = 1.0e-6);
6163
}
6264

6365
#[test]
6466
fn direct_and_indirect_destinations_are_close() {
6567
let p_1 = Point::new(9.177789688110352, 48.776781529534965);
66-
let p_2 = p_1.rhumb_destination(45., 10000.);
67-
let square_edge = { pow(10000., 2) / 2f64 }.sqrt();
68-
let p_3 = p_1.rhumb_destination(0., square_edge);
69-
let p_4 = p_3.rhumb_destination(90., square_edge);
68+
let p_2 = p_1.rhumb_destination(45., 10000.).unwrap();
69+
let square_edge = 10000.0 * 0.5f64.sqrt();
70+
let p_3 = p_1.rhumb_destination(0., square_edge).unwrap();
71+
let p_4 = p_3.rhumb_destination(90., square_edge).unwrap();
7072
assert_relative_eq!(p_4, p_2, epsilon = 1.0e-3);
7173
}
7274

7375
#[test]
7476
fn bearing_zero_is_north() {
7577
let p_1 = Point::new(9.177789688110352, 48.776781529534965);
76-
let p_2 = p_1.rhumb_destination(0., 1000.);
78+
let p_2 = p_1.rhumb_destination(0., 1000.).unwrap();
7779
assert_relative_eq!(p_1.x(), p_2.x(), epsilon = 1.0e-6);
78-
assert!(p_2.y() > p_1.y())
80+
assert!(p_2.y() > p_1.y());
81+
}
82+
83+
#[test]
84+
fn past_the_pole() {
85+
let p = Point::new(9.177789688110352, 48.776781529534965);
86+
let result = p.rhumb_destination(0., 1e8);
87+
assert!(result.is_none());
7988
}
8089
}

geo/src/algorithm/rhumb/mod.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl<T: CoordFloat + FromPrimitive> RhumbCalculations<T> {
5353
let delta_psi = ((phi2 / two + pi / four).tan() / (phi1 / two + pi / four).tan()).ln();
5454
let delta_phi = phi2 - phi1;
5555

56-
RhumbCalculations {
56+
Self {
5757
from: *from,
5858
to: *to,
5959
phi1,
@@ -82,7 +82,7 @@ impl<T: CoordFloat + FromPrimitive> RhumbCalculations<T> {
8282
let delta = fraction * self.delta();
8383
let theta = self.theta();
8484
let lambda1 = self.from.x().to_radians();
85-
calculate_destination(delta, lambda1, self.phi1, theta)
85+
calculate_destination(delta, lambda1, self.phi1, theta).unwrap()
8686
}
8787

8888
fn intermediate_fill(&self, max_delta: T, include_ends: bool) -> Vec<Point<T>> {
@@ -112,7 +112,7 @@ impl<T: CoordFloat + FromPrimitive> RhumbCalculations<T> {
112112
while current_step < T::one() {
113113
let delta = total_delta * current_step;
114114
let point = calculate_destination(delta, lambda1, self.phi1, theta);
115-
points.push(point);
115+
points.push(point.unwrap());
116116
current_step = current_step + interval;
117117
}
118118

@@ -129,22 +129,18 @@ fn calculate_destination<T: CoordFloat + FromPrimitive>(
129129
lambda1: T,
130130
phi1: T,
131131
theta: T,
132-
) -> Point<T> {
132+
) -> Option<Point<T>> {
133133
let pi = T::from(std::f64::consts::PI).unwrap();
134134
let two = T::one() + T::one();
135135
let four = two + two;
136136
let threshold = T::from(10.0e-12).unwrap();
137137

138138
let delta_phi = delta * theta.cos();
139-
let mut phi2 = phi1 + delta_phi;
139+
let phi2 = phi1 + delta_phi;
140140

141141
// check for some daft bugger going past the pole, normalise latitude if so
142142
if phi2.abs() > pi / two {
143-
phi2 = if phi2 > T::zero() {
144-
pi - phi2
145-
} else {
146-
-pi - phi2
147-
};
143+
return None;
148144
}
149145

150146
let delta_psi = ((phi2 / two + pi / four).tan() / (phi1 / two + pi / four).tan()).ln();
@@ -158,8 +154,8 @@ fn calculate_destination<T: CoordFloat + FromPrimitive>(
158154
let delta_lambda = (delta * theta.sin()) / q;
159155
let lambda2 = lambda1 + delta_lambda;
160156

161-
point! {
157+
Some(point! {
162158
x: normalize_longitude(lambda2.to_degrees()),
163159
y: phi2.to_degrees(),
164-
}
160+
})
165161
}

geo/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ extern crate log;
242242
/// https://link.springer.com/article/10.1007%2Fs001900050278
243243
/// https://sci-hub.se/https://doi.org/10.1007/s001900050278
244244
/// https://en.wikipedia.org/wiki/Earth_radius#Mean_radius
245-
const MEAN_EARTH_RADIUS: f64 = 6371008.8;
245+
const MEAN_EARTH_RADIUS: f64 = 6_371_008.8;
246246

247247
// Radius of Earth at the equator in meters (derived from the WGS-84 ellipsoid)
248248
const EQUATORIAL_EARTH_RADIUS: f64 = 6_378_137.0;

0 commit comments

Comments
 (0)