gistools/geometry/tools/
simplify.rs

1use alloc::vec;
2use libm::pow;
3use s2json::{MValue, VectorGeometry, VectorLineString, VectorPoint};
4
5/// Functions to simplify a vector geometry
6pub trait SimplifyVectorGeometry<M: Clone + Default = MValue> {
7    /// Build sequential distances for a vector geometry
8    fn build_sq_dists(&mut self, tolerance: f64, maxzoom: Option<u8>);
9    /// Simplify the geometry to have a tolerance which will be relative to the tile's zoom level.
10    fn simplify(&mut self, tolerance: f64, zoom: u8, maxzoom: Option<u8>);
11}
12
13impl<M: Clone + Default> SimplifyVectorGeometry<M> for VectorGeometry<M> {
14    /// Build sqdistances for a vector geometry
15    fn build_sq_dists(&mut self, tolerance: f64, maxzoom: Option<u8>) {
16        build_sq_dists(self, tolerance, maxzoom);
17    }
18
19    /// Simplify the geometry to have a tolerance which will be relative to the tile's zoom level.
20    fn simplify(&mut self, tolerance: f64, zoom: u8, maxzoom: Option<u8>) {
21        simplify(self, tolerance, zoom, maxzoom);
22    }
23}
24
25/// build sqdistances for line vector data
26pub fn build_sq_dists<M: Clone + Default>(
27    geometry: &mut VectorGeometry<M>,
28    tolerance: f64,
29    maxzoom: Option<u8>,
30) {
31    let maxzoom = maxzoom.unwrap_or(16);
32    let tol = pow(tolerance / (1 << maxzoom) as f64, 2.);
33
34    match geometry {
35        VectorGeometry::LineString(geo) => {
36            let coords = &mut geo.coordinates;
37            build_sq_dist(coords, 0, coords.len() - 1, tol);
38        }
39        VectorGeometry::Polygon(geo) | VectorGeometry::MultiLineString(geo) => {
40            let coords = &mut geo.coordinates;
41            coords.iter_mut().for_each(|line| build_sq_dist(line, 0, line.len() - 1, tol));
42        }
43        VectorGeometry::MultiPolygon(geo) => {
44            let coords = &mut geo.coordinates;
45            coords.iter_mut().for_each(|polygon| {
46                polygon.iter_mut().for_each(|line| build_sq_dist(line, 0, line.len() - 1, tol))
47            });
48        }
49        _ => {}
50    }
51}
52
53/// calculate simplification of line vector data using
54/// optimized Douglas-Peucker algorithm
55fn build_sq_dist<M: Clone + Default>(
56    coords: &mut VectorLineString<M>,
57    first: usize,
58    last: usize,
59    sq_tolerance: f64,
60) {
61    coords[first].t = Some(1.);
62    _build_sq_dist(coords, first, last, sq_tolerance);
63    coords[last].t = Some(1.);
64}
65
66/// calculate simplification of line vector data using
67/// optimized Douglas-Peucker algorithm
68fn _build_sq_dist<M: Clone + Default>(
69    coords: &mut VectorLineString<M>,
70    first: usize,
71    last: usize,
72    sq_tolerance: f64,
73) {
74    let mid = (last - first) >> 1;
75    let mut max_sq_dist = sq_tolerance;
76    let mut min_pos_to_mid = last - first;
77    let mut index: Option<usize> = None;
78
79    let VectorPoint { x: ax, y: ay, .. } = coords[first];
80    let VectorPoint { x: bx, y: by, .. } = coords[last];
81
82    let mut i = first;
83    while i < last {
84        let VectorPoint { x, y, .. } = coords[i];
85        let d = get_sq_seg_dist(x, y, ax, ay, bx, by);
86
87        if d > max_sq_dist {
88            index = Some(i);
89            max_sq_dist = d;
90        } else if d == max_sq_dist {
91            // a workaround to ensure we choose a pivot close to the middle of the list,
92            // reducing recursion depth, for certain degenerate inputs
93            let pos_to_mid = isize::abs(i as isize - mid as isize) as usize;
94            if pos_to_mid < min_pos_to_mid {
95                index = Some(i);
96                min_pos_to_mid = pos_to_mid;
97            }
98        }
99
100        i += 1;
101    }
102
103    if max_sq_dist > sq_tolerance
104        && let Some(index) = index
105    {
106        if index - first > 1 {
107            _build_sq_dist(coords, first, index, sq_tolerance);
108        }
109        coords[index].t = Some(max_sq_dist);
110        if last - index > 1 {
111            _build_sq_dist(coords, index, last, sq_tolerance);
112        }
113    }
114}
115
116/// square distance from a point to a segment
117fn get_sq_seg_dist(ps: f64, pt: f64, s: f64, t: f64, bs: f64, bt: f64) -> f64 {
118    let mut s = s;
119    let mut t = t;
120    let mut ds = bs - s;
121    let mut dt = bt - t;
122
123    if ds != 0. || dt != 0. {
124        let m = ((ps - s) * ds + (pt - t) * dt) / (ds * ds + dt * dt);
125
126        if m > 1. {
127            s = bs;
128            t = bt;
129        } else if m > 0. {
130            s += ds * m;
131            t += dt * m;
132        }
133    }
134
135    ds = ps - s;
136    dt = pt - t;
137
138    ds * ds + dt * dt
139}
140
141/// Simplify a vector geometry
142pub fn simplify<M: Clone + Default>(
143    geometry: &mut VectorGeometry<M>,
144    tolerance: f64,
145    zoom: u8,
146    maxzoom: Option<u8>,
147) {
148    let maxzoom = maxzoom.unwrap_or(16);
149    let zoom_tol = if zoom >= maxzoom { 0. } else { tolerance / (1 << (zoom as u64)) as f64 };
150    match geometry {
151        VectorGeometry::LineString(geo) => {
152            geo.coordinates = simplify_line(&geo.coordinates, zoom_tol, false, false);
153        }
154        VectorGeometry::Polygon(geo) | VectorGeometry::MultiLineString(geo) => {
155            geo.coordinates = geo
156                .coordinates
157                .iter()
158                .map(|line| simplify_line(line, zoom_tol, false, false))
159                .collect();
160        }
161        VectorGeometry::MultiPolygon(geo) => {
162            geo.coordinates = geo
163                .coordinates
164                .iter()
165                .map(|polygon| {
166                    polygon.iter().map(|line| simplify_line(line, zoom_tol, true, true)).collect()
167                })
168                .collect();
169        }
170        _ => (),
171    }
172}
173
174/// simplified a vector line
175fn simplify_line<M: Clone + Default>(
176    line: &VectorLineString<M>,
177    tolerance: f64,
178    is_polygon: bool,
179    is_outer: bool,
180) -> VectorLineString<M> {
181    let sq_tolerance = tolerance * tolerance;
182    let size = line.len() as f64;
183    if tolerance > 0. && size < (if is_polygon { sq_tolerance } else { tolerance }) {
184        return line.clone();
185    }
186
187    let mut ring: VectorLineString<M> = vec![];
188    for point in line {
189        if tolerance == 0. || (point.t.unwrap_or(0.0)) > sq_tolerance {
190            ring.push(point.clone());
191        }
192    }
193    if is_polygon {
194        rewind(&mut ring, is_outer);
195    }
196
197    ring
198}
199
200/// In place adjust the ring if necessary
201pub fn rewind<M: Clone + Default>(ring: &mut VectorLineString<M>, clockwise: bool) {
202    let len = ring.len();
203    let mut area: f64 = 0.;
204    let mut i = 0;
205    let mut j = len - 2;
206    while i < len {
207        area += (ring[i].x - ring[j].x) * (ring[i].y + ring[j].y);
208        j = i;
209        i += 2;
210    }
211    i = 0;
212    if (area > 0.) == clockwise {
213        let len_half = len / 2;
214        while i < len_half {
215            ring.swap(i, len - i - 1);
216            i += 1;
217        }
218    }
219}