Skip to content

Commit d035613

Browse files
committed
docs: add docstrings for VW
1 parent 3c93f22 commit d035613

File tree

1 file changed

+41
-4
lines changed

1 file changed

+41
-4
lines changed

src/algorithms/vw.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use crate::extensions::segments::{FromSegments, HullSegments};
1313
use std::cmp::Ordering;
1414
use std::collections::BinaryHeap;
1515

16+
/// Store triangle information. Score is used for ranking the priority queue which determines
17+
/// removal order.
1618
#[derive(Debug)]
1719
struct VWScore<T: CoordFloat> {
1820
score: T,
@@ -42,6 +44,10 @@ impl<T: CoordFloat> PartialEq for VWScore<T> {
4244
}
4345
}
4446

47+
/// Area and topology preserving Visvalingam-Whyatt algorithm
48+
/// adapted from the [geo implementation](https://github.com/georust/geo/blob/e8419735b5986f120ddf1de65ac68c1779c3df30/geo/src/algorithm/simplify_vw.rs)
49+
///
50+
///
4551
fn visvalingam_preserve<T>(orig: &LineString<T>, eps: T, min_len: usize) -> Vec<Coord<T>>
4652
where
4753
T: GeoFloat + RTreeNum,
@@ -56,6 +62,8 @@ where
5662
let tree: RTree<CachedEnvelope<_>> =
5763
RTree::bulk_load(orig.lines().map(CachedEnvelope::new).collect::<Vec<_>>());
5864

65+
// Point adjacency. Tuple at index contains indices into `orig`. Negative values or values
66+
// greater than or equal to max means no next element. (0, 0) sentinel means deleted element.
5967
let mut adjacent: Vec<_> = (0..orig.0.len())
6068
.map(|i| {
6169
if i == 0 {
@@ -66,6 +74,11 @@ where
6674
})
6775
.collect();
6876

77+
// Store all triangles in a minimum priority queue, based on signed area.
78+
//
79+
// Only triangles of positive score are pushed to the heap.
80+
// Invalid triangles are *not* removed when the corresponding point is removed; they are
81+
// invalidated using (0, 0) values in `adjacent` and skipped as necessary.
6982
let mut pq = orig
7083
.ord_triangles()
7184
.enumerate()
@@ -78,16 +91,25 @@ where
7891
.filter(|point| point.score >= T::zero())
7992
.collect::<BinaryHeap<VWScore<T>>>();
8093

94+
// Iterate over points while there is an associated triangle with area between 0 and epsilon
8195
while let Some(smallest) = pq.pop() {
8296
if smallest.score > eps {
97+
// Min-heap guarantees all future points have areas greater than epsilon
98+
break;
99+
}
100+
101+
if len <= min_len {
102+
// Further removal would send us below the minimum length
83103
break;
84104
}
85105

86106
let (left, right) = adjacent[smallest.current];
107+
// A point in this triangle has been removed since this `VScore` was created, so skip it
87108
if left != smallest.left as i32 || right != smallest.right as i32 {
88109
continue;
89110
}
90111

112+
// Removal of this point would cause self-intersection, so skip it
91113
if tree_intersect(&tree, &smallest, &orig.0) {
92114
continue;
93115
}
@@ -96,23 +118,31 @@ where
96118
let (_, rr) = adjacent[right as usize];
97119
adjacent[left as usize] = (ll, right);
98120
adjacent[right as usize] = (left, rr);
121+
// Remove the point from the adjacency list
99122
adjacent[smallest.current] = (0, 0);
123+
// Update the length of the linestring
100124
len -= 1;
125+
// The rtree is never updated as self-intersection can never occur with stale segments and
126+
// if a segment were to intersect with a new segment, then it also intersects with a stale
127+
// segment
101128

102-
if len == min_len {
103-
break;
104-
}
105-
129+
// Recompute the areas of adjacent triangles(s) using left and right adjacent points,
130+
// this may add new triangles to the heap
106131
recompute_triangles(orig, &mut pq, ll, left, right, rr, max);
107132
}
108133

134+
// Filter out deleted points, returning remaining points
109135
orig.0
110136
.iter()
111137
.zip(adjacent.iter())
112138
.filter_map(|(tup, adj)| if *adj != (0, 0) { Some(*tup) } else { None })
113139
.collect()
114140
}
115141

142+
/// Check whether the removal of a candidate point would cause a self-intersection.
143+
///
144+
/// To do this efficiently, and rtree is queried for any existing line segments which fall within
145+
/// the bounding box of the new line segment created.
116146
fn tree_intersect<T>(
117147
tree: &RTree<CachedEnvelope<Line<T>>>,
118148
triangle: &VWScore<T>,
@@ -142,6 +172,7 @@ where
142172
})
143173
}
144174

175+
/// Recompute adjacent triangle(s) using left and right adjacent points, pushing to the heap
145176
fn recompute_triangles<T: CoordFloat>(
146177
orig: &LineString<T>,
147178
pq: &mut BinaryHeap<VWScore<T>>,
@@ -154,6 +185,7 @@ fn recompute_triangles<T: CoordFloat>(
154185
let choices = [(ll, left, right), (left, right, rr)];
155186
for &(ai, current_point, bi) in &choices {
156187
if ai as usize >= max || bi as usize >= max {
188+
// Out of bounds, i.e. we're at an end point
157189
continue;
158190
}
159191

@@ -164,6 +196,7 @@ fn recompute_triangles<T: CoordFloat>(
164196
)
165197
.signed_area();
166198

199+
// If removal of a point would cause a reduction in signed area, skip it
167200
if area < T::zero() {
168201
continue;
169202
}
@@ -178,7 +211,10 @@ fn recompute_triangles<T: CoordFloat>(
178211
}
179212
}
180213

214+
/// Simplifies a geometry while preserving its topology and area.
181215
pub trait SimplifyVW<T, Epsilon = T> {
216+
/// Returns the simplified geometry using a topology and area preserving variant of the
217+
/// [Visvalingam-Whyatt](https://doi.org/10.1179/000870493786962263) algorithm.
182218
fn simplify_vw(&self, eps: Epsilon, len: usize) -> Self;
183219
}
184220

@@ -196,6 +232,7 @@ where
196232
T: GeoFloat + RTreeNum + Send + Sync,
197233
{
198234
fn simplify_vw(&self, eps: T, len: usize) -> Self {
235+
// Get convex hull segments, as their endpoints are invariant under reduction
199236
let segments = self.hull_segments();
200237

201238
if len > segments.len() {

0 commit comments

Comments
 (0)