gistools/geometry/tools/
simplify.rs1use alloc::vec;
2use libm::pow;
3use s2json::{MValue, VectorGeometry, VectorLineString, VectorPoint};
4
5pub trait SimplifyVectorGeometry<M: Clone + Default = MValue> {
7 fn build_sq_dists(&mut self, tolerance: f64, maxzoom: Option<u8>);
9 fn simplify(&mut self, tolerance: f64, zoom: u8, maxzoom: Option<u8>);
11}
12
13impl<M: Clone + Default> SimplifyVectorGeometry<M> for VectorGeometry<M> {
14 fn build_sq_dists(&mut self, tolerance: f64, maxzoom: Option<u8>) {
16 build_sq_dists(self, tolerance, maxzoom);
17 }
18
19 fn simplify(&mut self, tolerance: f64, zoom: u8, maxzoom: Option<u8>) {
21 simplify(self, tolerance, zoom, maxzoom);
22 }
23}
24
25pub 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
53fn 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
66fn _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 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
116fn 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
141pub 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
174fn 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
200pub 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}