geo/algorithm/
simplify_vw.rs

1use crate::prelude::*;
2use crate::{
3    Coord, CoordFloat, HasKernel, Line, LineString, MultiLineString, MultiPolygon, Point, Polygon,
4    Triangle,
5};
6use std::cmp::Ordering;
7use std::collections::BinaryHeap;
8
9use rstar::{RTree, RTreeNum};
10
11/// Store triangle information
12// current is the candidate point for removal
13#[derive(Debug)]
14struct VScore<T, I>
15where
16    T: CoordFloat,
17{
18    left: usize,
19    current: usize,
20    right: usize,
21    area: T,
22    // `visvalingam_preserve` uses `intersector`, `visvalingam` does not
23    intersector: I,
24}
25
26// These impls give us a min-heap
27impl<T, I> Ord for VScore<T, I>
28where
29    T: CoordFloat,
30{
31    fn cmp(&self, other: &VScore<T, I>) -> Ordering {
32        other.area.partial_cmp(&self.area).unwrap()
33    }
34}
35
36impl<T, I> PartialOrd for VScore<T, I>
37where
38    T: CoordFloat,
39{
40    fn partial_cmp(&self, other: &VScore<T, I>) -> Option<Ordering> {
41        Some(self.cmp(other))
42    }
43}
44
45impl<T, I> Eq for VScore<T, I> where T: CoordFloat {}
46
47impl<T, I> PartialEq for VScore<T, I>
48where
49    T: CoordFloat,
50{
51    fn eq(&self, other: &VScore<T, I>) -> bool
52    where
53        T: CoordFloat,
54    {
55        self.area == other.area
56    }
57}
58
59/// Simplify a line using the [Visvalingam-Whyatt](http://www.tandfonline.com/doi/abs/10.1179/000870493786962263) algorithm
60//
61// This method returns the **indices** of the simplified line
62// epsilon is the minimum triangle area
63// The paper states that:
64// If [the new triangle's] calculated area is less than that of the last point to be
65// eliminated, use the latter's area instead.
66// (This ensures that the current point cannot be eliminated
67// without eliminating previously eliminated points)
68// (Visvalingam and Whyatt 2013, p47)
69// However, this does *not* apply if you're using a user-defined epsilon;
70// It's OK to remove triangles with areas below the epsilon,
71// then recalculate the new triangle area and push it onto the heap
72// based on Huon Wilson's original implementation:
73// https://github.com/huonw/isrustfastyet/blob/25e7a68ff26673a8556b170d3c9af52e1c818288/mem/line_simplify.rs
74fn visvalingam_indices<T>(orig: &LineString<T>, epsilon: &T) -> Vec<usize>
75where
76    T: CoordFloat,
77{
78    // No need to continue without at least three points
79    if orig.0.len() < 3 {
80        return orig.0.iter().enumerate().map(|(idx, _)| idx).collect();
81    }
82
83    let max = orig.0.len();
84
85    // Adjacent retained points. Simulating the points in a
86    // linked list with indices into `orig`. Big number (larger than or equal to
87    // `max`) means no next element, and (0, 0) means deleted element.
88    let mut adjacent: Vec<_> = (0..orig.0.len())
89        .map(|i| {
90            if i == 0 {
91                (-1_i32, 1_i32)
92            } else {
93                ((i - 1) as i32, (i + 1) as i32)
94            }
95        })
96        .collect();
97
98    // Store all the triangles in a minimum priority queue, based on their area.
99    //
100    // Invalid triangles are *not* removed if / when points are removed; they're
101    // handled by skipping them as necessary in the main loop by checking the
102    // corresponding entry in adjacent for (0, 0) values
103
104    // Compute the initial triangles
105    let mut pq = orig
106        .triangles()
107        .enumerate()
108        .map(|(i, triangle)| VScore {
109            area: triangle.unsigned_area(),
110            current: i + 1,
111            left: i,
112            right: i + 2,
113            intersector: (),
114        })
115        .collect::<BinaryHeap<VScore<T, ()>>>();
116    // While there are still points for which the associated triangle
117    // has an area below the epsilon
118    while let Some(smallest) = pq.pop() {
119        // This triangle's area is above epsilon, so skip it
120        if smallest.area > *epsilon {
121            continue;
122        }
123        //  This triangle's area is below epsilon: eliminate the associated point
124        let (left, right) = adjacent[smallest.current];
125        // A point in this triangle has been removed since this VScore
126        // was created, so skip it
127        if left != smallest.left as i32 || right != smallest.right as i32 {
128            continue;
129        }
130        // We've got a valid triangle, and its area is smaller than epsilon, so
131        // remove it from the simulated "linked list"
132        let (ll, _) = adjacent[left as usize];
133        let (_, rr) = adjacent[right as usize];
134        adjacent[left as usize] = (ll, right);
135        adjacent[right as usize] = (left, rr);
136        adjacent[smallest.current] = (0, 0);
137
138        // Now recompute the adjacent triangle(s), using left and right adjacent points
139        let choices = [(ll, left, right), (left, right, rr)];
140        for &(ai, current_point, bi) in &choices {
141            if ai as usize >= max || bi as usize >= max {
142                // Out of bounds, i.e. we're on one edge
143                continue;
144            }
145            let area = Triangle::new(
146                orig.0[ai as usize],
147                orig.0[current_point as usize],
148                orig.0[bi as usize],
149            )
150            .unsigned_area();
151            pq.push(VScore {
152                area,
153                current: current_point as usize,
154                left: ai as usize,
155                right: bi as usize,
156                intersector: (),
157            });
158        }
159    }
160    // Filter out the points that have been deleted, returning remaining point indices
161    orig.0
162        .iter()
163        .enumerate()
164        .zip(adjacent.iter())
165        .filter_map(|(tup, adj)| if *adj != (0, 0) { Some(tup.0) } else { None })
166        .collect::<Vec<usize>>()
167}
168
169// Wrapper for visvalingam_indices, mapping indices back to points
170fn visvalingam<T>(orig: &LineString<T>, epsilon: &T) -> Vec<Coord<T>>
171where
172    T: CoordFloat,
173{
174    // Epsilon must be greater than zero for any meaningful simplification to happen
175    if *epsilon <= T::zero() {
176        return orig.0.to_vec();
177    }
178    let subset = visvalingam_indices(orig, epsilon);
179    // filter orig using the indices
180    // using get would be more robust here, but the input subset is guaranteed to be valid in this case
181    orig.0
182        .iter()
183        .zip(subset.iter())
184        .map(|(_, s)| orig[*s])
185        .collect()
186}
187
188// Wrap the actual VW function so the R* Tree can be shared.
189// this ensures that shell and rings have access to all segments, so
190// intersections between outer and inner rings are detected
191//
192// Constants:
193//
194// * `INITIAL_MIN`
195//   * If we ever have fewer than these, stop immediately
196// * `MIN_POINTS`
197//   * If we detect a self-intersection before point removal, and we only have `MIN_POINTS` left,
198//     stop: since a self-intersection causes removal of the spatially previous point, THAT could
199//     lead to a further self-intersection without the possibility of removing more points,
200//     potentially leaving the geometry in an invalid state.
201fn vwp_wrapper<T, const INITIAL_MIN: usize, const MIN_POINTS: usize>(
202    exterior: &LineString<T>,
203    interiors: Option<&[LineString<T>]>,
204    epsilon: &T,
205) -> Vec<Vec<Coord<T>>>
206where
207    T: CoordFloat + RTreeNum + HasKernel,
208{
209    let mut rings = vec![];
210    // Populate R* tree with exterior and interior samples, if any
211    let mut tree: RTree<Line<_>> = RTree::bulk_load(
212        exterior
213            .lines()
214            .chain(
215                interiors
216                    .iter()
217                    .flat_map(|ring| *ring)
218                    .flat_map(|line_string| line_string.lines()),
219            )
220            .collect::<Vec<_>>(),
221    );
222
223    // Simplify shell
224    rings.push(visvalingam_preserve::<T, INITIAL_MIN, MIN_POINTS>(
225        exterior, epsilon, &mut tree,
226    ));
227    // Simplify interior rings, if any
228    if let Some(interior_rings) = interiors {
229        for ring in interior_rings {
230            rings.push(visvalingam_preserve::<T, INITIAL_MIN, MIN_POINTS>(
231                ring, epsilon, &mut tree,
232            ))
233        }
234    }
235    rings
236}
237
238/// Visvalingam-Whyatt with self-intersection detection to preserve topologies
239/// this is a port of the technique at https://www.jasondavies.com/simplify/
240//
241// Constants:
242//
243// * `INITIAL_MIN`
244//   * If we ever have fewer than these, stop immediately
245// * `MIN_POINTS`
246//   * If we detect a self-intersection before point removal, and we only have `MIN_POINTS` left,
247//     stop: since a self-intersection causes removal of the spatially previous point, THAT could
248//     lead to a further self-intersection without the possibility of removing more points,
249//     potentially leaving the geometry in an invalid state.
250fn visvalingam_preserve<T, const INITIAL_MIN: usize, const MIN_POINTS: usize>(
251    orig: &LineString<T>,
252    epsilon: &T,
253    tree: &mut RTree<Line<T>>,
254) -> Vec<Coord<T>>
255where
256    T: CoordFloat + RTreeNum + HasKernel,
257{
258    if orig.0.len() < 3 || *epsilon <= T::zero() {
259        return orig.0.to_vec();
260    }
261    let max = orig.0.len();
262    let mut counter = orig.0.len();
263
264    // Adjacent retained points. Simulating the points in a
265    // linked list with indices into `orig`. Big number (larger than or equal to
266    // `max`) means no next element, and (0, 0) means deleted element.
267    let mut adjacent: Vec<_> = (0..orig.0.len())
268        .map(|i| {
269            if i == 0 {
270                (-1_i32, 1_i32)
271            } else {
272                ((i - 1) as i32, (i + 1) as i32)
273            }
274        })
275        .collect();
276    // Store all the triangles in a minimum priority queue, based on their area.
277    //
278    // Invalid triangles are *not* removed if / when points are removed; they're
279    // handled by skipping them as necessary in the main loop by checking the
280    // corresponding entry in adjacent for (0, 0) values
281
282    // Compute the initial triangles
283    let mut pq = orig
284        .triangles()
285        .enumerate()
286        .map(|(i, triangle)| VScore {
287            area: triangle.unsigned_area(),
288            current: i + 1,
289            left: i,
290            right: i + 2,
291            intersector: false,
292        })
293        .collect::<BinaryHeap<VScore<T, bool>>>();
294
295    // While there are still points for which the associated triangle
296    // has an area below the epsilon
297    while let Some(mut smallest) = pq.pop() {
298        if smallest.area > *epsilon {
299            continue;
300        }
301        if counter <= INITIAL_MIN {
302            // we can't remove any more points no matter what
303            break;
304        }
305        let (left, right) = adjacent[smallest.current];
306        // A point in this triangle has been removed since this VScore
307        // was created, so skip it
308        if left != smallest.left as i32 || right != smallest.right as i32 {
309            continue;
310        }
311        // if removal of this point causes a self-intersection, we also remove the previous point
312        // that removal alters the geometry, removing the self-intersection
313        // HOWEVER if we're within 2 points of the absolute minimum, we can't remove this point or the next
314        // because we could then no longer form a valid geometry if removal of next also caused an intersection.
315        // The simplification process is thus over.
316        smallest.intersector = tree_intersect(tree, &smallest, &orig.0);
317        if smallest.intersector && counter <= MIN_POINTS {
318            break;
319        }
320        // We've got a valid triangle, and its area is smaller than epsilon, so
321        // remove it from the simulated "linked list"
322        adjacent[smallest.current] = (0, 0);
323        counter -= 1;
324        // Remove stale segments from R* tree
325        let left_point = Point::from(orig.0[left as usize]);
326        let middle_point = Point::from(orig.0[smallest.current]);
327        let right_point = Point::from(orig.0[right as usize]);
328
329        let line_1 = Line::new(left_point, middle_point);
330        let line_2 = Line::new(middle_point, right_point);
331        assert!(tree.remove(&line_1).is_some());
332        assert!(tree.remove(&line_2).is_some());
333
334        // Restore continuous line segment
335        tree.insert(Line::new(left_point, right_point));
336
337        // Now recompute the adjacent triangle(s), using left and right adjacent points
338        let (ll, _) = adjacent[left as usize];
339        let (_, rr) = adjacent[right as usize];
340        adjacent[left as usize] = (ll, right);
341        adjacent[right as usize] = (left, rr);
342        let choices = [(ll, left, right), (left, right, rr)];
343        for &(ai, current_point, bi) in &choices {
344            if ai as usize >= max || bi as usize >= max {
345                // Out of bounds, i.e. we're on one edge
346                continue;
347            }
348            let new = Triangle::new(
349                orig.0[ai as usize],
350                orig.0[current_point as usize],
351                orig.0[bi as usize],
352            );
353            // The current point causes a self-intersection, and this point precedes it
354            // we ensure it gets removed next by demoting its area to negative epsilon
355            let temp_area = if smallest.intersector && (current_point as usize) < smallest.current {
356                -*epsilon
357            } else {
358                new.unsigned_area()
359            };
360            let new_triangle = VScore {
361                area: temp_area,
362                current: current_point as usize,
363                left: ai as usize,
364                right: bi as usize,
365                intersector: false,
366            };
367
368            // push re-computed triangle onto heap
369            pq.push(new_triangle);
370        }
371    }
372    // Filter out the points that have been deleted, returning remaining points
373    orig.0
374        .iter()
375        .zip(adjacent.iter())
376        .filter_map(|(tup, adj)| if *adj != (0, 0) { Some(*tup) } else { None })
377        .collect()
378}
379
380/// is p1 -> p2 -> p3 wound counterclockwise?
381#[inline]
382fn ccw<T>(p1: Point<T>, p2: Point<T>, p3: Point<T>) -> bool
383where
384    T: CoordFloat + HasKernel,
385{
386    let o = <T as HasKernel>::Ker::orient2d(p1.into(), p2.into(), p3.into());
387    o == Orientation::CounterClockwise
388}
389
390/// checks whether line segments with p1-p4 as their start and endpoints touch or cross
391fn cartesian_intersect<T>(p1: Point<T>, p2: Point<T>, p3: Point<T>, p4: Point<T>) -> bool
392where
393    T: CoordFloat + HasKernel,
394{
395    (ccw(p1, p3, p4) ^ ccw(p2, p3, p4)) & (ccw(p1, p2, p3) ^ ccw(p1, p2, p4))
396}
397
398/// check whether a triangle's edges intersect with any other edges of the LineString
399fn tree_intersect<T>(tree: &RTree<Line<T>>, triangle: &VScore<T, bool>, orig: &[Coord<T>]) -> bool
400where
401    T: CoordFloat + RTreeNum + HasKernel,
402{
403    let point_a = orig[triangle.left];
404    let point_c = orig[triangle.right];
405    let bounding_rect = Triangle::new(
406        orig[triangle.left],
407        orig[triangle.current],
408        orig[triangle.right],
409    )
410    .bounding_rect();
411    let br = Point::new(bounding_rect.min().x, bounding_rect.min().y);
412    let tl = Point::new(bounding_rect.max().x, bounding_rect.max().y);
413    tree.locate_in_envelope_intersecting(&rstar::AABB::from_corners(br, tl))
414        .any(|c| {
415            // triangle start point, end point
416            let (ca, cb) = c.points();
417            ca.0 != point_a
418                && ca.0 != point_c
419                && cb.0 != point_a
420                && cb.0 != point_c
421                && cartesian_intersect(ca, cb, Point::from(point_a), Point::from(point_c))
422        })
423}
424
425/// Simplifies a geometry.
426///
427/// Polygons are simplified by running the algorithm on all their constituent rings.  This may
428/// result in invalid Polygons, and has no guarantee of preserving topology. Multi* objects are
429/// simplified by simplifying all their constituent geometries individually.
430///
431/// An epsilon less than or equal to zero will return an unaltered version of the geometry.
432pub trait SimplifyVw<T, Epsilon = T> {
433    /// Returns the simplified representation of a geometry, using the [Visvalingam-Whyatt](http://www.tandfonline.com/doi/abs/10.1179/000870493786962263) algorithm
434    ///
435    /// See [here](https://bost.ocks.org/mike/simplify/) for a graphical explanation
436    ///
437    /// # Examples
438    ///
439    /// ```
440    /// use geo::SimplifyVw;
441    /// use geo::line_string;
442    ///
443    /// let line_string = line_string![
444    ///     (x: 5.0, y: 2.0),
445    ///     (x: 3.0, y: 8.0),
446    ///     (x: 6.0, y: 20.0),
447    ///     (x: 7.0, y: 25.0),
448    ///     (x: 10.0, y: 10.0),
449    /// ];
450    ///
451    /// let simplified = line_string.simplify_vw(&30.0);
452    ///
453    /// let expected = line_string![
454    ///     (x: 5.0, y: 2.0),
455    ///     (x: 7.0, y: 25.0),
456    ///     (x: 10.0, y: 10.0),
457    /// ];
458    ///
459    /// assert_eq!(expected, simplified);
460    /// ```
461    fn simplify_vw(&self, epsilon: &T) -> Self
462    where
463        T: CoordFloat;
464}
465
466/// Simplifies a geometry, returning the retained _indices_ of the output
467///
468/// This operation uses the Visvalingam-Whyatt algorithm,
469/// and does **not** guarantee that the returned geometry is valid.
470///
471/// An epsilon less than or equal to zero will return an unaltered version of the geometry.
472pub trait SimplifyVwIdx<T, Epsilon = T> {
473    /// Returns the simplified representation of a geometry, using the [Visvalingam-Whyatt](http://www.tandfonline.com/doi/abs/10.1179/000870493786962263) algorithm
474    ///
475    /// See [here](https://bost.ocks.org/mike/simplify/) for a graphical explanation
476    ///
477    /// # Examples
478    ///
479    /// ```
480    /// use geo::SimplifyVwIdx;
481    /// use geo::line_string;
482    ///
483    /// let line_string = line_string![
484    ///     (x: 5.0, y: 2.0),
485    ///     (x: 3.0, y: 8.0),
486    ///     (x: 6.0, y: 20.0),
487    ///     (x: 7.0, y: 25.0),
488    ///     (x: 10.0, y: 10.0),
489    /// ];
490    ///
491    /// let simplified = line_string.simplify_vw_idx(&30.0);
492    ///
493    /// let expected = vec![
494    ///     0_usize,
495    ///     3_usize,
496    ///     4_usize,
497    /// ];
498    ///
499    /// assert_eq!(expected, simplified);
500    /// ```
501    fn simplify_vw_idx(&self, epsilon: &T) -> Vec<usize>
502    where
503        T: CoordFloat;
504}
505
506/// Simplifies a geometry, preserving its topology by removing self-intersections
507///
508/// An epsilon less than or equal to zero will return an unaltered version of the geometry.
509pub trait SimplifyVwPreserve<T, Epsilon = T> {
510    /// Returns the simplified representation of a geometry, using a topology-preserving variant of the
511    /// [Visvalingam-Whyatt](http://www.tandfonline.com/doi/abs/10.1179/000870493786962263) algorithm.
512    ///
513    /// See [here](https://www.jasondavies.com/simplify/) for a graphical explanation.
514    ///
515    /// The topology-preserving algorithm uses an [R* tree](../../../rstar/struct.RTree.html) to
516    /// efficiently find candidate line segments which are tested for intersection with a given triangle.
517    /// If intersections are found, the previous point (i.e. the left component of the current triangle)
518    /// is also removed, altering the geometry and removing the intersection.
519    ///
520    /// In the example below, `(135.0, 68.0)` would be retained by the standard algorithm,
521    /// forming triangle `(0, 1, 3),` which intersects with the segments `(280.0, 19.0),
522    /// (117.0, 48.0)` and `(117.0, 48.0), (300,0, 40.0)`. By removing it,
523    /// a new triangle with indices `(0, 3, 4)` is formed, which does not cause a self-intersection.
524    ///
525    /// **Note**: it is possible for the simplification algorithm to displace a Polygon's interior ring outside its shell.
526    ///
527    /// **Note**: if removal of a point causes a self-intersection, but the geometry only has `n + 2`
528    /// points remaining (4 for a `LineString`, 6 for a `Polygon`), the point is retained and the
529    /// simplification process ends. This is because there is no guarantee that removal of two points will remove
530    /// the intersection, but removal of further points would leave too few points to form a valid geometry.
531    ///
532    /// # Examples
533    ///
534    /// ```
535    /// use approx::assert_relative_eq;
536    /// use geo::SimplifyVwPreserve;
537    /// use geo::line_string;
538    ///
539    /// let line_string = line_string![
540    ///     (x: 10., y: 60.),
541    ///     (x: 135., y: 68.),
542    ///     (x: 94., y: 48.),
543    ///     (x: 126., y: 31.),
544    ///     (x: 280., y: 19.),
545    ///     (x: 117., y: 48.),
546    ///     (x: 300., y: 40.),
547    ///     (x: 301., y: 10.),
548    /// ];
549    ///
550    /// let simplified = line_string.simplify_vw_preserve(&668.6);
551    ///
552    /// let expected = line_string![
553    ///     (x: 10., y: 60.),
554    ///     (x: 126., y: 31.),
555    ///     (x: 280., y: 19.),
556    ///     (x: 117., y: 48.),
557    ///     (x: 300., y: 40.),
558    ///     (x: 301., y: 10.),
559    /// ];
560    ///
561    /// assert_relative_eq!(expected, simplified, epsilon = 1e-6);
562    /// ```
563    fn simplify_vw_preserve(&self, epsilon: &T) -> Self
564    where
565        T: CoordFloat + RTreeNum;
566}
567
568impl<T> SimplifyVwPreserve<T> for LineString<T>
569where
570    T: CoordFloat + RTreeNum + HasKernel,
571{
572    fn simplify_vw_preserve(&self, epsilon: &T) -> LineString<T> {
573        let mut simplified = vwp_wrapper::<_, 2, 4>(self, None, epsilon);
574        LineString::from(simplified.pop().unwrap())
575    }
576}
577
578impl<T> SimplifyVwPreserve<T> for MultiLineString<T>
579where
580    T: CoordFloat + RTreeNum + HasKernel,
581{
582    fn simplify_vw_preserve(&self, epsilon: &T) -> MultiLineString<T> {
583        MultiLineString::new(
584            self.0
585                .iter()
586                .map(|l| l.simplify_vw_preserve(epsilon))
587                .collect(),
588        )
589    }
590}
591
592impl<T> SimplifyVwPreserve<T> for Polygon<T>
593where
594    T: CoordFloat + RTreeNum + HasKernel,
595{
596    fn simplify_vw_preserve(&self, epsilon: &T) -> Polygon<T> {
597        let mut simplified =
598            vwp_wrapper::<_, 4, 6>(self.exterior(), Some(self.interiors()), epsilon);
599        let exterior = LineString::from(simplified.remove(0));
600        let interiors = simplified.into_iter().map(LineString::from).collect();
601        Polygon::new(exterior, interiors)
602    }
603}
604
605impl<T> SimplifyVwPreserve<T> for MultiPolygon<T>
606where
607    T: CoordFloat + RTreeNum + HasKernel,
608{
609    fn simplify_vw_preserve(&self, epsilon: &T) -> MultiPolygon<T> {
610        MultiPolygon::new(
611            self.0
612                .iter()
613                .map(|p| p.simplify_vw_preserve(epsilon))
614                .collect(),
615        )
616    }
617}
618
619impl<T> SimplifyVw<T> for LineString<T>
620where
621    T: CoordFloat,
622{
623    fn simplify_vw(&self, epsilon: &T) -> LineString<T> {
624        LineString::from(visvalingam(self, epsilon))
625    }
626}
627
628impl<T> SimplifyVwIdx<T> for LineString<T>
629where
630    T: CoordFloat,
631{
632    fn simplify_vw_idx(&self, epsilon: &T) -> Vec<usize> {
633        visvalingam_indices(self, epsilon)
634    }
635}
636
637impl<T> SimplifyVw<T> for MultiLineString<T>
638where
639    T: CoordFloat,
640{
641    fn simplify_vw(&self, epsilon: &T) -> MultiLineString<T> {
642        MultiLineString::new(self.iter().map(|l| l.simplify_vw(epsilon)).collect())
643    }
644}
645
646impl<T> SimplifyVw<T> for Polygon<T>
647where
648    T: CoordFloat,
649{
650    fn simplify_vw(&self, epsilon: &T) -> Polygon<T> {
651        Polygon::new(
652            self.exterior().simplify_vw(epsilon),
653            self.interiors()
654                .iter()
655                .map(|l| l.simplify_vw(epsilon))
656                .collect(),
657        )
658    }
659}
660
661impl<T> SimplifyVw<T> for MultiPolygon<T>
662where
663    T: CoordFloat,
664{
665    fn simplify_vw(&self, epsilon: &T) -> MultiPolygon<T> {
666        MultiPolygon::new(self.iter().map(|p| p.simplify_vw(epsilon)).collect())
667    }
668}
669
670#[cfg(test)]
671mod test {
672    use super::{cartesian_intersect, visvalingam, vwp_wrapper, SimplifyVw, SimplifyVwPreserve};
673    use crate::{
674        line_string, point, polygon, Coord, LineString, MultiLineString, MultiPolygon, Point,
675        Polygon,
676    };
677
678    #[test]
679    fn visvalingam_test() {
680        // this is the PostGIS example
681        let ls = line_string![
682            (x: 5.0, y: 2.0),
683            (x: 3.0, y: 8.0),
684            (x: 6.0, y: 20.0),
685            (x: 7.0, y: 25.0),
686            (x: 10.0, y: 10.0)
687        ];
688
689        let correct = vec![(5.0, 2.0), (7.0, 25.0), (10.0, 10.0)];
690        let correct_ls: Vec<_> = correct.iter().map(|e| Coord::from((e.0, e.1))).collect();
691
692        let simplified = visvalingam(&ls, &30.);
693        assert_eq!(simplified, correct_ls);
694    }
695    #[test]
696    fn vwp_intersection_test() {
697        // does the intersection check always work
698        let a = point!(x: 1., y: 3.);
699        let b = point!(x: 3., y: 1.);
700        let c = point!(x: 3., y: 3.);
701        let d = point!(x: 1., y: 1.);
702        // cw + ccw
703        assert!(cartesian_intersect(a, b, c, d));
704        // ccw + ccw
705        assert!(cartesian_intersect(b, a, c, d));
706        // cw + cw
707        assert!(cartesian_intersect(a, b, d, c));
708        // ccw + cw
709        assert!(cartesian_intersect(b, a, d, c));
710    }
711    #[test]
712    fn simple_vwp_test() {
713        // this LineString will have a self-intersection if the point with the
714        // smallest associated area is removed
715        // the associated triangle is (1, 2, 3), and has an area of 668.5
716        // the new triangle (0, 1, 3) self-intersects with triangle (3, 4, 5)
717        // Point 1 must also be removed giving a final, valid
718        // LineString of (0, 3, 4, 5, 6, 7)
719        let ls = line_string![
720            (x: 10., y:60.),
721            (x: 135., y: 68.),
722            (x: 94.,  y: 48.),
723            (x: 126., y: 31.),
724            (x: 280., y: 19.),
725            (x: 117., y: 48.),
726            (x: 300., y: 40.),
727            (x: 301., y: 10.)
728        ];
729        let simplified = vwp_wrapper::<_, 2, 4>(&ls, None, &668.6);
730        // this is the correct, non-intersecting LineString
731        let correct = vec![
732            (10., 60.),
733            (126., 31.),
734            (280., 19.),
735            (117., 48.),
736            (300., 40.),
737            (301., 10.),
738        ];
739        let correct_ls: Vec<_> = correct.iter().map(|e| Coord::from((e.0, e.1))).collect();
740        assert_eq!(simplified[0], correct_ls);
741    }
742    #[test]
743    fn retained_vwp_test() {
744        // we would expect outer[2] to be removed, as its associated area
745        // is below epsilon. However, this causes a self-intersection
746        // with the inner ring, which would also trigger removal of outer[1],
747        // leaving the geometry below min_points. It is thus retained.
748        // Inner should also be reduced, but has points == initial_min for the Polygon type
749        let outer = line_string![
750            (x: -54.4921875, y: 21.289374355860424),
751            (x: -33.5, y: 56.9449741808516),
752            (x: -22.5, y: 44.08758502824516),
753            (x: -19.5, y: 23.241346102386135),
754            (x: -54.4921875, y: 21.289374355860424)
755        ];
756        let inner = line_string![
757            (x: -24.451171875, y: 35.266685523707665),
758            (x: -29.513671875, y: 47.32027765985069),
759            (x: -22.869140625, y: 43.80817468459856),
760            (x: -24.451171875, y: 35.266685523707665)
761        ];
762        let poly = Polygon::new(outer.clone(), vec![inner]);
763        let simplified = poly.simplify_vw_preserve(&95.4);
764        assert_relative_eq!(simplified.exterior(), &outer, epsilon = 1e-6);
765    }
766    #[test]
767    fn remove_inner_point_vwp_test() {
768        // we would expect outer[2] to be removed, as its associated area
769        // is below epsilon. However, this causes a self-intersection
770        // with the inner ring, which would also trigger removal of outer[1],
771        // leaving the geometry below min_points. It is thus retained.
772        // Inner should be reduced to four points by removing inner[2]
773        let outer = line_string![
774            (x: -54.4921875, y: 21.289374355860424),
775            (x: -33.5, y: 56.9449741808516),
776            (x: -22.5, y: 44.08758502824516),
777            (x: -19.5, y: 23.241346102386135),
778            (x: -54.4921875, y: 21.289374355860424)
779        ];
780        let inner = line_string![
781            (x: -24.451171875, y: 35.266685523707665),
782            (x: -40.0, y: 45.),
783            (x: -29.513671875, y: 47.32027765985069),
784            (x: -22.869140625, y: 43.80817468459856),
785            (x: -24.451171875, y: 35.266685523707665)
786        ];
787        let correct_inner = line_string![
788            (x: -24.451171875, y: 35.266685523707665),
789            (x: -40.0, y: 45.0),
790            (x: -22.869140625, y: 43.80817468459856),
791            (x: -24.451171875, y: 35.266685523707665)
792        ];
793        let poly = Polygon::new(outer.clone(), vec![inner]);
794        let simplified = poly.simplify_vw_preserve(&95.4);
795        assert_eq!(simplified.exterior(), &outer);
796        assert_eq!(simplified.interiors()[0], correct_inner);
797    }
798    #[test]
799    fn very_long_vwp_test() {
800        // simplify an 8k-point LineString, eliminating self-intersections
801        let points_ls = geo_test_fixtures::norway_main::<f64>();
802        let simplified = vwp_wrapper::<_, 2, 4>(&points_ls, None, &0.0005);
803        assert_eq!(simplified[0].len(), 3278);
804    }
805
806    #[test]
807    fn visvalingam_test_long() {
808        // simplify a longer LineString
809        let points_ls = geo_test_fixtures::vw_orig::<f64>();
810        let correct_ls = geo_test_fixtures::vw_simplified::<f64>();
811        let simplified = visvalingam(&points_ls, &0.0005);
812        assert_eq!(simplified, correct_ls.0);
813    }
814    #[test]
815    fn visvalingam_preserve_test_long() {
816        // simplify a longer LineString using the preserve variant
817        let points_ls = geo_test_fixtures::vw_orig::<f64>();
818        let correct_ls = geo_test_fixtures::vw_simplified::<f64>();
819        let simplified = points_ls.simplify_vw_preserve(&0.0005);
820        assert_relative_eq!(simplified, correct_ls, epsilon = 1e-6);
821    }
822    #[test]
823    fn visvalingam_test_empty_linestring() {
824        let vec: Vec<[f32; 2]> = Vec::new();
825        let compare = Vec::new();
826        let simplified = visvalingam(&LineString::from(vec), &1.0);
827        assert_eq!(simplified, compare);
828    }
829    #[test]
830    fn visvalingam_test_two_point_linestring() {
831        let vec = vec![Point::new(0.0, 0.0), Point::new(27.8, 0.1)];
832        let compare = vec![Coord::from((0.0, 0.0)), Coord::from((27.8, 0.1))];
833        let simplified = visvalingam(&LineString::from(vec), &1.0);
834        assert_eq!(simplified, compare);
835    }
836
837    #[test]
838    fn multilinestring() {
839        // this is the PostGIS example
840        let points = vec![
841            (5.0, 2.0),
842            (3.0, 8.0),
843            (6.0, 20.0),
844            (7.0, 25.0),
845            (10.0, 10.0),
846        ];
847        let points_ls: Vec<_> = points.iter().map(|e| Point::new(e.0, e.1)).collect();
848
849        let correct = vec![(5.0, 2.0), (7.0, 25.0), (10.0, 10.0)];
850        let correct_ls: Vec<_> = correct.iter().map(|e| Point::new(e.0, e.1)).collect();
851
852        let mline = MultiLineString::new(vec![LineString::from(points_ls)]);
853        assert_relative_eq!(
854            mline.simplify_vw(&30.),
855            MultiLineString::new(vec![LineString::from(correct_ls)]),
856            epsilon = 1e-6
857        );
858    }
859
860    #[test]
861    fn polygon() {
862        let poly = polygon![
863            (x: 0., y: 0.),
864            (x: 0., y: 10.),
865            (x: 5., y: 11.),
866            (x: 10., y: 10.),
867            (x: 10., y: 0.),
868            (x: 0., y: 0.),
869        ];
870
871        let poly2 = poly.simplify_vw(&10.);
872
873        assert_relative_eq!(
874            poly2,
875            polygon![
876                (x: 0., y: 0.),
877                (x: 0., y: 10.),
878                (x: 10., y: 10.),
879                (x: 10., y: 0.),
880                (x: 0., y: 0.),
881            ],
882            epsilon = 1e-6
883        );
884    }
885
886    #[test]
887    fn multipolygon() {
888        let mpoly = MultiPolygon::new(vec![Polygon::new(
889            LineString::from(vec![
890                (0., 0.),
891                (0., 10.),
892                (5., 11.),
893                (10., 10.),
894                (10., 0.),
895                (0., 0.),
896            ]),
897            vec![],
898        )]);
899
900        let mpoly2 = mpoly.simplify_vw(&10.);
901
902        assert_relative_eq!(
903            mpoly2,
904            MultiPolygon::new(vec![Polygon::new(
905                LineString::from(vec![(0., 0.), (0., 10.), (10., 10.), (10., 0.), (0., 0.)]),
906                vec![],
907            )]),
908            epsilon = 1e-6
909        );
910    }
911}