geo/algorithm/
simplify.rs

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