geo/algorithm/
line_locate_point.rs

1// This algorithm will be deprecated in the future, replaced by a unified implementation
2// rather than being Euclidean specific. Until the alternative is available, lets allow deprecations
3// so as not to change the method signature for existing users.
4#[allow(deprecated)]
5use crate::{
6    CoordFloat, Line, LineString, Point,
7    {euclidean_distance::EuclideanDistance, euclidean_length::EuclideanLength},
8};
9use std::ops::AddAssign;
10
11/// Returns a (option of the) fraction of the line's total length
12/// representing the location of the closest point on the line to
13/// the given point.
14///
15/// If the line has zero length the fraction returned is zero.
16///
17/// If either the point's coordinates or any coordinates of the line
18/// are not finite, returns `None`.
19///
20/// # Examples
21///
22/// ```
23/// use geo::{LineString, point};
24/// use geo::LineLocatePoint;
25///
26/// let linestring: LineString = vec![
27///     [-1.0, 0.0],
28///     [0.0, 0.0],
29///     [0.0, 1.0]
30/// ].into();
31///
32/// assert_eq!(linestring.line_locate_point(&point!(x: -1.0, y: 0.0)), Some(0.0));
33/// assert_eq!(linestring.line_locate_point(&point!(x: -0.5, y: 0.0)), Some(0.25));
34/// assert_eq!(linestring.line_locate_point(&point!(x: 0.0, y: 0.0)), Some(0.5));
35/// assert_eq!(linestring.line_locate_point(&point!(x: 0.0, y: 0.5)), Some(0.75));
36/// assert_eq!(linestring.line_locate_point(&point!(x: 0.0, y: 1.0)), Some(1.0));
37/// ```
38pub trait LineLocatePoint<T, Rhs> {
39    type Output;
40    type Rhs;
41
42    fn line_locate_point(&self, p: &Rhs) -> Self::Output;
43}
44
45impl<T> LineLocatePoint<T, Point<T>> for Line<T>
46where
47    T: CoordFloat,
48{
49    type Output = Option<T>;
50    type Rhs = Point<T>;
51
52    fn line_locate_point(&self, p: &Self::Rhs) -> Self::Output {
53        // let $s$ be the starting point of the line, and $v$ its
54        // direction vector. We want to find $l$ such that
55        // $(p - (s + lv)) \cdot v = 0$, i.e. the vector from
56        // $l$ along the line to $p$ is perpendicular to $v$.a
57
58        // vector $p - s$
59        let sp: Point<_> = *p - self.start_point();
60
61        // direction vector of line, $v$
62        let v: Point<_> = (self.end - self.start).into();
63
64        // $v \cdot v$
65        let v_sq = v.dot(v);
66        if v_sq == T::zero() {
67            // The line has zero length, return zero
68            Some(T::zero())
69        } else {
70            // $v \cdot (p - s)$
71            let v_dot_sp = v.dot(sp);
72            let l = v_dot_sp / v_sq;
73            if l.is_finite() {
74                Some(l.max(T::zero()).min(T::one()))
75            } else {
76                None
77            }
78        }
79    }
80}
81
82#[allow(deprecated)]
83impl<T> LineLocatePoint<T, Point<T>> for LineString<T>
84where
85    T: CoordFloat + AddAssign,
86    Line<T>: EuclideanDistance<T, Point<T>> + EuclideanLength<T>,
87    LineString<T>: EuclideanLength<T>,
88{
89    type Output = Option<T>;
90    type Rhs = Point<T>;
91
92    fn line_locate_point(&self, p: &Self::Rhs) -> Self::Output {
93        let total_length = (*self).euclidean_length();
94        if total_length == T::zero() {
95            return Some(T::zero());
96        }
97        let mut cum_length = T::zero();
98        let mut closest_dist_to_point = T::infinity();
99        let mut fraction = T::zero();
100        for segment in self.lines() {
101            let segment_distance_to_point = segment.euclidean_distance(p);
102            let segment_length = segment.euclidean_length();
103            let segment_fraction = segment.line_locate_point(p)?; // if any segment has a None fraction, return None
104            if segment_distance_to_point < closest_dist_to_point {
105                closest_dist_to_point = segment_distance_to_point;
106                fraction = (cum_length + segment_fraction * segment_length) / total_length;
107            }
108            cum_length += segment_length;
109        }
110        Some(fraction)
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use super::*;
117    use crate::{coord, point};
118    use num_traits::Float;
119
120    #[test]
121    fn test_line_locate_point_line() {
122        // Some finite examples
123        let line = Line::new(coord! { x: -1.0, y: 0.0 }, coord! { x: 1.0, y: 0.0 });
124        let point = Point::new(0.0, 1.0);
125        assert_eq!(line.line_locate_point(&point), Some(0.5));
126
127        let point = Point::new(1.0, 1.0);
128        assert_eq!(line.line_locate_point(&point), Some(1.0));
129
130        let point = Point::new(2.0, 1.0);
131        assert_eq!(line.line_locate_point(&point), Some(1.0));
132
133        let point = Point::new(-1.0, 1.0);
134        assert_eq!(line.line_locate_point(&point), Some(0.0));
135
136        let point = Point::new(-2.0, 1.0);
137        assert_eq!(line.line_locate_point(&point), Some(0.0));
138
139        // point contains inf or nan
140        let point = Point::new(Float::nan(), 1.0);
141        assert_eq!(line.line_locate_point(&point), None);
142
143        let point = Point::new(Float::infinity(), 1.0);
144        assert_eq!(line.line_locate_point(&point), None);
145
146        let point = Point::new(Float::neg_infinity(), 1.0);
147        assert_eq!(line.line_locate_point(&point), None);
148
149        // line contains inf or nan
150        let line = Line::new(
151            coord! { x: 0.0, y: 0.0 },
152            coord! {
153                x: Float::infinity(),
154                y: 0.0,
155            },
156        );
157        let point = Point::new(1000.0, 1000.0);
158        assert_eq!(line.line_locate_point(&point), None);
159
160        let line = Line::new(
161            coord! { x: 0.0, y: 0.0 },
162            coord! {
163                x: Float::neg_infinity(),
164                y: 0.0,
165            },
166        );
167        let point = Point::new(1000.0, 1000.0);
168        assert_eq!(line.line_locate_point(&point), None);
169
170        let line = Line::new(
171            coord! { x: 0.0, y: 0.0 },
172            coord! {
173                x: Float::nan(),
174                y: 0.0,
175            },
176        );
177        let point = Point::new(1000.0, 1000.0);
178        assert_eq!(line.line_locate_point(&point), None);
179
180        // zero length line
181        let line: Line = Line::new(coord! { x: 1.0, y: 1.0 }, coord! { x: 1.0, y: 1.0 });
182        let pt = point!(x: 2.0, y: 2.0);
183        assert_eq!(line.line_locate_point(&pt), Some(0.0));
184
185        // another concrete example
186        let line: Line = Line::new(coord! { x: 0.0, y: 0.0 }, coord! { x: 10.0, y: 0.0 });
187        let pt = Point::new(555.0, 555.0);
188        assert_eq!(line.line_locate_point(&pt), Some(1.0));
189        let pt = Point::new(10.0000001, 0.0);
190        assert_eq!(line.line_locate_point(&pt), Some(1.0));
191        let pt = Point::new(9.0, 0.001);
192        assert_eq!(line.line_locate_point(&pt), Some(0.9));
193    }
194
195    #[test]
196    fn test_line_locate_point_linestring() {
197        // finite example using the ring
198        let ring: LineString = geo_test_fixtures::ring::<f64>();
199        let pt = point!(x: 10.0, y: 1.0);
200        assert_eq!(ring.line_locate_point(&pt), Some(0.0));
201
202        let pt = point!(x: 10.0, y: 1.0000000000000742);
203        assert_eq!(ring.line_locate_point(&pt), Some(0.9999999999999988));
204
205        let pt = point!(x: 10.0, y: 1.0);
206        assert_eq!(ring.line_locate_point(&pt), Some(0.0));
207
208        // point contains inf or nan
209        let pt = point!(x: Float::nan(), y: 1.0);
210        assert_eq!(ring.line_locate_point(&pt), None);
211
212        let pt = point!(x: Float::infinity(), y: 1.0);
213        assert_eq!(ring.line_locate_point(&pt), None);
214
215        let pt = point!(x: Float::neg_infinity(), y: 1.0);
216        assert_eq!(ring.line_locate_point(&pt), None);
217
218        // point is equidistant to two line segments - return the fraction from the first closest
219        let line: LineString = LineString::new(vec![
220            (0.0, 0.0).into(),
221            (1.0, 0.0).into(),
222            (1.0, 1.0).into(),
223            (0.0, 1.0).into(),
224        ]);
225        let pt = point!(x: 0.0, y: 0.5);
226        assert_eq!(line.line_locate_point(&pt), Some(0.0));
227
228        let line: LineString = LineString::new(vec![
229            (1.0, 1.0).into(),
230            (1.0, 1.0).into(),
231            (1.0, 1.0).into(),
232        ]);
233        let pt = point!(x: 2.0, y: 2.0);
234        assert_eq!(line.line_locate_point(&pt), Some(0.0));
235
236        // line contains inf or nan
237        let line: LineString = LineString::new(vec![
238            coord! { x: 1.0, y: 1.0 },
239            coord! {
240                x: Float::nan(),
241                y: 1.0,
242            },
243            coord! { x: 0.0, y: 0.0 },
244        ]);
245        let pt = point!(x: 2.0, y: 2.0);
246        assert_eq!(line.line_locate_point(&pt), None);
247
248        let line: LineString = LineString::new(vec![
249            coord! { x: 1.0, y: 1.0 },
250            coord! {
251                x: Float::infinity(),
252                y: 1.0,
253            },
254            coord! { x: 0.0, y: 0.0 },
255        ]);
256        let pt = point!(x: 2.0, y: 2.0);
257        assert_eq!(line.line_locate_point(&pt), None);
258        let line: LineString = LineString::new(vec![
259            coord! { x: 1.0, y: 1.0 },
260            coord! {
261                x: Float::neg_infinity(),
262                y: 1.0,
263            },
264            coord! { x: 0.0, y: 0.0 },
265        ]);
266        let pt = point!(x: 2.0, y: 2.0);
267        assert_eq!(line.line_locate_point(&pt), None);
268    }
269}