geo_polygonize_core/
tiling.rs1use crate::options::{DedupPolicy, TileOwnershipPolicy};
2use crate::types::Polygon3D;
3use crate::Polygonizer;
4use geo::bounding_rect::BoundingRect;
5use geo::intersects::Intersects;
6use geo_types::{Coord, Geometry, Rect};
7#[cfg(feature = "parallel")]
8use rayon::prelude::*;
9use std::hash::{DefaultHasher, Hash, Hasher};
10
11pub struct TiledPolygonizer<'a> {
12 bbox: Rect<f64>,
13 tile_size: f64,
14 buffer: f64, geometries: Vec<(&'a Geometry<f64>, Option<Rect<f64>>)>,
16 ownership_policy: TileOwnershipPolicy,
17 dedup_policy: DedupPolicy,
18}
19
20impl<'a> TiledPolygonizer<'a> {
21 pub fn new(bbox: Rect<f64>, tile_size: f64) -> Self {
22 Self {
23 bbox,
24 tile_size,
25 buffer: 0.0,
26 geometries: Vec::new(),
27 ownership_policy: TileOwnershipPolicy::Centroid,
28 dedup_policy: DedupPolicy::KeepAll,
29 }
30 }
31
32 pub fn with_buffer(mut self, buffer: f64) -> Self {
33 self.buffer = buffer;
34 self
35 }
36
37 pub fn with_ownership_policy(mut self, policy: TileOwnershipPolicy) -> Self {
38 self.ownership_policy = policy;
39 self
40 }
41
42 pub fn with_dedup_policy(mut self, policy: DedupPolicy) -> Self {
43 self.dedup_policy = policy;
44 self
45 }
46
47 pub fn add_geometry(&mut self, geom: &'a Geometry<f64>) {
48 let bbox = geom.bounding_rect();
49 self.geometries.push((geom, bbox));
50 }
51
52 fn process_tile(&self, tile_bbox: Rect<f64>) -> Vec<Polygon3D> {
53 let mut local_poly = Polygonizer::new();
54 local_poly.node_input = true;
55
56 let buffered_bbox = Rect::new(
58 Coord {
59 x: tile_bbox.min().x - self.buffer,
60 y: tile_bbox.min().y - self.buffer,
61 },
62 Coord {
63 x: tile_bbox.max().x + self.buffer,
64 y: tile_bbox.max().y + self.buffer,
65 },
66 );
67
68 let mut relevant_lines = 0;
70 for (geom, bbox) in &self.geometries {
71 if bbox.map(|b| b.intersects(&buffered_bbox)).unwrap_or(false) {
72 local_poly.add_borrowed_geometry(geom);
73 relevant_lines += 1;
74 }
75 }
76
77 if relevant_lines == 0 {
78 return Vec::new();
79 }
80
81 if let Ok(result) = local_poly.polygonize() {
83 let mut valid_polys = Vec::new();
85 for poly in result.polygons {
86 let area = poly.unsigned_area_2d();
87
88 if area < 1e-6 {
90 continue;
91 }
92
93 let ownership_point = match self.ownership_policy {
94 TileOwnershipPolicy::Centroid
95 | TileOwnershipPolicy::RepresentativePointInsidePolygon => poly.centroid_2d(),
96 TileOwnershipPolicy::LexicographicMinVertex => {
97 let mut min_pt = None;
98 for coord in &poly.exterior {
99 if let Some(curr) = min_pt {
100 let curr_coord: geo_types::Point<f64> = curr;
101 if coord.x < curr_coord.x()
102 || (coord.x == curr_coord.x() && coord.y < curr_coord.y())
103 {
104 min_pt = Some(geo_types::Point::new(coord.x, coord.y));
105 }
106 } else {
107 min_pt = Some(geo_types::Point::new(coord.x, coord.y));
108 }
109 }
110 min_pt
111 }
112 TileOwnershipPolicy::CanonicalBoundaryHash => {
113 if poly.exterior.is_empty() {
114 poly.centroid_2d()
115 } else {
116 let mut coords = poly.exterior.clone();
117 coords.sort_unstable_by(|a, b| {
118 a.x.total_cmp(&b.x).then(a.y.total_cmp(&b.y))
119 });
120 let mut hasher = DefaultHasher::new();
121 for c in &coords {
122 c.x.to_bits().hash(&mut hasher);
123 c.y.to_bits().hash(&mut hasher);
124 }
125 let hash_val = hasher.finish();
126
127 let mut min_x = f64::INFINITY;
128 let mut min_y = f64::INFINITY;
129 let mut max_x = f64::NEG_INFINITY;
130 let mut max_y = f64::NEG_INFINITY;
131 for c in &poly.exterior {
132 min_x = min_x.min(c.x);
133 min_y = min_y.min(c.y);
134 max_x = max_x.max(c.x);
135 max_y = max_y.max(c.y);
136 }
137
138 let width = max_x - min_x;
139 let height = max_y - min_y;
140
141 let u = (hash_val % 1000) as f64 / 1000.0;
142 let v = ((hash_val / 1000) % 1000) as f64 / 1000.0;
143
144 Some(geo_types::Point::new(min_x + u * width, min_y + v * height))
145 }
146 }
147 };
148
149 if let Some(pt) = ownership_point {
150 let c = pt;
151
152 let max_x_inclusive = tile_bbox.max().x >= self.bbox.max().x;
155 let max_y_inclusive = tile_bbox.max().y >= self.bbox.max().y;
156
157 let in_x = if max_x_inclusive {
158 c.x() >= tile_bbox.min().x && c.x() <= tile_bbox.max().x
159 } else {
160 c.x() >= tile_bbox.min().x && c.x() < tile_bbox.max().x
161 };
162
163 let in_y = if max_y_inclusive {
164 c.y() >= tile_bbox.min().y && c.y() <= tile_bbox.max().y
165 } else {
166 c.y() >= tile_bbox.min().y && c.y() < tile_bbox.max().y
167 };
168
169 if in_x && in_y {
170 valid_polys.push(poly);
171 }
172 }
173 }
174 valid_polys
175 } else {
176 Vec::new()
177 }
178 }
179
180 fn generate_tiles(&self) -> Vec<Rect<f64>> {
181 let min = self.bbox.min();
182 let max = self.bbox.max();
183 let width = max.x - min.x;
184 let height = max.y - min.y;
185
186 let cols = (width / self.tile_size).ceil() as usize;
187 let rows = (height / self.tile_size).ceil() as usize;
188
189 let mut tiles = Vec::new();
190 for r in 0..rows {
191 for c in 0..cols {
192 let x0 = min.x + c as f64 * self.tile_size;
193 let y0 = min.y + r as f64 * self.tile_size;
194 let x1 = (x0 + self.tile_size).min(max.x);
195 let y1 = (y0 + self.tile_size).min(max.y);
196
197 tiles.push(Rect::new(Coord { x: x0, y: y0 }, Coord { x: x1, y: y1 }));
198 }
199 }
200 tiles
201 }
202
203 pub fn polygonize(&self) -> Vec<Polygon3D> {
204 let tiles = self.generate_tiles();
205
206 let result_polygons: Vec<Polygon3D>;
208 #[cfg(feature = "parallel")]
209 {
210 result_polygons = tiles
211 .into_par_iter()
212 .flat_map(|tile| self.process_tile(tile))
213 .collect();
214 }
215 #[cfg(not(feature = "parallel"))]
216 {
217 result_polygons = tiles
218 .into_iter()
219 .flat_map(|tile| self.process_tile(tile))
220 .collect();
221 }
222
223 match self.dedup_policy {
224 DedupPolicy::KeepAll => result_polygons,
225 DedupPolicy::CanonicalRingHash => {
226 use std::collections::HashSet;
227 use std::hash::{DefaultHasher, Hash, Hasher};
228
229 let mut unique_polygons = Vec::new();
230 let mut seen_hashes = HashSet::new();
231
232 fn hash_ring(ring: &[crate::types::Coord3D]) -> u64 {
233 if ring.is_empty() {
234 return 0;
235 }
236 if ring.len() == 1 {
237 let mut h = DefaultHasher::new();
238 ring[0].x.to_bits().hash(&mut h);
239 ring[0].y.to_bits().hash(&mut h);
240 ring[0].z.to_bits().hash(&mut h);
241 return h.finish();
242 }
243
244 let unique_len = ring.len() - 1;
245
246 let mut min_idx_fwd = 0;
248 for i in 1..unique_len {
249 let a = &ring[i];
250 let b = &ring[min_idx_fwd];
251 if a.x
252 .total_cmp(&b.x)
253 .then(a.y.total_cmp(&b.y))
254 .then(a.z.total_cmp(&b.z))
255 == std::cmp::Ordering::Less
256 {
257 min_idx_fwd = i;
258 }
259 }
260 let mut h_fwd = DefaultHasher::new();
261 for i in 0..=unique_len {
262 let idx = (min_idx_fwd + i) % unique_len;
263 let c = &ring[idx];
264 c.x.to_bits().hash(&mut h_fwd);
265 c.y.to_bits().hash(&mut h_fwd);
266 c.z.to_bits().hash(&mut h_fwd);
267 }
268
269 let mut min_idx_rev = 0;
271 let mut rev_ring = ring[0..unique_len].to_vec();
272 rev_ring.reverse();
273 for i in 1..unique_len {
274 let a = &rev_ring[i];
275 let b = &rev_ring[min_idx_rev];
276 if a.x
277 .total_cmp(&b.x)
278 .then(a.y.total_cmp(&b.y))
279 .then(a.z.total_cmp(&b.z))
280 == std::cmp::Ordering::Less
281 {
282 min_idx_rev = i;
283 }
284 }
285 let mut h_rev = DefaultHasher::new();
286 for i in 0..=unique_len {
287 let idx = (min_idx_rev + i) % unique_len;
288 let c = &rev_ring[idx];
289 c.x.to_bits().hash(&mut h_rev);
290 c.y.to_bits().hash(&mut h_rev);
291 c.z.to_bits().hash(&mut h_rev);
292 }
293
294 std::cmp::min(h_fwd.finish(), h_rev.finish())
295 }
296
297 for poly in result_polygons {
298 let mut hasher = DefaultHasher::new();
299
300 let ext_hash = hash_ring(&poly.exterior);
301 ext_hash.hash(&mut hasher);
302
303 let mut interior_hashes = Vec::new();
304 for interior in &poly.interiors {
305 interior_hashes.push(hash_ring(interior));
306 }
307
308 interior_hashes.sort_unstable();
309 for h in interior_hashes {
310 h.hash(&mut hasher);
311 }
312
313 let hash_val = hasher.finish();
314 if seen_hashes.insert(hash_val) {
315 unique_polygons.push(poly);
316 }
317 }
318
319 unique_polygons
320 }
321 }
322 }
323}
324
325#[cfg(test)]
326#[path = "tiling_tests.rs"]
327mod tests;