geo/algorithm/
simplify.rs

1use crate::algorithm::{CoordsIter, Distance, Euclidean};
2use crate::geometry::{Coord, Line, LineString, MultiLineString, MultiPolygon, Polygon};
3use crate::GeoFloat;
4
5const LINE_STRING_INITIAL_MIN: usize = 2;
6const POLYGON_INITIAL_MIN: usize = 4;
7
8// Because the RDP algorithm is recursive, we can't assign an index to a point inside the loop
9// instead, we wrap a simple struct around index and point in a wrapper function,
10// passing that around instead, extracting either points or indices on the way back out
11#[derive(Copy, Clone)]
12struct RdpIndex<T>
13where
14    T: GeoFloat,
15{
16    index: usize,
17    coord: Coord<T>,
18}
19
20// Wrapper for the RDP algorithm, returning simplified points
21fn rdp<T, I: Iterator<Item = Coord<T>>, const INITIAL_MIN: usize>(
22    coords: I,
23    epsilon: &T,
24) -> Vec<Coord<T>>
25where
26    T: GeoFloat,
27{
28    // Epsilon must be greater than zero for any meaningful simplification to happen
29    if *epsilon <= T::zero() {
30        return coords.collect::<Vec<Coord<T>>>();
31    }
32    let rdp_indices = &coords
33        .enumerate()
34        .map(|(idx, coord)| RdpIndex { index: idx, coord })
35        .collect::<Vec<RdpIndex<T>>>();
36    let mut simplified_len = rdp_indices.len();
37    let simplified_coords: Vec<_> =
38        compute_rdp::<T, INITIAL_MIN>(rdp_indices, &mut simplified_len, epsilon)
39            .into_iter()
40            .map(|rdpindex| rdpindex.coord)
41            .collect();
42    debug_assert_eq!(simplified_coords.len(), simplified_len);
43    simplified_coords
44}
45
46// Wrapper for the RDP algorithm, returning simplified point indices
47fn calculate_rdp_indices<T, const INITIAL_MIN: usize>(
48    rdp_indices: &[RdpIndex<T>],
49    epsilon: &T,
50) -> Vec<usize>
51where
52    T: GeoFloat,
53{
54    if *epsilon <= T::zero() {
55        return rdp_indices
56            .iter()
57            .map(|rdp_index| rdp_index.index)
58            .collect();
59    }
60
61    let mut simplified_len = rdp_indices.len();
62    let simplified_coords =
63        compute_rdp::<T, INITIAL_MIN>(rdp_indices, &mut simplified_len, epsilon)
64            .into_iter()
65            .map(|rdpindex| rdpindex.index)
66            .collect::<Vec<usize>>();
67    debug_assert_eq!(simplified_len, simplified_coords.len());
68    simplified_coords
69}
70
71// Ramer–Douglas-Peucker line simplification algorithm
72// This function returns both the retained points, and their indices in the original geometry,
73// for more flexible use by FFI implementers
74fn compute_rdp<T, const INITIAL_MIN: usize>(
75    rdp_indices: &[RdpIndex<T>],
76    simplified_len: &mut usize,
77    epsilon: &T,
78) -> Vec<RdpIndex<T>>
79where
80    T: GeoFloat,
81{
82    if rdp_indices.is_empty() {
83        return vec![];
84    }
85
86    let first = rdp_indices[0];
87    let last = rdp_indices[rdp_indices.len() - 1];
88    if rdp_indices.len() == 2 {
89        return vec![first, last];
90    }
91
92    let first_last_line = Line::new(first.coord, last.coord);
93
94    // Find the farthest `RdpIndex` from `first_last_line`
95    let (farthest_index, farthest_distance) = rdp_indices
96        .iter()
97        .enumerate()
98        .take(rdp_indices.len() - 1) // Don't include the last index
99        .skip(1) // Don't include the first index
100        .map(|(index, rdp_index)| (index, Euclidean.distance(rdp_index.coord, &first_last_line)))
101        .fold(
102            (0usize, T::zero()),
103            |(farthest_index, farthest_distance), (index, distance)| {
104                if distance >= farthest_distance {
105                    (index, distance)
106                } else {
107                    (farthest_index, farthest_distance)
108                }
109            },
110        );
111    debug_assert_ne!(farthest_index, 0);
112
113    if farthest_distance > *epsilon {
114        // The farthest index was larger than epsilon, so we will recursively simplify subsegments
115        // split by the farthest index.
116        let mut intermediate =
117            compute_rdp::<T, INITIAL_MIN>(&rdp_indices[..=farthest_index], simplified_len, epsilon);
118
119        intermediate.pop(); // Don't include the farthest index twice
120
121        intermediate.extend_from_slice(&compute_rdp::<T, INITIAL_MIN>(
122            &rdp_indices[farthest_index..],
123            simplified_len,
124            epsilon,
125        ));
126        return intermediate;
127    }
128
129    // The farthest index was less than or equal to epsilon, so we will retain only the first
130    // and last indices, resulting in the indices inbetween getting culled.
131
132    // Update `simplified_len` to reflect the new number of indices by subtracting the number
133    // of indices we're culling.
134    let number_culled = rdp_indices.len() - 2;
135    let new_length = *simplified_len - number_culled;
136
137    // If `simplified_len` is now lower than the minimum number of indices needed, then don't
138    // perform the culling and return the original input.
139    if new_length < INITIAL_MIN {
140        return rdp_indices.to_owned();
141    }
142    *simplified_len = new_length;
143
144    // Cull indices between `first` and `last`.
145    vec![first, last]
146}
147
148/// Simplifies a geometry.
149///
150/// The [Ramer–Douglas–Peucker
151/// algorithm](https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm) simplifies a
152/// linestring. Polygons are simplified by running the RDP algorithm on all their constituent
153/// rings. This may result in invalid Polygons, and has no guarantee of preserving topology.
154///
155/// Multi* objects are simplified by simplifying all their constituent geometries individually.
156///
157/// A larger `epsilon` means being more aggressive about removing points with less concern for
158/// maintaining the existing shape.
159///
160/// Specifically, points closer than `epsilon` distance from the simplified output may be
161/// discarded.
162///
163/// An `epsilon` less than or equal to zero will return an unaltered version of the geometry.
164pub trait Simplify<T, Epsilon = T> {
165    /// Returns the simplified representation of a geometry, using the [Ramer–Douglas–Peucker](https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm) algorithm
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// use geo::Simplify;
171    /// use geo::line_string;
172    ///
173    /// let line_string = line_string![
174    ///     (x: 0.0, y: 0.0),
175    ///     (x: 5.0, y: 4.0),
176    ///     (x: 11.0, y: 5.5),
177    ///     (x: 17.3, y: 3.2),
178    ///     (x: 27.8, y: 0.1),
179    /// ];
180    ///
181    /// let simplified = line_string.simplify(&1.0);
182    ///
183    /// let expected = line_string![
184    ///     (x: 0.0, y: 0.0),
185    ///     (x: 5.0, y: 4.0),
186    ///     (x: 11.0, y: 5.5),
187    ///     (x: 27.8, y: 0.1),
188    /// ];
189    ///
190    /// assert_eq!(expected, simplified)
191    /// ```
192    fn simplify(&self, epsilon: &T) -> Self
193    where
194        T: GeoFloat;
195}
196
197/// Simplifies a geometry, returning the retained _indices_ of the input.
198///
199/// This operation uses the [Ramer–Douglas–Peucker algorithm](https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm)
200/// and does not guarantee that the returned geometry is valid.
201///
202/// A larger `epsilon` means being more aggressive about removing points with less concern for
203/// maintaining the existing shape.
204///
205/// Specifically, points closer than `epsilon` distance from the simplified output may be
206/// discarded.
207///
208/// An `epsilon` less than or equal to zero will return an unaltered version of the geometry.
209pub trait SimplifyIdx<T, Epsilon = T> {
210    /// Returns the simplified indices of a geometry, using the [Ramer–Douglas–Peucker](https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm) algorithm
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use geo::SimplifyIdx;
216    /// use geo::line_string;
217    ///
218    /// let line_string = line_string![
219    ///     (x: 0.0, y: 0.0),
220    ///     (x: 5.0, y: 4.0),
221    ///     (x: 11.0, y: 5.5),
222    ///     (x: 17.3, y: 3.2),
223    ///     (x: 27.8, y: 0.1),
224    /// ];
225    ///
226    /// let simplified = line_string.simplify_idx(&1.0);
227    ///
228    /// let expected = vec![
229    ///     0_usize,
230    ///     1_usize,
231    ///     2_usize,
232    ///     4_usize,
233    /// ];
234    ///
235    /// assert_eq!(expected, simplified);
236    /// ```
237    fn simplify_idx(&self, epsilon: &T) -> Vec<usize>
238    where
239        T: GeoFloat;
240}
241
242impl<T> Simplify<T> for LineString<T>
243where
244    T: GeoFloat,
245{
246    fn simplify(&self, epsilon: &T) -> Self {
247        LineString::from(rdp::<_, _, LINE_STRING_INITIAL_MIN>(
248            self.coords_iter(),
249            epsilon,
250        ))
251    }
252}
253
254impl<T> SimplifyIdx<T> for LineString<T>
255where
256    T: GeoFloat,
257{
258    fn simplify_idx(&self, epsilon: &T) -> Vec<usize> {
259        calculate_rdp_indices::<_, LINE_STRING_INITIAL_MIN>(
260            &self
261                .0
262                .iter()
263                .enumerate()
264                .map(|(idx, coord)| RdpIndex {
265                    index: idx,
266                    coord: *coord,
267                })
268                .collect::<Vec<RdpIndex<T>>>(),
269            epsilon,
270        )
271    }
272}
273
274impl<T> Simplify<T> for MultiLineString<T>
275where
276    T: GeoFloat,
277{
278    fn simplify(&self, epsilon: &T) -> Self {
279        MultiLineString::new(self.iter().map(|l| l.simplify(epsilon)).collect())
280    }
281}
282
283impl<T> Simplify<T> for Polygon<T>
284where
285    T: GeoFloat,
286{
287    fn simplify(&self, epsilon: &T) -> Self {
288        Polygon::new(
289            LineString::from(rdp::<_, _, POLYGON_INITIAL_MIN>(
290                self.exterior().coords_iter(),
291                epsilon,
292            )),
293            self.interiors()
294                .iter()
295                .map(|l| {
296                    LineString::from(rdp::<_, _, POLYGON_INITIAL_MIN>(l.coords_iter(), epsilon))
297                })
298                .collect(),
299        )
300    }
301}
302
303impl<T> Simplify<T> for MultiPolygon<T>
304where
305    T: GeoFloat,
306{
307    fn simplify(&self, epsilon: &T) -> Self {
308        MultiPolygon::new(self.iter().map(|p| p.simplify(epsilon)).collect())
309    }
310}
311
312#[cfg(test)]
313mod test {
314    use super::*;
315    use crate::{coord, line_string, polygon};
316
317    #[test]
318    fn recursion_test() {
319        let input = [
320            coord! { x: 8.0, y: 100.0 },
321            coord! { x: 9.0, y: 100.0 },
322            coord! { x: 12.0, y: 100.0 },
323        ];
324        let actual = rdp::<_, _, 2>(input.into_iter(), &1.0);
325        let expected = [coord! { x: 8.0, y: 100.0 }, coord! { x: 12.0, y: 100.0 }];
326        assert_eq!(actual, expected);
327    }
328
329    #[test]
330    fn rdp_test() {
331        let vec = vec![
332            coord! { x: 0.0, y: 0.0 },
333            coord! { x: 5.0, y: 4.0 },
334            coord! { x: 11.0, y: 5.5 },
335            coord! { x: 17.3, y: 3.2 },
336            coord! { x: 27.8, y: 0.1 },
337        ];
338        let compare = vec![
339            coord! { x: 0.0, y: 0.0 },
340            coord! { x: 5.0, y: 4.0 },
341            coord! { x: 11.0, y: 5.5 },
342            coord! { x: 27.8, y: 0.1 },
343        ];
344        let simplified = rdp::<_, _, 2>(vec.into_iter(), &1.0);
345        assert_eq!(simplified, compare);
346    }
347    #[test]
348    fn rdp_test_empty_linestring() {
349        let vec = Vec::new();
350        let compare = Vec::new();
351        let simplified = rdp::<_, _, 2>(vec.into_iter(), &1.0);
352        assert_eq!(simplified, compare);
353    }
354    #[test]
355    fn rdp_test_two_point_linestring() {
356        let vec = vec![coord! { x: 0.0, y: 0.0 }, coord! { x: 27.8, y: 0.1 }];
357        let compare = vec![coord! { x: 0.0, y: 0.0 }, coord! { x: 27.8, y: 0.1 }];
358        let simplified = rdp::<_, _, 2>(vec.into_iter(), &1.0);
359        assert_eq!(simplified, compare);
360    }
361
362    #[test]
363    fn multilinestring() {
364        let mline = MultiLineString::new(vec![LineString::from(vec![
365            (0.0, 0.0),
366            (5.0, 4.0),
367            (11.0, 5.5),
368            (17.3, 3.2),
369            (27.8, 0.1),
370        ])]);
371
372        let mline2 = mline.simplify(&1.0);
373
374        assert_eq!(
375            mline2,
376            MultiLineString::new(vec![LineString::from(vec![
377                (0.0, 0.0),
378                (5.0, 4.0),
379                (11.0, 5.5),
380                (27.8, 0.1),
381            ])])
382        );
383    }
384
385    #[test]
386    fn polygon() {
387        let poly = polygon![
388            (x: 0., y: 0.),
389            (x: 0., y: 10.),
390            (x: 5., y: 11.),
391            (x: 10., y: 10.),
392            (x: 10., y: 0.),
393            (x: 0., y: 0.),
394        ];
395
396        let poly2 = poly.simplify(&2.);
397
398        assert_eq!(
399            poly2,
400            polygon![
401                (x: 0., y: 0.),
402                (x: 0., y: 10.),
403                (x: 10., y: 10.),
404                (x: 10., y: 0.),
405                (x: 0., y: 0.),
406            ],
407        );
408    }
409
410    #[test]
411    fn multipolygon() {
412        let mpoly = MultiPolygon::new(vec![polygon![
413            (x: 0., y: 0.),
414            (x: 0., y: 10.),
415            (x: 5., y: 11.),
416            (x: 10., y: 10.),
417            (x: 10., y: 0.),
418            (x: 0., y: 0.),
419        ]]);
420
421        let mpoly2 = mpoly.simplify(&2.);
422
423        assert_eq!(
424            mpoly2,
425            MultiPolygon::new(vec![polygon![
426                (x: 0., y: 0.),
427                (x: 0., y: 10.),
428                (x: 10., y: 10.),
429                (x: 10., y: 0.),
430                (x: 0., y: 0.)
431            ]]),
432        );
433    }
434
435    #[test]
436    fn simplify_negative_epsilon() {
437        let ls = line_string![
438            (x: 0., y: 0.),
439            (x: 0., y: 10.),
440            (x: 5., y: 11.),
441            (x: 10., y: 10.),
442            (x: 10., y: 0.),
443        ];
444        let simplified = ls.simplify(&-1.0);
445        assert_eq!(ls, simplified);
446    }
447
448    #[test]
449    fn simplify_idx_negative_epsilon() {
450        let ls = line_string![
451            (x: 0., y: 0.),
452            (x: 0., y: 10.),
453            (x: 5., y: 11.),
454            (x: 10., y: 10.),
455            (x: 10., y: 0.),
456        ];
457        let indices = ls.simplify_idx(&-1.0);
458        assert_eq!(vec![0usize, 1, 2, 3, 4], indices);
459    }
460
461    // https://github.com/georust/geo/issues/142
462    #[test]
463    fn simplify_line_string_polygon_initial_min() {
464        let ls = line_string![
465            ( x: 1.4324054e-16, y: 1.4324054e-16 ),
466            ( x: 1.4324054e-16, y: 1.4324054e-16 ),
467            ( x: -5.9730447e26, y: 1.5590374e-27 ),
468            ( x: 1.4324054e-16, y: 1.4324054e-16 ),
469        ];
470        let epsilon: f64 = 3.46e-43;
471
472        // LineString result should be three coordinates
473        let result = ls.simplify(&epsilon);
474        assert_eq!(
475            line_string![
476                ( x: 1.4324054e-16, y: 1.4324054e-16 ),
477                ( x: -5.9730447e26, y: 1.5590374e-27 ),
478                ( x: 1.4324054e-16, y: 1.4324054e-16 ),
479            ],
480            result
481        );
482
483        // Polygon result should be five coordinates
484        let result = Polygon::new(ls, vec![]).simplify(&epsilon);
485        assert_eq!(
486            polygon![
487                ( x: 1.4324054e-16, y: 1.4324054e-16 ),
488                ( x: 1.4324054e-16, y: 1.4324054e-16 ),
489                ( x: -5.9730447e26, y: 1.5590374e-27 ),
490                ( x: 1.4324054e-16, y: 1.4324054e-16 ),
491            ],
492            result,
493        );
494    }
495
496    // https://github.com/georust/geo/issues/995
497    #[test]
498    fn dont_oversimplify() {
499        let unsimplified = line_string![
500            (x: 0.0, y: 0.0),
501            (x: 5.0, y: 4.0),
502            (x: 11.0, y: 5.5),
503            (x: 17.3, y: 3.2),
504            (x: 27.8, y: 0.1)
505        ];
506        let actual = unsimplified.simplify(&30.0);
507        let expected = line_string![
508            (x: 0.0, y: 0.0),
509            (x: 27.8, y: 0.1)
510        ];
511        assert_eq!(actual, expected);
512    }
513}