1use geo_types::Coord;
16
17use crate::TileError;
18use crate::clip_polygon::{ClipScratch, RingClip, clip_ring};
19use crate::clip_polyline::to_local;
20use crate::grid::Grid;
21use crate::polygon_view::{PolygonView, offset};
22use crate::tile::TileId;
23use crate::vertex::PolyVertex;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
28struct FeatureEntry<A> {
29 rings_end: u32,
30 attr: A,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct PolygonSlicerOne<V: PolyVertex = Coord<i32>, A = ()> {
40 grid: Grid,
41 tile: TileId,
42 verts: Vec<V>,
44 ring_ends: Vec<u32>,
45 features: Vec<FeatureEntry<A>>,
46 scratch: ClipScratch<V>,
48}
49
50impl<V: PolyVertex, A> PolygonSlicerOne<V, A> {
51 pub fn new(extent: u32, buffer: u16, tile: TileId) -> Result<Self, TileError> {
59 Ok(Self {
60 grid: Grid::new(extent, buffer)?,
61 tile,
62 verts: Vec::new(),
63 ring_ends: Vec::new(),
64 features: Vec::new(),
65 scratch: ClipScratch::new(),
66 })
67 }
68
69 #[must_use]
71 pub fn extent(&self) -> u32 {
72 self.grid.extent()
73 }
74
75 #[must_use]
77 pub fn buffer(&self) -> u16 {
78 self.grid.buffer()
79 }
80
81 #[must_use]
83 pub fn tile(&self) -> TileId {
84 self.tile
85 }
86
87 pub fn add_feature_with(
103 &mut self,
104 exterior: &[V],
105 holes: &[&[V]],
106 attr: A,
107 ) -> Result<&mut Self, TileError> {
108 let (min, max) = self.grid.tile_buffered_bounds(self.tile)?;
109 let origin = self
110 .tile
111 .origin(self.grid.extent())
112 .ok_or(TileError::Overflow)?;
113
114 let (verts_before, rings_before) = (self.verts.len(), self.ring_ends.len());
115 let kept = self
116 .clip_feature(exterior, holes, min, max, origin)
117 .and_then(|kept| kept.then(|| offset(self.ring_ends.len())).transpose());
118 match kept {
119 Ok(Some(rings_end)) => self.features.push(FeatureEntry { rings_end, attr }),
120 other => {
121 self.verts.truncate(verts_before);
123 self.ring_ends.truncate(rings_before);
124 other?;
125 }
126 }
127 Ok(self)
128 }
129
130 fn clip_feature(
132 &mut self,
133 exterior: &[V],
134 holes: &[&[V]],
135 min: Coord<i32>,
136 max: Coord<i32>,
137 origin: Coord<i32>,
138 ) -> Result<bool, TileError> {
139 let start = self.verts.len();
142 if clip_ring(exterior, min, max, &mut self.scratch, &mut self.verts)? == RingClip::Outside {
143 return Ok(false);
144 }
145 self.end_ring(start, origin)?;
146 for hole in holes {
147 let start = self.verts.len();
148 match clip_ring(hole, min, max, &mut self.scratch, &mut self.verts)? {
149 RingClip::Covers => return Ok(false),
152 RingClip::Clipped => self.end_ring(start, origin)?,
153 RingClip::Outside => {}
154 }
155 }
156 Ok(true)
157 }
158
159 fn end_ring(&mut self, start: usize, origin: Coord<i32>) -> Result<(), TileError> {
162 for v in &mut self.verts[start..] {
163 *v = to_local(*v, origin)?;
164 }
165 self.ring_ends.push(offset(self.verts.len())?);
166 Ok(())
167 }
168
169 pub fn iter_features(&self) -> impl Iterator<Item = PolygonView<'_, V, A>> {
172 let (verts, ring_ends, features) = (&self.verts, &self.ring_ends, &self.features);
173 (0..features.len()).map(move |f| {
174 let start = f
175 .checked_sub(1)
176 .map_or(0, |p| features[p].rings_end as usize);
177 let rings = start..features[f].rings_end as usize;
178 PolygonView::new(verts, ring_ends, rings, &features[f].attr)
179 })
180 }
181
182 #[must_use]
184 pub fn len(&self) -> usize {
185 self.features.len()
186 }
187
188 #[must_use]
190 pub fn is_empty(&self) -> bool {
191 self.features.is_empty()
192 }
193
194 pub fn clear(&mut self) {
196 self.verts.clear();
197 self.ring_ends.clear();
198 self.features.clear();
199 }
200}
201
202impl<V: PolyVertex> PolygonSlicerOne<V, ()> {
203 pub fn add_feature(&mut self, exterior: &[V], holes: &[&[V]]) -> Result<&mut Self, TileError> {
210 self.add_feature_with(exterior, holes, ())
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use geo_types::Coord;
217
218 use super::*;
219
220 fn ring(pts: &[(i32, i32)]) -> Vec<Coord<i32>> {
221 pts.iter().map(|&(x, y)| Coord { x, y }).collect()
222 }
223
224 #[test]
225 fn inside_polygon_is_kept_local() {
226 let mut s = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(0, 0)).unwrap();
228 let ext = ring(&[(5, 5), (20, 5), (20, 20), (5, 20), (5, 5)]);
229 s.add_feature(&ext, &[]).unwrap();
230 assert_eq!(s.len(), 1);
231 let f = s.iter_features().next().unwrap();
232 let rings: Vec<_> = f.iter_rings().collect();
233 assert_eq!(rings.len(), 1);
234 assert!(!rings[0].is_hole());
235 assert_eq!(rings[0].vertices(), ext.as_slice());
236 }
237
238 #[test]
239 fn polygon_missing_the_tile_is_dropped() {
240 let mut s = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(0, 0)).unwrap();
241 let ext = ring(&[(105, 105), (120, 105), (120, 120), (105, 120), (105, 105)]);
243 s.add_feature(&ext, &[]).unwrap();
244 assert!(
245 s.is_empty(),
246 "a polygon that misses the tile records nothing"
247 );
248 }
249
250 #[test]
251 fn tile_fully_inside_polygon_is_filled() {
252 let mut s = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(1, 1)).unwrap();
255 let ext = ring(&[(-10, -10), (80, -10), (80, 80), (-10, 80), (-10, -10)]);
256 s.add_feature(&ext, &[]).unwrap();
257 let f = s.iter_features().next().expect("filled feature");
258 let rings: Vec<_> = f.iter_rings().collect();
259 assert_eq!(rings.len(), 1);
260 let xs: Vec<i32> = rings[0].vertices().iter().map(|c| c.x).collect();
262 assert!(
263 xs.iter().all(|&x| !(0..25).contains(&x)),
264 "fill box is outside the core cell"
265 );
266 }
267
268 #[test]
269 fn hole_inside_the_tile_is_kept() {
270 let mut s = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(0, 0)).unwrap();
271 let ext = ring(&[(2, 2), (22, 2), (22, 22), (2, 22), (2, 2)]);
272 let hole = ring(&[(8, 8), (16, 8), (16, 16), (8, 16), (8, 8)]);
273 s.add_feature(&ext, &[&hole]).unwrap();
274 let f = s.iter_features().next().unwrap();
275 let rings: Vec<_> = f.iter_rings().collect();
276 assert_eq!(rings.len(), 2);
277 assert!(!rings[0].is_hole());
278 assert!(rings[1].is_hole());
279 assert_eq!(rings[1].vertices(), hole.as_slice());
280 }
281
282 #[test]
283 fn tile_fully_inside_hole_records_nothing() {
284 let ext = ring(&[(2, 2), (72, 2), (72, 72), (2, 72), (2, 2)]);
288 let hole = ring(&[(22, 22), (22, 52), (52, 52), (52, 22), (22, 22)]);
289
290 let mut inside = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(1, 1)).unwrap();
291 inside.add_feature(&ext, &[&hole]).unwrap();
292 assert!(
293 inside.is_empty(),
294 "a tile entirely inside a hole records nothing"
295 );
296
297 let mut edge = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(0, 1)).unwrap();
299 edge.add_feature(&ext, &[&hole]).unwrap();
300 assert_eq!(
301 edge.len(),
302 1,
303 "a partially-holed tile still records a feature"
304 );
305 }
306
307 #[test]
308 fn attribute_rides_through() {
309 let mut s = PolygonSlicerOne::<Coord<i32>, &str>::new(25, 0, TileId::new(0, 0)).unwrap();
310 let ext = ring(&[(5, 5), (20, 5), (20, 20), (5, 20), (5, 5)]);
311 s.add_feature_with(&ext, &[], "lake").unwrap();
312 assert_eq!(*s.iter_features().next().unwrap().attr(), "lake");
313 }
314}