gistools/geometry/
simplify.rs1use crate::{VectorGeometry, VectorLineString, VectorPoint};
2
3use libm::pow;
4
5use alloc::vec;
6
7impl VectorGeometry {
8 pub fn build_sq_dists(&mut self, tolerance: f64, maxzoom: Option<u8>) {
10 build_sq_dists(self, tolerance, maxzoom);
11 }
12
13 pub fn simplify(&mut self, tolerance: f64, zoom: u8, maxzoom: Option<u8>) {
15 simplify(self, tolerance, zoom, maxzoom);
16 }
17}
18
19pub fn build_sq_dists(geometry: &mut VectorGeometry, tolerance: f64, maxzoom: Option<u8>) {
21 let maxzoom = maxzoom.unwrap_or(16);
22 let tol = pow(tolerance / ((1 << maxzoom) as f64 * 4_096.), 2.);
23
24 match geometry {
25 VectorGeometry::LineString(geo) => {
26 let coords = &mut geo.coordinates;
27 build_sq_dist(coords, 0, coords.len() - 1, tol);
28 }
29 VectorGeometry::Polygon(geo) | VectorGeometry::MultiLineString(geo) => {
30 let coords = &mut geo.coordinates;
31 coords.iter_mut().for_each(|line| build_sq_dist(line, 0, line.len() - 1, tol));
32 }
33 VectorGeometry::MultiPolygon(geo) => {
34 let coords = &mut geo.coordinates;
35 coords.iter_mut().for_each(|polygon| {
36 polygon.iter_mut().for_each(|line| build_sq_dist(line, 0, line.len() - 1, tol))
37 });
38 }
39 _ => {}
40 }
41}
42
43fn build_sq_dist(coords: &mut VectorLineString, first: usize, last: usize, sq_tolerance: f64) {
46 coords[first].t = Some(1.);
47 _build_sq_dist(coords, first, last, sq_tolerance);
48 coords[last].t = Some(1.);
49}
50
51fn _build_sq_dist(coords: &mut VectorLineString, first: usize, last: usize, sq_tolerance: f64) {
54 let mid = (last - first) >> 1;
55 let mut max_sq_dist = sq_tolerance;
56 let mut min_pos_to_mid = last - first;
57 let mut index: Option<usize> = None;
58
59 let VectorPoint { x: ax, y: ay, .. } = coords[first];
60 let VectorPoint { x: bx, y: by, .. } = coords[last];
61
62 let mut i = first;
63 while i < last {
64 let VectorPoint { x, y, .. } = coords[i];
65 let d = get_sq_seg_dist(x, y, ax, ay, bx, by);
66
67 if d > max_sq_dist {
68 index = Some(i);
69 max_sq_dist = d;
70 } else if d == max_sq_dist {
71 let pos_to_mid = isize::abs(i as isize - mid as isize) as usize;
74 if pos_to_mid < min_pos_to_mid {
75 index = Some(i);
76 min_pos_to_mid = pos_to_mid;
77 }
78 }
79
80 i += 1;
81 }
82
83 if max_sq_dist > sq_tolerance {
84 if let Some(index) = index {
85 if index - first > 1 {
86 _build_sq_dist(coords, first, index, sq_tolerance);
87 }
88 coords[index].t = Some(max_sq_dist);
89 if last - index > 1 {
90 _build_sq_dist(coords, index, last, sq_tolerance);
91 }
92 }
93 }
94}
95
96fn get_sq_seg_dist(ps: f64, pt: f64, s: f64, t: f64, bs: f64, bt: f64) -> f64 {
98 let mut s = s;
99 let mut t = t;
100 let mut ds = bs - s;
101 let mut dt = bt - t;
102
103 if ds != 0. || dt != 0. {
104 let m = ((ps - s) * ds + (pt - t) * dt) / (ds * ds + dt * dt);
105
106 if m > 1. {
107 s = bs;
108 t = bt;
109 } else if m > 0. {
110 s += ds * m;
111 t += dt * m;
112 }
113 }
114
115 ds = ps - s;
116 dt = pt - t;
117
118 ds * ds + dt * dt
119}
120
121pub fn simplify(geometry: &mut VectorGeometry, tolerance: f64, zoom: u8, maxzoom: Option<u8>) {
123 let maxzoom = maxzoom.unwrap_or(16);
124 let zoom_tol = if zoom >= maxzoom { 0. } else { tolerance / ((1 << zoom) as f64 * 4_096.) };
125 match geometry {
126 VectorGeometry::LineString(geo) => {
127 geo.coordinates = simplify_line(&geo.coordinates, zoom_tol, false, false);
128 }
129 VectorGeometry::Polygon(geo) | VectorGeometry::MultiLineString(geo) => {
130 geo.coordinates = geo
131 .coordinates
132 .iter()
133 .map(|line| simplify_line(line, zoom_tol, false, false))
134 .collect();
135 }
136 VectorGeometry::MultiPolygon(geo) => {
137 geo.coordinates = geo
138 .coordinates
139 .iter()
140 .map(|polygon| {
141 polygon.iter().map(|line| simplify_line(line, zoom_tol, true, true)).collect()
142 })
143 .collect();
144 }
145 _ => (),
146 }
147}
148
149fn simplify_line(
151 line: &VectorLineString,
152 tolerance: f64,
153 is_polygon: bool,
154 is_outer: bool,
155) -> VectorLineString {
156 let sq_tolerance = tolerance * tolerance;
157 let size = line.len() as f64;
158 if tolerance > 0. && size < (if is_polygon { sq_tolerance } else { tolerance }) {
159 return line.clone();
160 }
161
162 let mut ring: VectorLineString = vec![];
163 for point in line {
164 if tolerance == 0. || (point.t.unwrap_or(0.0)) > sq_tolerance {
165 ring.push(point.clone());
166 }
167 }
168 if is_polygon {
169 rewind(&mut ring, is_outer);
170 }
171
172 ring
173}
174
175pub fn rewind(ring: &mut VectorLineString, clockwise: bool) {
177 let len = ring.len();
178 let mut area: f64 = 0.;
179 let mut i = 0;
180 let mut j = len - 2;
181 while i < len {
182 area += (ring[i].x - ring[j].x) * (ring[i].y + ring[j].y);
183 j = i;
184 i += 2;
185 }
186 i = 0;
187 if (area > 0.) == clockwise {
188 let len_half = len / 2;
189 while i < len_half {
190 ring.swap(i, len - i - 1);
191 i += 2;
192 }
193 }
194}