Skip to main content

geo_polygonize_core/
tiling.rs

1use 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, // Overlap buffer to ensure polygons are fully captured
15    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        // Define buffered bbox
57        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        // Filter geometries intersecting the BUFFERED tile
69        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        // Run polygonization
82        if let Ok(result) = local_poly.polygonize() {
83            // Ownership check:
84            let mut valid_polys = Vec::new();
85            for poly in result.polygons {
86                let area = poly.unsigned_area_2d();
87
88                // Filter slivers
89                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                    // Check inclusion [min, max)
153                    // For the last tile in a row/col, we include the max boundary to cover the full bbox.
154                    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        // Process tiles in parallel or sequential
207        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                    // compute forward minimum
247                    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                    // compute backward minimum
270                    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;