Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

line measure docs #1239

Merged
merged 3 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions geo/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
line_string.densify::<Geodesic>();
line_string.densify::<Rhumb>();
```
* Added `InterpolatePoint::point_at_distance_between` for line_measures.
* <https://github.com/georust/geo/pull/1235>
* Change IntersectionMatrix::is_equal_topo to now consider empty geometries as equal.
* <https://github.com/georust/geo/pull/1223>
* Fix `(LINESTRING EMPTY).contains(LINESTRING EMPTY)` and `(MULTIPOLYGON EMPTY).contains(MULTIPOINT EMPTY)` which previously
Expand Down
2 changes: 1 addition & 1 deletion geo/src/algorithm/line_measures/bearing.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use geo_types::{CoordFloat, Point};

/// Calculate the bearing between two points
/// Calculate the bearing between two points.
pub trait Bearing<F: CoordFloat> {
/// Calculate the bearing from `origin` to `destination` in degrees.
///
Expand Down
2 changes: 1 addition & 1 deletion geo/src/algorithm/line_measures/destination.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use geo_types::{CoordFloat, Point};

/// Calculate the destination point from an origin point, a bearing and a distance.
/// Calculate the destination point from an origin point, given 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`.
Expand Down
2 changes: 1 addition & 1 deletion geo/src/algorithm/line_measures/distance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// Calculate the distance between the `Origin` and `Destination` geometry.
/// Calculate the minimum distance between two geometries.
pub trait Distance<F, Origin, Destination> {
/// Note that not all implementations support all geometry combinations, but at least `Point` to `Point`
/// is supported.
Expand Down
14 changes: 10 additions & 4 deletions geo/src/algorithm/line_measures/interpolate_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ use crate::{CoordFloat, Point};

/// Interpolate a `Point` along a line between two existing points
pub trait InterpolatePoint<F: CoordFloat> {
/// Returns a new Point along a line between two existing points
/// 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>;
fn point_at_distance_between(
start: Point<F>,
end: Point<F>,
distance_from_start: F,
) -> Point<F>;

// TODO:
// fn point_at_distance_between(start: Point<F>, end: Point<F>, distance_from_start: F) -> Point<F>;
/// 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>;

/// Interpolates `Point`s along a line between `start` and `end`.
///
Expand Down
72 changes: 72 additions & 0 deletions geo/src/algorithm/line_measures/metric_spaces/euclidean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,68 @@ use num_traits::FromPrimitive;
/// [metric spaces]: super
pub struct Euclidean;

/// Interpolate Point(s) along a line on the [Euclidean plane].
///
/// [Euclidean plane]: https://en.wikipedia.org/wiki/Euclidean_plane
impl<F: CoordFloat + FromPrimitive> InterpolatePoint<F> for Euclidean {
/// Returns the point at the given distance along the line between `start` and `end`.
///
/// # Units
/// - `distance`: Measured in whatever units your `start` and `end` points use.
///
/// `distance` and your `start` and `end` points should have non-agular
/// units, like meters or miles, **not** lon/lat.
/// For lon/lat points, use the [`Haversine`] or [`Geodesic`] [metric spaces].
///
/// [`Haversine`]: crate::line_measures::Haversine
/// [`Geodesic`]: crate::line_measures::Geodesic
/// [metric spaces]: crate::line_measures::metric_spaces
fn point_at_distance_between(
start: Point<F>,
end: Point<F>,
distance_from_start: F,
) -> Point<F> {
let diff = end - start;
let total_distance = diff.x().hypot(diff.y());
let offset = diff * distance_from_start / total_distance;
start + offset
}

/// Returns the point at the given ratio along the line between `start` and `end`.
///
/// # Units
/// - `distance`: Measured in whatever units your `start` and `end` points use.
///
/// `distance` and your `start` and `end` points should have non-agular
michaelkirk marked this conversation as resolved.
Show resolved Hide resolved
/// units, like meters or miles, **not** lon/lat.
/// For lon/lat points, use the [`Haversine`] or [`Geodesic`] [metric spaces].
///
/// [`Haversine`]: crate::line_measures::Haversine
/// [`Geodesic`]: crate::line_measures::Geodesic
/// [metric spaces]: crate::line_measures::metric_spaces
fn point_at_ratio_between(start: Point<F>, end: Point<F>, ratio_from_start: F) -> Point<F> {
let diff = end - start;
start + diff * ratio_from_start
}

/// Interpolates `Point`s along a line between `start` and `end`.
///
/// 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?
///
/// # Units
/// - `max_distance`: Measured in whatever units your `start` and `end` points use.
///
/// `max_distance` and your `start` and `end` points should have non-agular
/// units, like meters or miles, **not** lon/lat.
/// For lon/lat points, use the [`Haversine`] or [`Geodesic`] [metric spaces].
///
/// [`Haversine`]: crate::line_measures::Haversine
/// [`Geodesic`]: crate::line_measures::Geodesic
/// [metric spaces]: crate::line_measures::metric_spaces
fn points_along_line(
start: Point<F>,
end: Point<F>,
Expand Down Expand Up @@ -68,5 +124,21 @@ mod tests {
distance.round()
);
}

#[test]
fn test_point_at_distance_between() {
let new_york_city = Point::new(-8_238_310.24, 4_942_194.78);
// web mercator
let london = Point::new(-14_226.63, 6_678_077.70);
let start = MetricSpace::point_at_distance_between(new_york_city, london, 0.0);
assert_relative_eq!(new_york_city, start);

let midway =
MetricSpace::point_at_distance_between(new_york_city, london, 8_405_286.0 / 2.0);
assert_relative_eq!(Point::new(-4_126_268., 5_810_136.), midway, epsilon = 1.0);

let end = MetricSpace::point_at_distance_between(new_york_city, london, 8_405_286.0);
assert_relative_eq!(london, end, epsilon = 1.0);
}
}
}
44 changes: 41 additions & 3 deletions geo/src/algorithm/line_measures/metric_spaces/geodesic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,44 @@ impl Distance<f64, Point<f64>, Point<f64>> for Geodesic {
///
/// [geodesic line]: https://en.wikipedia.org/wiki/Geodesics_on_an_ellipsoid
impl InterpolatePoint<f64> for Geodesic {
/// Returns a new Point along a [geodesic line] between two existing points on an ellipsoidal model of the earth.
///
/// # Units
/// `meters_from_start`: meters
michaelkirk marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Examples
///
/// ```
/// # use approx::assert_relative_eq;
/// use geo::{Geodesic, InterpolatePoint};
/// use geo::Point;
///
///
/// let p1 = Point::new(10.0, 20.0);
/// let p2 = Point::new(125.0, 25.0);
///
/// let closer_to_p1 = Geodesic::point_at_distance_between(p1, p2, 100_000.0);
/// assert_relative_eq!(closer_to_p1, Point::new(10.81, 20.49), epsilon = 1.0e-2);
///
/// let closer_to_p2 = Geodesic::point_at_distance_between(p1, p2, 10_000_000.0);
/// assert_relative_eq!(closer_to_p2, Point::new(112.20, 30.67), epsilon = 1.0e-2);
/// ```
///
/// # References
///
/// This uses the geodesic methods given by [Karney (2013)].
///
/// [geodesic line]: https://en.wikipedia.org/wiki/Geodesics_on_an_ellipsoid
/// [Karney (2013)]: https://arxiv.org/pdf/1109.4448.pdf
fn point_at_distance_between(
start: Point<f64>,
end: Point<f64>,
meters_from_start: f64,
) -> Point<f64> {
let bearing = Self::bearing(start, end);
Self::destination(start, bearing, meters_from_start)
}

/// Returns a new Point along a [geodesic line] between two existing points on an ellipsoidal model of the earth.
///
/// # Examples
Expand Down Expand Up @@ -170,9 +208,7 @@ impl InterpolatePoint<f64> for Geodesic {
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;
let (lat2, lon2) = g.direct(start.y(), start.x(), azi1, distance);

Point::new(lon2, lat2)
Self::destination(start, azi1, distance)
}

/// Interpolates `Point`s along a [geodesic line] between `start` and `end`.
Expand Down Expand Up @@ -335,6 +371,7 @@ mod tests {
let midpoint = MetricSpace::point_at_ratio_between(start, end, 0.5);
assert_relative_eq!(midpoint, Point::new(65.87936072133309, 37.72225378005785));
}

#[test]
fn points_along_line_with_endpoints() {
let start = Point::new(10.0, 20.0);
Expand All @@ -347,6 +384,7 @@ mod tests {
assert_eq!(route.last().unwrap(), &end);
assert_relative_eq!(route[1], Point::new(17.878754355562464, 24.466667836189565));
}

#[test]
fn points_along_line_without_endpoints() {
let start = Point::new(10.0, 20.0);
Expand Down
29 changes: 27 additions & 2 deletions geo/src/algorithm/line_measures/metric_spaces/haversine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ impl<F: CoordFloat + FromPrimitive> Destination<F> for Haversine {
/// the IUGG](ftp://athena.fsv.cvut.cz/ZFG/grs80-Moritz.pdf)
///
/// [great circle]: https://en.wikipedia.org/wiki/Great_circle
fn destination(origin: Point<F>, bearing: F, distance: F) -> Point<F> {
fn destination(origin: Point<F>, bearing: F, meters: F) -> Point<F> {
let center_lng = origin.x().to_radians();
let center_lat = origin.y().to_radians();
let bearing_rad = bearing.to_radians();

let rad = distance / F::from(MEAN_EARTH_RADIUS).unwrap();
let rad = meters / F::from(MEAN_EARTH_RADIUS).unwrap();

let lat =
{ center_lat.sin() * rad.cos() + center_lat.cos() * rad.sin() * bearing_rad.cos() }
Expand Down Expand Up @@ -155,6 +155,31 @@ impl<F: CoordFloat + FromPrimitive> Distance<F, Point<F>, Point<F>> for Haversin
///
/// [great circle]: https://en.wikipedia.org/wiki/Great_circle
impl<F: CoordFloat + FromPrimitive> InterpolatePoint<F> for Haversine {
/// Returns a new Point along a [great circle] between two existing points.
///
/// # Examples
///
/// ```
/// # use approx::assert_relative_eq;
/// use geo::{Haversine, InterpolatePoint};
/// use geo::Point;
///
/// let p1 = Point::new(10.0, 20.0);
/// let p2 = Point::new(125.0, 25.0);
///
/// let closer_to_p1 = Haversine::point_at_distance_between(p1, p2, 100_000.0);
/// assert_relative_eq!(closer_to_p1, Point::new(10.81, 20.49), epsilon = 1.0e-2);
///
/// let closer_to_p2 = Haversine::point_at_distance_between(p1, p2, 10_000_000.0);
/// assert_relative_eq!(closer_to_p2, Point::new(112.33, 30.57), epsilon = 1.0e-2);
/// ```
///
/// [great circle]: https://en.wikipedia.org/wiki/Great_circle
fn point_at_distance_between(start: Point<F>, end: Point<F>, meters_from_start: F) -> Point<F> {
let bearing = Self::bearing(start, end);
Self::destination(start, bearing, meters_from_start)
}

/// Returns a new Point along a [great circle] between two existing points.
///
/// # Examples
Expand Down
25 changes: 25 additions & 0 deletions geo/src/algorithm/line_measures/metric_spaces/rhumb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,31 @@ impl<F: CoordFloat + FromPrimitive> Distance<F, Point<F>, Point<F>> for Rhumb {
///
/// [rhumb line]: https://en.wikipedia.org/wiki/Rhumb_line
impl<F: CoordFloat + FromPrimitive> InterpolatePoint<F> for Rhumb {
/// Returns a new Point along a [rhumb line] between two existing points.
///
/// # Examples
///
/// ```
/// # use approx::assert_relative_eq;
/// use geo::{Rhumb, InterpolatePoint};
/// use geo::Point;
///
/// let p1 = Point::new(10.0, 20.0);
/// let p2 = Point::new(125.0, 25.0);
///
/// let closer_to_p1 = Rhumb::point_at_distance_between(p1, p2, 100_000.0);
/// assert_relative_eq!(closer_to_p1, Point::new(10.96, 20.04), epsilon = 1.0e-2);
///
/// let closer_to_p2 = Rhumb::point_at_distance_between(p1, p2, 10_000_000.0);
/// assert_relative_eq!(closer_to_p2, Point::new(107.00, 24.23), epsilon = 1.0e-2);
/// ```
///
/// [rhumb line]: https://en.wikipedia.org/wiki/Rhumb_line
fn point_at_distance_between(start: Point<F>, end: Point<F>, meters_from_start: F) -> Point<F> {
let bearing = Self::bearing(start, end);
Self::destination(start, bearing, meters_from_start)
}

/// Returns a new Point along a [rhumb line] between two existing points.
///
/// # Examples
Expand Down
Loading
Loading