use crate::{
CoordFloat, Line, LineString, Point,
{euclidean_distance::EuclideanDistance, euclidean_length::EuclideanLength},
};
use std::ops::AddAssign;
pub trait LineLocatePoint<T, Rhs> {
type Output;
type Rhs;
fn line_locate_point(&self, p: &Rhs) -> Self::Output;
}
impl<T> LineLocatePoint<T, Point<T>> for Line<T>
where
T: CoordFloat,
{
type Output = Option<T>;
type Rhs = Point<T>;
fn line_locate_point(&self, p: &Self::Rhs) -> Self::Output {
let sp: Point<_> = *p - self.start_point();
let v: Point<_> = (self.end - self.start).into();
let v_sq = v.dot(v);
if v_sq == T::zero() {
Some(T::zero())
} else {
let v_dot_sp = v.dot(sp);
let l = v_dot_sp / v_sq;
if l.is_finite() {
Some(l.max(T::zero()).min(T::one()))
} else {
None
}
}
}
}
impl<T> LineLocatePoint<T, Point<T>> for LineString<T>
where
T: CoordFloat + AddAssign,
Line<T>: EuclideanDistance<T, Point<T>> + EuclideanLength<T>,
LineString<T>: EuclideanLength<T>,
{
type Output = Option<T>;
type Rhs = Point<T>;
fn line_locate_point(&self, p: &Self::Rhs) -> Self::Output {
let total_length = (*self).euclidean_length();
if total_length == T::zero() {
return Some(T::zero());
}
let mut cum_length = T::zero();
let mut closest_dist_to_point = T::infinity();
let mut fraction = T::zero();
for segment in self.lines() {
let segment_distance_to_point = segment.euclidean_distance(p);
let segment_length = segment.euclidean_length();
let segment_fraction = segment.line_locate_point(p)?; if segment_distance_to_point < closest_dist_to_point {
closest_dist_to_point = segment_distance_to_point;
fraction = (cum_length + segment_fraction * segment_length) / total_length;
}
cum_length += segment_length;
}
Some(fraction)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::geo_types::coord;
use crate::point;
use num_traits::Float;
#[test]
fn test_line_locate_point_line() {
let line = Line::new(coord! { x: -1.0, y: 0.0 }, coord! { x: 1.0, y: 0.0 });
let point = Point::new(0.0, 1.0);
assert_eq!(line.line_locate_point(&point), Some(0.5));
let point = Point::new(1.0, 1.0);
assert_eq!(line.line_locate_point(&point), Some(1.0));
let point = Point::new(2.0, 1.0);
assert_eq!(line.line_locate_point(&point), Some(1.0));
let point = Point::new(-1.0, 1.0);
assert_eq!(line.line_locate_point(&point), Some(0.0));
let point = Point::new(-2.0, 1.0);
assert_eq!(line.line_locate_point(&point), Some(0.0));
let point = Point::new(Float::nan(), 1.0);
assert_eq!(line.line_locate_point(&point), None);
let point = Point::new(Float::infinity(), 1.0);
assert_eq!(line.line_locate_point(&point), None);
let point = Point::new(Float::neg_infinity(), 1.0);
assert_eq!(line.line_locate_point(&point), None);
let line = Line::new(
coord! { x: 0.0, y: 0.0 },
coord! {
x: Float::infinity(),
y: 0.0,
},
);
let point = Point::new(1000.0, 1000.0);
assert_eq!(line.line_locate_point(&point), None);
let line = Line::new(
coord! { x: 0.0, y: 0.0 },
coord! {
x: Float::neg_infinity(),
y: 0.0,
},
);
let point = Point::new(1000.0, 1000.0);
assert_eq!(line.line_locate_point(&point), None);
let line = Line::new(
coord! { x: 0.0, y: 0.0 },
coord! {
x: Float::nan(),
y: 0.0,
},
);
let point = Point::new(1000.0, 1000.0);
assert_eq!(line.line_locate_point(&point), None);
let line: Line = Line::new(coord! { x: 1.0, y: 1.0 }, coord! { x: 1.0, y: 1.0 });
let pt = point!(x: 2.0, y: 2.0);
assert_eq!(line.line_locate_point(&pt), Some(0.0));
let line: Line = Line::new(coord! { x: 0.0, y: 0.0 }, coord! { x: 10.0, y: 0.0 });
let pt = Point::new(555.0, 555.0);
assert_eq!(line.line_locate_point(&pt), Some(1.0));
let pt = Point::new(10.0000001, 0.0);
assert_eq!(line.line_locate_point(&pt), Some(1.0));
let pt = Point::new(9.0, 0.001);
assert_eq!(line.line_locate_point(&pt), Some(0.9));
}
#[test]
fn test_line_locate_point_linestring() {
let ring: LineString = geo_test_fixtures::ring::<f64>();
let pt = point!(x: 10.0, y: 1.0);
assert_eq!(ring.line_locate_point(&pt), Some(0.0));
let pt = point!(x: 10.0, y: 1.0000000000000742);
assert_eq!(ring.line_locate_point(&pt), Some(0.9999999999999988));
let pt = point!(x: 10.0, y: 1.0);
assert_eq!(ring.line_locate_point(&pt), Some(0.0));
let pt = point!(x: Float::nan(), y: 1.0);
assert_eq!(ring.line_locate_point(&pt), None);
let pt = point!(x: Float::infinity(), y: 1.0);
assert_eq!(ring.line_locate_point(&pt), None);
let pt = point!(x: Float::neg_infinity(), y: 1.0);
assert_eq!(ring.line_locate_point(&pt), None);
let line: LineString = LineString::new(vec![
(0.0, 0.0).into(),
(1.0, 0.0).into(),
(1.0, 1.0).into(),
(0.0, 1.0).into(),
]);
let pt = point!(x: 0.0, y: 0.5);
assert_eq!(line.line_locate_point(&pt), Some(0.0));
let line: LineString = LineString::new(vec![
(1.0, 1.0).into(),
(1.0, 1.0).into(),
(1.0, 1.0).into(),
]);
let pt = point!(x: 2.0, y: 2.0);
assert_eq!(line.line_locate_point(&pt), Some(0.0));
let line: LineString = LineString::new(vec![
coord! { x: 1.0, y: 1.0 },
coord! {
x: Float::nan(),
y: 1.0,
},
coord! { x: 0.0, y: 0.0 },
]);
let pt = point!(x: 2.0, y: 2.0);
assert_eq!(line.line_locate_point(&pt), None);
let line: LineString = LineString::new(vec![
coord! { x: 1.0, y: 1.0 },
coord! {
x: Float::infinity(),
y: 1.0,
},
coord! { x: 0.0, y: 0.0 },
]);
let pt = point!(x: 2.0, y: 2.0);
assert_eq!(line.line_locate_point(&pt), None);
let line: LineString = LineString::new(vec![
coord! { x: 1.0, y: 1.0 },
coord! {
x: Float::neg_infinity(),
y: 1.0,
},
coord! { x: 0.0, y: 0.0 },
]);
let pt = point!(x: 2.0, y: 2.0);
assert_eq!(line.line_locate_point(&pt), None);
}
}