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}