Skip to content

Commit

Permalink
Unify line measures
Browse files Browse the repository at this point in the history
All of these algorithm implementations already existed, but I'm
proposing a new way to organize them which I hope will be more
consistent and discoverable.

Note: The new Haversine::bearing and Geodesic::bearing return 0..360,
the legacy traits HaversineBearing and GeodesicBearing returned
-180..180

Additional changes:

Deleted the deprecated `Bearing` trait which was previously superceeded
by the unambiguous `HaversineBearing` trait, but now is re-defined as
`Haversine::bearing`

= Future Work =

In an effort to minimize this PR, while keeping the change reasonably
coherent, I've left some things out

== Methods on Euclidean ==
 -[ ] bearing (doesn't currently exist)
 -[ ] destination (doesn't currently exist)
 -[ ] intermediate (exists, but InterpolatePoint trait also needs intermediate_fill)
 -[ ] intermediate_fill (doesn't currently exist)

== Deprecate Legacy Traits ==
 -[ ] Deprecate Legacy impls
 -[ ] Switcheroo the actual implementation: move the actual implementation to the new traits, and have the legacy traits delegate to the new traits.
 -[ ] Move over any tests from the legacy implementation to the new home

== Methods on Geoms (Future PR) ==
 -[ ] Length
     -[ ] Haversine
     -[ ] Rhumb
     -[ ] Geodesic
     -[ ] Euclidean
 -[ ] Densify
     -[ ] Haversine
     -[ ] Rhumb
     -[ ] Geodesic
     -[ ] Euclidean

FIXES #1210
FIXES #1181
  • Loading branch information
michaelkirk committed Sep 25, 2024
1 parent 68f80f8 commit a635f84
Show file tree
Hide file tree
Showing 15 changed files with 1,173 additions and 47 deletions.
33 changes: 0 additions & 33 deletions geo/src/algorithm/bearing.rs

This file was deleted.

2 changes: 0 additions & 2 deletions geo/src/algorithm/geodesic_intermediate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::{CoordFloat, Point};
use geographiclib_rs::{DirectGeodesic, Geodesic, InverseGeodesic};

/// Returns a new Point along a route between two existing points on an ellipsoidal model of the earth

pub trait GeodesicIntermediate<T: CoordFloat> {
/// Returns a new Point along a route between two existing points on an ellipsoidal model of the earth
///
Expand All @@ -25,7 +24,6 @@ pub trait GeodesicIntermediate<T: CoordFloat> {
/// assert_relative_eq!(i50, i50_should, epsilon = 1.0e-6);
/// assert_relative_eq!(i80, i80_should, epsilon = 1.0e-6);
/// ```

fn geodesic_intermediate(&self, other: &Point<T>, f: T) -> Point<T>;
fn geodesic_intermediate_fill(
&self,
Expand Down
13 changes: 13 additions & 0 deletions geo/src/algorithm/line_measures/bearing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use geo_types::{CoordFloat, Point};

/// Calculate the bearing between two points
pub trait Bearing<F: CoordFloat> {
/// Calculate the bearing from `origin` to `destination` in degrees.
///
/// See [specific implementations](#implementors) for details.
///
/// # Units
/// - `origin`, `destination`: Point where the units of x/y depend on the [trait implementation](#implementors).
/// - returns: degrees, where: North: 0°, East: 90°, South: 180°, West: 270°
fn bearing(origin: Point<F>, destination: Point<F>) -> F;
}
19 changes: 19 additions & 0 deletions geo/src/algorithm/line_measures/destination.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use geo_types::{CoordFloat, Point};

/// Calculate the destination point from an origin point, a bearing and a distance.
pub trait Destination<F: CoordFloat> {
/// Returns a new point having travelled the `distance` along a line
/// from the `origin` point with the given `bearing`.
///
/// See [specific implementations](#implementors) for details.
///
/// # Units
///
/// - `origin`: Point where the units of x/y depend on the [trait implementation](#implementors).
/// - `bearing`: degrees, where: North: 0°, East: 90°, South: 180°, West: 270°
/// - `distance`: depends on the [trait implementation](#implementors).
/// - returns: Point where the units of x/y depend on the [trait implementation](#implementors).
///
/// [`metric_spaces`]: super::metric_spaces
fn destination(origin: Point<F>, bearing: F, distance: F) -> Point<F>;
}
12 changes: 12 additions & 0 deletions geo/src/algorithm/line_measures/distance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// Calculate the distance between the `Origin` and `Destination` geometry.
pub trait Distance<F, Origin, Destination> {
/// Note that not all implementations support all geometry combinations, but at least `Point` to `Point`
/// is supported.
/// See [specific implementations](#implementors) for details.
///
/// # Units
///
/// - `origin`, `destination`: geometry where the units of x/y depend on the trait implementation.
/// - returns: depends on the trait implementation.
fn distance(origin: Origin, destination: Destination) -> F;
}
34 changes: 34 additions & 0 deletions geo/src/algorithm/line_measures/interpolate_point.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crate::{CoordFloat, Point};

// REVIEW: Naming alternatives:
// - LinearReferencing
// - PointAlongLine
// - LineInterpolatePoint (postgis)
// - Interpolate (shapely)
// - Position (geographiclib)
// - Intermediate (georust::geo)
pub trait InterpolatePoint<F: CoordFloat> {
/// Returns a new Point along a line between two existing points
///
/// See [specific implementations](#implementors) for details.
fn point_at_ratio_between(start: Point<F>, end: Point<F>, ratio_from_start: F) -> Point<F>;

// TODO:
// fn point_at_distance_between(start: Point<F>, end: Point<F>, distance_from_start: F) -> Point<F>;

/// Interpolates `Point`s along a line between `start` and `end`.
///
/// See [specific implementations](#implementors) for details.
///
/// As many points as necessary will be added such that the distance between points
/// never exceeds `max_distance`. If the distance between start and end is less than
/// `max_distance`, no additional points will be included in the output.
///
/// `include_ends`: Should the start and end points be included in the output?
fn points_along_line(
start: Point<F>,
end: Point<F>,
max_distance: F,
include_ends: bool,
) -> impl Iterator<Item = Point<F>>;
}
106 changes: 106 additions & 0 deletions geo/src/algorithm/line_measures/metric_spaces/euclidean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use super::super::Distance;
use crate::{GeoFloat, Point};

/// Euclidean space measures distance with the pythagorean formula - what you'd measure with a ruler.
///
/// You must use projected coordinates with Euclidean space —
/// for lon/lat points, use the [`Haversine`], [`Geodesic`], or other [metric spaces].
///
/// [`Haversine`]: super::Haversine
/// [`Geodesic`]: super::Geodesic
/// [metric spaces]: super
pub struct Euclidean;

/// Calculate the Euclidean distance (a.k.a. pythagorean distance) between two Points
impl<F: GeoFloat> Distance<F, Point<F>, Point<F>> for Euclidean {
/// Calculate the Euclidean distance (a.k.a. pythagorean distance) between two Points
///
/// # Units
/// - `origin`, `destination`: Point where the units of x/y represent non-angular units
/// — e.g. meters or miles, not lon/lat. For lon/lat points, use the
/// [`Haversine`] or [`Geodesic`] [metric spaces].
/// - returns: distance in the same units as the `origin` and `destination` points
///
/// # Example
/// ```
/// use geo::{Euclidean, Distance};
/// use geo::Point;
/// // web mercator
/// let new_york_city = Point::new(-8238310.24, 4942194.78);
/// // web mercator
/// let london = Point::new(-14226.63, 6678077.70);
/// let distance: f64 = Euclidean::distance(new_york_city, london);
///
/// assert_eq!(
/// 8_405_286., // meters in web mercator
/// distance.round()
/// );
/// ```
///
/// [`Haversine`]: super::Haversine
/// [`Geodesic`]: super::Geodesic
/// [metric spaces]: super
fn distance(origin: Point<F>, destination: Point<F>) -> F {
crate::EuclideanDistance::euclidean_distance(&origin, &destination)
}
}

#[cfg(test)]
mod tests {
use super::*;

type MetricSpace = Euclidean;

mod distance {
use super::*;

#[test]
fn new_york_to_london() {
// web mercator
let new_york_city = Point::new(-8238310.24, 4942194.78);
// web mercator
let london = Point::new(-14226.63, 6678077.70);
let distance: f64 = MetricSpace::distance(new_york_city, london);

assert_relative_eq!(
8_405_286., // meters in web mercator
distance.round()
);
}
}
/*
mod interpolate_point {
use super::*;
#[test]
fn point_at_ratio_between_midpoint() {
let start = Point::new(10.0, 20.0);
let end = Point::new(125.0, 25.0);
let midpoint = MetricSpace::point_at_ratio_between(start, end, 0.5);
assert_relative_eq!(midpoint, Point::new(65.87394172511485, 37.61809316888599));
}
#[test]
fn points_along_line_with_endpoints() {
let start = Point::new(10.0, 20.0);
let end = Point::new(125.0, 25.0);
let max_dist = 1000000.0; // meters
let route =
MetricSpace::points_along_line(start, end, max_dist, true).collect::<Vec<_>>();
assert_eq!(route.len(), 13);
assert_eq!(route[0], start);
assert_eq!(route.last().unwrap(), &end);
assert_relative_eq!(route[1], Point::new(17.882467331860965, 24.435542998803793));
}
#[test]
fn points_along_line_without_endpoints() {
let start = Point::new(10.0, 20.0);
let end = Point::new(125.0, 25.0);
let max_dist = 1000000.0; // meters
let route =
MetricSpace::points_along_line(start, end, max_dist, false).collect::<Vec<_>>();
assert_eq!(route.len(), 11);
assert_relative_eq!(route[0], Point::new(17.882467331860965, 24.435542998803793));
}
}
*/
}
Loading

0 comments on commit a635f84

Please sign in to comment.