geo/algorithm/
line_interpolate_point.rs

1use crate::coords_iter::CoordsIter;
2use crate::{CoordFloat, EuclideanLength, Line, LineString, Point};
3use std::ops::AddAssign;
4
5/// Returns an option of the point that lies a given fraction along the line.
6///
7/// If the given fraction is
8///  * less than zero (including negative infinity): returns a `Some`
9///    of the starting point
10///  * greater than one (including infinity): returns a `Some` of the ending point
11///
12///  If either the fraction is NaN, or any coordinates of the line are not
13///  finite, returns `None`.
14///
15/// # Examples
16///
17/// ```
18/// use geo::{LineString, point};
19/// use geo::LineInterpolatePoint;
20///
21/// let linestring: LineString = vec![
22///     [-1.0, 0.0],
23///     [0.0, 0.0],
24///     [0.0, 1.0]
25/// ].into();
26///
27/// assert_eq!(linestring.line_interpolate_point(-1.0), Some(point!(x: -1.0, y: 0.0)));
28/// assert_eq!(linestring.line_interpolate_point(0.25), Some(point!(x: -0.5, y: 0.0)));
29/// assert_eq!(linestring.line_interpolate_point(0.5), Some(point!(x: 0.0, y: 0.0)));
30/// assert_eq!(linestring.line_interpolate_point(0.75), Some(point!(x: 0.0, y: 0.5)));
31/// assert_eq!(linestring.line_interpolate_point(2.0), Some(point!(x: 0.0, y: 1.0)));
32/// ```
33pub trait LineInterpolatePoint<F: CoordFloat> {
34    type Output;
35
36    fn line_interpolate_point(&self, fraction: F) -> Self::Output;
37}
38
39impl<T> LineInterpolatePoint<T> for Line<T>
40where
41    T: CoordFloat,
42{
43    type Output = Option<Point<T>>;
44
45    fn line_interpolate_point(&self, fraction: T) -> Self::Output {
46        if (fraction >= T::zero()) && (fraction <= T::one()) {
47            // fraction between 0 and 1, return a point between start and end
48            let diff = self.end - self.start;
49            let r = self.start + diff * (fraction);
50            if r.x.is_finite() && r.y.is_finite() {
51                Some(r.into())
52            } else {
53                None
54            }
55        } else if fraction < T::zero() {
56            // negative fractions are replaced with zero
57            self.line_interpolate_point(T::zero())
58        } else if fraction > T::one() {
59            // fractions above one are replaced with one
60            self.line_interpolate_point(T::one())
61        } else {
62            // fraction is nan
63            debug_assert!(fraction.is_nan());
64            None
65        }
66    }
67}
68
69impl<T> LineInterpolatePoint<T> for LineString<T>
70where
71    T: CoordFloat + AddAssign + std::fmt::Debug,
72    Line<T>: EuclideanLength<T>,
73    LineString<T>: EuclideanLength<T>,
74{
75    type Output = Option<Point<T>>;
76
77    fn line_interpolate_point(&self, fraction: T) -> Self::Output {
78        if (fraction >= T::zero()) && (fraction <= T::one()) {
79            // find the point along the linestring which is fraction along it
80            let total_length = self.euclidean_length();
81            let fractional_length = total_length * fraction;
82            let mut cum_length = T::zero();
83            for segment in self.lines() {
84                let length = segment.euclidean_length();
85                if cum_length + length >= fractional_length {
86                    let segment_fraction = (fractional_length - cum_length) / length;
87                    return segment.line_interpolate_point(segment_fraction);
88                }
89                cum_length += length;
90            }
91            // either cum_length + length is never larger than fractional_length, i.e.
92            // fractional_length is nan, or the linestring has no lines to loop through
93            debug_assert!(fractional_length.is_nan() || (self.coords_count() == 0));
94            None
95        } else if fraction < T::zero() {
96            // negative fractions replaced with zero
97            self.line_interpolate_point(T::zero())
98        } else if fraction > T::one() {
99            // fractions above one replaced with one
100            self.line_interpolate_point(T::one())
101        } else {
102            // fraction is nan
103            debug_assert!(fraction.is_nan());
104            None
105        }
106    }
107}
108
109#[cfg(test)]
110mod test {
111
112    use super::*;
113    use crate::{coord, point};
114    use crate::{ClosestPoint, LineLocatePoint};
115    use num_traits::Float;
116
117    #[test]
118    fn test_line_interpolate_point_line() {
119        let line = Line::new(coord! { x: -1.0, y: 0.0 }, coord! { x: 1.0, y: 0.0 });
120        // some finite examples
121        assert_eq!(
122            line.line_interpolate_point(-1.0),
123            Some(point!(x: -1.0, y: 0.0))
124        );
125        assert_eq!(
126            line.line_interpolate_point(0.5),
127            Some(point!(x: 0.0, y: 0.0))
128        );
129        assert_eq!(
130            line.line_interpolate_point(0.75),
131            Some(point!(x: 0.5, y: 0.0))
132        );
133        assert_eq!(
134            line.line_interpolate_point(0.0),
135            Some(point!(x: -1.0, y: 0.0))
136        );
137        assert_eq!(
138            line.line_interpolate_point(1.0),
139            Some(point!(x: 1.0, y: 0.0))
140        );
141        assert_eq!(
142            line.line_interpolate_point(2.0),
143            Some(point!(x: 1.0, y: 0.0))
144        );
145
146        // fraction is nan or inf
147        assert_eq!(line.line_interpolate_point(Float::nan()), None);
148        assert_eq!(
149            line.line_interpolate_point(Float::infinity()),
150            Some(line.end_point())
151        );
152        assert_eq!(
153            line.line_interpolate_point(Float::neg_infinity()),
154            Some(line.start_point())
155        );
156
157        let line = Line::new(coord! { x: 0.0, y: 0.0 }, coord! { x: 1.0, y: 1.0 });
158        assert_eq!(
159            line.line_interpolate_point(0.5),
160            Some(point!(x: 0.5, y: 0.5))
161        );
162
163        // line contains nans or infs
164        let line = Line::new(
165            coord! {
166                x: Float::nan(),
167                y: 0.0,
168            },
169            coord! { x: 1.0, y: 1.0 },
170        );
171        assert_eq!(line.line_interpolate_point(0.5), None);
172
173        let line = Line::new(
174            coord! {
175                x: Float::infinity(),
176                y: 0.0,
177            },
178            coord! { x: 1.0, y: 1.0 },
179        );
180        assert_eq!(line.line_interpolate_point(0.5), None);
181
182        let line = Line::new(
183            coord! { x: 0.0, y: 0.0 },
184            coord! {
185                x: 1.0,
186                y: Float::infinity(),
187            },
188        );
189        assert_eq!(line.line_interpolate_point(0.5), None);
190
191        let line = Line::new(
192            coord! {
193                x: Float::neg_infinity(),
194                y: 0.0,
195            },
196            coord! { x: 1.0, y: 1.0 },
197        );
198        assert_eq!(line.line_interpolate_point(0.5), None);
199
200        let line = Line::new(
201            coord! { x: 0.0, y: 0.0 },
202            coord! {
203                x: 1.0,
204                y: Float::neg_infinity(),
205            },
206        );
207        assert_eq!(line.line_interpolate_point(0.5), None);
208    }
209
210    #[test]
211    fn test_line_interpolate_point_linestring() {
212        // some finite examples
213        let linestring: LineString = vec![[-1.0, 0.0], [0.0, 0.0], [1.0, 0.0]].into();
214        assert_eq!(
215            linestring.line_interpolate_point(0.0),
216            Some(point!(x: -1.0, y: 0.0))
217        );
218        assert_eq!(
219            linestring.line_interpolate_point(0.5),
220            Some(point!(x: 0.0, y: 0.0))
221        );
222        assert_eq!(
223            linestring.line_interpolate_point(1.0),
224            Some(point!(x: 1.0, y: 0.0))
225        );
226        assert_eq!(
227            linestring.line_interpolate_point(1.0),
228            linestring.line_interpolate_point(2.0)
229        );
230        assert_eq!(
231            linestring.line_interpolate_point(0.0),
232            linestring.line_interpolate_point(-2.0)
233        );
234
235        // fraction is nan or inf
236        assert_eq!(
237            linestring.line_interpolate_point(Float::infinity()),
238            linestring.points().last()
239        );
240        assert_eq!(
241            linestring.line_interpolate_point(Float::neg_infinity()),
242            linestring.points().next()
243        );
244        assert_eq!(linestring.line_interpolate_point(Float::nan()), None);
245
246        let linestring: LineString = vec![[-1.0, 0.0], [0.0, 0.0], [0.0, 1.0]].into();
247        assert_eq!(
248            linestring.line_interpolate_point(0.5),
249            Some(point!(x: 0.0, y: 0.0))
250        );
251        assert_eq!(
252            linestring.line_interpolate_point(1.5),
253            Some(point!(x: 0.0, y: 1.0))
254        );
255
256        // linestrings with nans/infs
257        let linestring: LineString = vec![[-1.0, 0.0], [0.0, Float::nan()], [0.0, 1.0]].into();
258        assert_eq!(linestring.line_interpolate_point(0.5), None);
259        assert_eq!(linestring.line_interpolate_point(1.5), None);
260        assert_eq!(linestring.line_interpolate_point(-1.0), None);
261
262        let linestring: LineString = vec![[-1.0, 0.0], [0.0, Float::infinity()], [0.0, 1.0]].into();
263        assert_eq!(linestring.line_interpolate_point(0.5), None);
264        assert_eq!(linestring.line_interpolate_point(1.5), None);
265        assert_eq!(linestring.line_interpolate_point(-1.0), None);
266
267        let linestring: LineString =
268            vec![[-1.0, 0.0], [0.0, Float::neg_infinity()], [0.0, 1.0]].into();
269        assert_eq!(linestring.line_interpolate_point(0.5), None);
270        assert_eq!(linestring.line_interpolate_point(1.5), None);
271        assert_eq!(linestring.line_interpolate_point(-1.0), None);
272
273        // Empty line
274        let coords: Vec<Point> = Vec::new();
275        let linestring: LineString = coords.into();
276        assert_eq!(linestring.line_interpolate_point(0.5), None);
277    }
278
279    #[test]
280    fn test_matches_closest_point() {
281        // line_locate_point should return the fraction to the closest point,
282        // so interpolating the line with that fraction should yield the closest point
283        let linestring: LineString = vec![[-1.0, 0.0], [0.5, 1.0], [1.0, 2.0]].into();
284        let pt = point!(x: 0.7, y: 0.7);
285        let frac = linestring
286            .line_locate_point(&pt)
287            .expect("Should result in fraction between 0 and 1");
288        let interpolated_point = linestring
289            .line_interpolate_point(frac)
290            .expect("Shouldn't return None");
291        let closest_point = linestring.closest_point(&pt);
292        match closest_point {
293            crate::Closest::SinglePoint(p) => assert_eq!(interpolated_point, p),
294            _ => panic!("The closest point should be a SinglePoint"), // example chosen to not be an intersection
295        };
296    }
297}