Skip to content

Commit

Permalink
Merge pull request #1248 from georust/mkirk/point_at_ratio
Browse files Browse the repository at this point in the history
InterpolatePoint: handle some reasonable edge cases, document current behavior in tests.
  • Loading branch information
michaelkirk authored Nov 1, 2024
2 parents d6edde2 + fd935b2 commit 5f3f912
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 0 deletions.
1 change: 1 addition & 0 deletions geo/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

* Improve handling of InterploatePoint with collapsed Line

## 0.29.0 - 2024.10.30

Expand Down
156 changes: 156 additions & 0 deletions geo/src/algorithm/line_measures/interpolate_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,159 @@ pub trait InterpolatePoint<F: CoordFloat> {
include_ends: bool,
) -> impl Iterator<Item = Point<F>>;
}

#[cfg(test)]
mod tests {
use crate::{Euclidean, Geodesic, Haversine, InterpolatePoint, Point, Rhumb};

#[test]
fn point_at_ratio_between_line_ends() {
let start = Point::new(0.0, 0.0);
let end = Point::new(1.0, 1.0);

let ratio = 0.0;
assert_eq!(Haversine::point_at_ratio_between(start, end, ratio), start);
assert_eq!(Euclidean::point_at_ratio_between(start, end, ratio), start);
assert_eq!(Geodesic::point_at_ratio_between(start, end, ratio), start);
assert_eq!(Rhumb::point_at_ratio_between(start, end, ratio), start);

let ratio = 1.0;
assert_eq!(Haversine::point_at_ratio_between(start, end, ratio), end);
assert_eq!(Euclidean::point_at_ratio_between(start, end, ratio), end);
assert_eq!(Geodesic::point_at_ratio_between(start, end, ratio), end);
assert_eq!(Rhumb::point_at_ratio_between(start, end, ratio), end);
}

mod degenerate {
use super::*;

#[test]
fn point_at_ratio_between_collapsed_line() {
let start = Point::new(1.0, 1.0);

let ratio = 0.0;
assert_eq!(
Haversine::point_at_ratio_between(start, start, ratio),
start
);
assert_eq!(
Euclidean::point_at_ratio_between(start, start, ratio),
start
);
assert_eq!(Geodesic::point_at_ratio_between(start, start, ratio), start);
assert_eq!(Rhumb::point_at_ratio_between(start, start, ratio), start);

let ratio = 0.5;
assert_eq!(
Haversine::point_at_ratio_between(start, start, ratio),
start
);
assert_eq!(
Euclidean::point_at_ratio_between(start, start, ratio),
start
);
assert_eq!(Geodesic::point_at_ratio_between(start, start, ratio), start);
assert_eq!(Rhumb::point_at_ratio_between(start, start, ratio), start);

let ratio = 1.0;
assert_eq!(
Haversine::point_at_ratio_between(start, start, ratio),
start
);
assert_eq!(
Euclidean::point_at_ratio_between(start, start, ratio),
start
);
assert_eq!(Geodesic::point_at_ratio_between(start, start, ratio), start);
assert_eq!(Rhumb::point_at_ratio_between(start, start, ratio), start);
}

#[test]
fn point_at_distance_between_collapsed_line() {
// This method just documents existing behavior. I don't think our current behavior
// is especially useful, but we might consider handling it uniformly one day.
let start: Point = Point::new(1.0, 1.0);

let distance = 0.0;
assert_eq!(
Haversine::point_at_distance_between(start, start, distance),
start
);

let euclidean_result = Euclidean::point_at_distance_between(start, start, distance);
assert!(euclidean_result.x().is_nan());
assert!(euclidean_result.y().is_nan());
assert_eq!(
Geodesic::point_at_distance_between(start, start, distance),
start
);
assert_eq!(
Rhumb::point_at_distance_between(start, start, distance),
start
);

let distance = 100000.0;
let due_north = Point::new(1.0, 1.9);
let due_south = Point::new(1.0, 0.1);
assert_relative_eq!(
Haversine::point_at_distance_between(start, start, distance),
due_north,
epsilon = 1.0e-1
);
let euclidean_result = Euclidean::point_at_distance_between(start, start, distance);
assert!(euclidean_result.x().is_nan());
assert!(euclidean_result.y().is_nan());
assert_relative_eq!(
Geodesic::point_at_distance_between(start, start, distance),
due_south,
epsilon = 1.0e-1
);
assert_relative_eq!(
Rhumb::point_at_distance_between(start, start, distance),
due_north,
epsilon = 1.0e-1
);
}

#[test]
fn points_along_collapsed_line() {
let start = Point::new(1.0, 1.0);

let max_distance = 1.0;

let include_ends = true;
let points: Vec<_> =
Haversine::points_along_line(start, start, max_distance, include_ends).collect();
assert_eq!(points, vec![start, start]);

let points: Vec<_> =
Euclidean::points_along_line(start, start, max_distance, include_ends).collect();
assert_eq!(points, vec![start, start]);

let points: Vec<_> =
Geodesic::points_along_line(start, start, max_distance, include_ends).collect();
assert_eq!(points, vec![start, start]);

let points: Vec<_> =
Rhumb::points_along_line(start, start, max_distance, include_ends).collect();
assert_eq!(points, vec![start, start]);

let include_ends = false;
let points: Vec<_> =
Haversine::points_along_line(start, start, max_distance, include_ends).collect();
assert_eq!(points, vec![]);

let points: Vec<_> =
Euclidean::points_along_line(start, start, max_distance, include_ends).collect();
assert_eq!(points, vec![]);

let points: Vec<_> =
Geodesic::points_along_line(start, start, max_distance, include_ends).collect();
assert_eq!(points, vec![]);

let points: Vec<_> =
Rhumb::points_along_line(start, start, max_distance, include_ends).collect();
assert_eq!(points, vec![]);
}
}
}
10 changes: 10 additions & 0 deletions geo/src/algorithm/line_measures/metric_spaces/geodesic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ impl InterpolatePoint<f64> for Geodesic {
end: Point<f64>,
meters_from_start: f64,
) -> Point<f64> {
if meters_from_start == 0.0 {
return start;
}
let bearing = Self::bearing(start, end);
Self::destination(start, bearing, meters_from_start)
}
Expand Down Expand Up @@ -205,6 +208,13 @@ impl InterpolatePoint<f64> for Geodesic {
end: Point<f64>,
ratio_from_start: f64,
) -> Point<f64> {
if start == end || ratio_from_start == 0.0 {
return start;
}
if ratio_from_start == 1.0 {
return end;
}

let g = geographiclib_rs::Geodesic::wgs84();
let (total_distance, azi1, _azi2, _a12) = g.inverse(start.y(), start.x(), end.y(), end.x());
let distance = total_distance * ratio_from_start;
Expand Down
6 changes: 6 additions & 0 deletions geo/src/algorithm/line_measures/metric_spaces/haversine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ impl<F: CoordFloat + FromPrimitive> InterpolatePoint<F> for Haversine {
///
/// [great circle]: https://en.wikipedia.org/wiki/Great_circle
fn point_at_ratio_between(start: Point<F>, end: Point<F>, ratio_from_start: F) -> Point<F> {
if start == end || ratio_from_start == F::zero() {
return start;
}
if ratio_from_start == F::one() {
return end;
}
let calculation = HaversineIntermediateFillCalculation::new(start, end);
calculation.point_at_ratio(ratio_from_start)
}
Expand Down

0 comments on commit 5f3f912

Please sign in to comment.