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

Add Haversine:distance for Line vs. Point (same as CrossTrackDistance) #1243

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
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

* Add `Haversine:distance` for Line vs. Point (same as CrossTrackDistance)

## 0.29.0 - 2024.10.30

Expand Down
28 changes: 12 additions & 16 deletions geo/src/algorithm/cross_track_distance.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use crate::{Bearing, Distance, Haversine, MEAN_EARTH_RADIUS};
use geo_types::{CoordFloat, Point};
use crate::Distance;
use geo_types::{CoordFloat, Line, Point};
use num_traits::FromPrimitive;

/// Determine the cross track distance (also known as the cross track error) which is the shortest
/// distance between a point and a continuous line.
pub trait CrossTrackDistance<T, Rhs = Self> {
///
/// Distance is measured in meters, using the Haversine formula.
pub trait CrossTrackDistance<T, Rhs = Self>
where
T: CoordFloat + FromPrimitive,
{
/// Determine the cross track distance between this point and a line
/// which passes through line_point_a and line_point_b
///
Expand Down Expand Up @@ -42,20 +47,14 @@ where
T: CoordFloat + FromPrimitive,
{
fn cross_track_distance(&self, line_point_a: &Point<T>, line_point_b: &Point<T>) -> T {
let mean_earth_radius = T::from(MEAN_EARTH_RADIUS).unwrap();
let l_delta_13: T = Haversine::distance(*line_point_a, *self) / mean_earth_radius;
let theta_13: T = Haversine::bearing(*line_point_a, *self).to_radians();
let theta_12: T = Haversine::bearing(*line_point_a, *line_point_b).to_radians();
let l_delta_xt: T = (l_delta_13.sin() * (theta_12 - theta_13).sin()).asin();
mean_earth_radius * l_delta_xt.abs()
crate::Haversine::distance(&Line::new(*line_point_a, *line_point_b), self)
}
}

#[cfg(test)]
mod test {
use crate::CrossTrackDistance;
use crate::Point;
use crate::{Distance, Haversine};
use crate::{CrossTrackDistance, Distance, Haversine};

#[test]
fn distance1_test() {
Expand Down Expand Up @@ -107,10 +106,7 @@ mod test {
let line_point_a = Point::new(-80.1918f64, 25.7617f64);
let line_point_b = Point::new(-120.7401f64, 47.7511f64);

assert_relative_eq!(
p1.cross_track_distance(&line_point_a, &line_point_b),
1_547_104.,
epsilon = 1.0
);
let haversine_distance = p1.cross_track_distance(&line_point_a, &line_point_b);
assert_relative_eq!(haversine_distance, 1_547_104., epsilon = 1.0);
}
}
15 changes: 15 additions & 0 deletions geo/src/algorithm/line_measures/distance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,18 @@ pub trait Distance<F, Origin, Destination> {
/// - returns: depends on the trait implementation.
fn distance(origin: Origin, destination: Destination) -> F;
}

// Distance is a symmetric operation, so we can implement it once for both
macro_rules! symmetric_distance_impl {
($a:ty, $b:ty, for: $metric_space: ty, where: $($bound:tt)+) => {
impl<F> $crate::Distance<F, $a, $b> for $metric_space
where
F: $($bound)+,
{
fn distance(a: $a, b: $b) -> F {
Self::distance(b, a)
}
}
};
}
pub(crate) use symmetric_distance_impl;
35 changes: 11 additions & 24 deletions geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,12 @@ use super::{Distance, Euclidean};
use crate::algorithm::Intersects;
use crate::coordinate_position::{coord_pos_relative_to_ring, CoordPos};
use crate::geometry::*;
use crate::line_measures::distance::symmetric_distance_impl;
use crate::{CoordFloat, GeoFloat, GeoNum};
use num_traits::{Bounded, Float};
use rstar::primitives::CachedEnvelope;
use rstar::RTree;

// Distance is a symmetric operation, so we can implement it once for both
macro_rules! symmetric_distance_impl {
($t:ident, $a:ty, $b:ty) => {
impl<F> $crate::Distance<F, $a, $b> for Euclidean
where
F: $t,
{
fn distance(a: $a, b: $b) -> F {
Self::distance(b, a)
}
}
};
}

// ┌───────────────────────────┐
// │ Implementations for Coord │
// └───────────────────────────┘
Expand Down Expand Up @@ -124,8 +111,8 @@ impl<F: GeoFloat> Distance<F, &Point<F>, &Polygon<F>> for Euclidean {
// │ Implementations for Line │
// └──────────────────────────┘

symmetric_distance_impl!(CoordFloat, &Line<F>, Coord<F>);
symmetric_distance_impl!(CoordFloat, &Line<F>, &Point<F>);
symmetric_distance_impl!(&Line<F>, Coord<F>, for: Euclidean, where: CoordFloat);
symmetric_distance_impl!(&Line<F>, &Point<F>, for: Euclidean, where: CoordFloat);

impl<F: GeoFloat> Distance<F, &Line<F>, &Line<F>> for Euclidean {
fn distance(line_a: &Line<F>, line_b: &Line<F>) -> F {
Expand Down Expand Up @@ -169,8 +156,8 @@ impl<F: GeoFloat> Distance<F, &Line<F>, &Polygon<F>> for Euclidean {
// │ Implementations for LineString │
// └────────────────────────────────┘

symmetric_distance_impl!(CoordFloat, &LineString<F>, &Point<F>);
symmetric_distance_impl!(GeoFloat, &LineString<F>, &Line<F>);
symmetric_distance_impl!(&LineString<F>, &Point<F>, for: Euclidean, where: CoordFloat);
symmetric_distance_impl!(&LineString<F>, &Line<F>, for: Euclidean, where: GeoFloat);

impl<F: GeoFloat> Distance<F, &LineString<F>, &LineString<F>> for Euclidean {
fn distance(line_string_a: &LineString<F>, line_string_b: &LineString<F>) -> F {
Expand Down Expand Up @@ -206,9 +193,9 @@ impl<F: GeoFloat> Distance<F, &LineString<F>, &Polygon<F>> for Euclidean {
// │ Implementations for Polygon │
// └─────────────────────────────┘

symmetric_distance_impl!(GeoFloat, &Polygon<F>, &Point<F>);
symmetric_distance_impl!(GeoFloat, &Polygon<F>, &Line<F>);
symmetric_distance_impl!(GeoFloat, &Polygon<F>, &LineString<F>);
symmetric_distance_impl!(&Polygon<F>, &Point<F>, for: Euclidean, where: GeoFloat);
symmetric_distance_impl!(&Polygon<F>, &Line<F>, for: Euclidean, where: GeoFloat);
symmetric_distance_impl!(&Polygon<F>, &LineString<F>, for: Euclidean, where: GeoFloat);
impl<F: GeoFloat> Distance<F, &Polygon<F>, &Polygon<F>> for Euclidean {
fn distance(polygon_a: &Polygon<F>, polygon_b: &Polygon<F>) -> F {
if polygon_a.intersects(polygon_b) {
Expand Down Expand Up @@ -259,7 +246,7 @@ macro_rules! impl_euclidean_distance_for_polygonlike_geometry {
Self::distance(&polygonlike.to_polygon(), geometry_b)
}
}
symmetric_distance_impl!(GeoFloat, $geometry_b, $polygonlike);
symmetric_distance_impl!($geometry_b, $polygonlike, for: Euclidean, where: GeoFloat);
)*
};
}
Expand Down Expand Up @@ -293,7 +280,7 @@ macro_rules! impl_euclidean_distance_for_iter_geometry {
})
}
}
symmetric_distance_impl!(GeoFloat, $to_geometry, $iter_geometry);
symmetric_distance_impl!($to_geometry, $iter_geometry, for: Euclidean, where: GeoFloat);
)*
};
}
Expand Down Expand Up @@ -327,7 +314,7 @@ macro_rules! impl_euclidean_distance_for_geometry_and_variant {
}
}
}
symmetric_distance_impl!(GeoFloat, &Geometry<F>, $target);
symmetric_distance_impl!(&Geometry<F>, $target, for: Euclidean, where: GeoFloat);
)*
};
}
Expand Down
15 changes: 14 additions & 1 deletion geo/src/algorithm/line_measures/metric_spaces/haversine.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use num_traits::FromPrimitive;

use super::super::{Bearing, Destination, Distance, InterpolatePoint};
use crate::line_measures::distance::symmetric_distance_impl;
use crate::utils::normalize_longitude;
use crate::{CoordFloat, Point, MEAN_EARTH_RADIUS};
use crate::{CoordFloat, Line, Point, MEAN_EARTH_RADIUS};

/// A spherical model of the earth using the [haversine formula].
///
Expand Down Expand Up @@ -151,6 +152,18 @@ impl<F: CoordFloat + FromPrimitive> Distance<F, Point<F>, Point<F>> for Haversin
}
}

impl<F: CoordFloat + FromPrimitive> Distance<F, &Line<F>, &Point<F>> for Haversine {
fn distance(line: &Line<F>, point: &Point<F>) -> F {
let mean_earth_radius = F::from(MEAN_EARTH_RADIUS).unwrap();
let l_delta_13 = Haversine::distance(line.start_point(), *point) / mean_earth_radius;
let theta_13 = Haversine::bearing(line.start_point(), *point).to_radians();
let theta_12 = Haversine::bearing(line.start_point(), line.end_point()).to_radians();
let l_delta_xt = (l_delta_13.sin() * (theta_12 - theta_13).sin()).asin();
mean_earth_radius * l_delta_xt.abs()
}
}
symmetric_distance_impl!(&Point<F>, &Line<F>, for: Haversine, where: CoordFloat + FromPrimitive);

/// Interpolate Point(s) along a [great circle].
///
/// [great circle]: https://en.wikipedia.org/wiki/Great_circle
Expand Down
Loading