use geo_types::Coord;
use crate::TileError;
use crate::clip_polygon::{ClipScratch, RingClip, clip_ring};
use crate::clip_polyline::to_local;
use crate::grid::Grid;
use crate::polygon_view::{PolygonView, offset};
use crate::tile::TileId;
use crate::vertex::PolyVertex;
#[derive(Debug, Clone, PartialEq, Eq)]
struct FeatureEntry<A> {
rings_end: u32,
attr: A,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PolygonSlicerOne<V: PolyVertex = Coord<i32>, A = ()> {
grid: Grid,
tile: TileId,
verts: Vec<V>,
ring_ends: Vec<u32>,
features: Vec<FeatureEntry<A>>,
scratch: ClipScratch<V>,
}
impl<V: PolyVertex, A> PolygonSlicerOne<V, A> {
pub fn new(extent: u32, buffer: u16, tile: TileId) -> Result<Self, TileError> {
Ok(Self {
grid: Grid::new(extent, buffer)?,
tile,
verts: Vec::new(),
ring_ends: Vec::new(),
features: Vec::new(),
scratch: ClipScratch::new(),
})
}
#[must_use]
pub fn extent(&self) -> u32 {
self.grid.extent()
}
#[must_use]
pub fn buffer(&self) -> u16 {
self.grid.buffer()
}
#[must_use]
pub fn tile(&self) -> TileId {
self.tile
}
pub fn add_feature_with(
&mut self,
exterior: &[V],
holes: &[&[V]],
attr: A,
) -> Result<&mut Self, TileError> {
let (min, max) = self.grid.tile_buffered_bounds(self.tile)?;
let origin = self
.tile
.origin(self.grid.extent())
.ok_or(TileError::Overflow)?;
let (verts_before, rings_before) = (self.verts.len(), self.ring_ends.len());
let kept = self
.clip_feature(exterior, holes, min, max, origin)
.and_then(|kept| kept.then(|| offset(self.ring_ends.len())).transpose());
match kept {
Ok(Some(rings_end)) => self.features.push(FeatureEntry { rings_end, attr }),
other => {
self.verts.truncate(verts_before);
self.ring_ends.truncate(rings_before);
other?;
}
}
Ok(self)
}
fn clip_feature(
&mut self,
exterior: &[V],
holes: &[&[V]],
min: Coord<i32>,
max: Coord<i32>,
origin: Coord<i32>,
) -> Result<bool, TileError> {
let start = self.verts.len();
if clip_ring(exterior, min, max, &mut self.scratch, &mut self.verts)? == RingClip::Outside {
return Ok(false);
}
self.end_ring(start, origin)?;
for hole in holes {
let start = self.verts.len();
match clip_ring(hole, min, max, &mut self.scratch, &mut self.verts)? {
RingClip::Covers => return Ok(false),
RingClip::Clipped => self.end_ring(start, origin)?,
RingClip::Outside => {}
}
}
Ok(true)
}
fn end_ring(&mut self, start: usize, origin: Coord<i32>) -> Result<(), TileError> {
for v in &mut self.verts[start..] {
*v = to_local(*v, origin)?;
}
self.ring_ends.push(offset(self.verts.len())?);
Ok(())
}
pub fn iter_features(&self) -> impl Iterator<Item = PolygonView<'_, V, A>> {
let (verts, ring_ends, features) = (&self.verts, &self.ring_ends, &self.features);
(0..features.len()).map(move |f| {
let start = f
.checked_sub(1)
.map_or(0, |p| features[p].rings_end as usize);
let rings = start..features[f].rings_end as usize;
PolygonView::new(verts, ring_ends, rings, &features[f].attr)
})
}
#[must_use]
pub fn len(&self) -> usize {
self.features.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.features.is_empty()
}
pub fn clear(&mut self) {
self.verts.clear();
self.ring_ends.clear();
self.features.clear();
}
}
impl<V: PolyVertex> PolygonSlicerOne<V, ()> {
pub fn add_feature(&mut self, exterior: &[V], holes: &[&[V]]) -> Result<&mut Self, TileError> {
self.add_feature_with(exterior, holes, ())
}
}
#[cfg(test)]
mod tests {
use geo_types::Coord;
use super::*;
fn ring(pts: &[(i32, i32)]) -> Vec<Coord<i32>> {
pts.iter().map(|&(x, y)| Coord { x, y }).collect()
}
#[test]
fn inside_polygon_is_kept_local() {
let mut s = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(0, 0)).unwrap();
let ext = ring(&[(5, 5), (20, 5), (20, 20), (5, 20), (5, 5)]);
s.add_feature(&ext, &[]).unwrap();
assert_eq!(s.len(), 1);
let f = s.iter_features().next().unwrap();
let rings: Vec<_> = f.iter_rings().collect();
assert_eq!(rings.len(), 1);
assert!(!rings[0].is_hole());
assert_eq!(rings[0].vertices(), ext.as_slice());
}
#[test]
fn polygon_missing_the_tile_is_dropped() {
let mut s = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(0, 0)).unwrap();
let ext = ring(&[(105, 105), (120, 105), (120, 120), (105, 120), (105, 105)]);
s.add_feature(&ext, &[]).unwrap();
assert!(
s.is_empty(),
"a polygon that misses the tile records nothing"
);
}
#[test]
fn tile_fully_inside_polygon_is_filled() {
let mut s = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(1, 1)).unwrap();
let ext = ring(&[(-10, -10), (80, -10), (80, 80), (-10, 80), (-10, -10)]);
s.add_feature(&ext, &[]).unwrap();
let f = s.iter_features().next().expect("filled feature");
let rings: Vec<_> = f.iter_rings().collect();
assert_eq!(rings.len(), 1);
let xs: Vec<i32> = rings[0].vertices().iter().map(|c| c.x).collect();
assert!(
xs.iter().all(|&x| !(0..25).contains(&x)),
"fill box is outside the core cell"
);
}
#[test]
fn hole_inside_the_tile_is_kept() {
let mut s = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(0, 0)).unwrap();
let ext = ring(&[(2, 2), (22, 2), (22, 22), (2, 22), (2, 2)]);
let hole = ring(&[(8, 8), (16, 8), (16, 16), (8, 16), (8, 8)]);
s.add_feature(&ext, &[&hole]).unwrap();
let f = s.iter_features().next().unwrap();
let rings: Vec<_> = f.iter_rings().collect();
assert_eq!(rings.len(), 2);
assert!(!rings[0].is_hole());
assert!(rings[1].is_hole());
assert_eq!(rings[1].vertices(), hole.as_slice());
}
#[test]
fn tile_fully_inside_hole_records_nothing() {
let ext = ring(&[(2, 2), (72, 2), (72, 72), (2, 72), (2, 2)]);
let hole = ring(&[(22, 22), (22, 52), (52, 52), (52, 22), (22, 22)]);
let mut inside = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(1, 1)).unwrap();
inside.add_feature(&ext, &[&hole]).unwrap();
assert!(
inside.is_empty(),
"a tile entirely inside a hole records nothing"
);
let mut edge = PolygonSlicerOne::<Coord<i32>>::new(25, 0, TileId::new(0, 1)).unwrap();
edge.add_feature(&ext, &[&hole]).unwrap();
assert_eq!(
edge.len(),
1,
"a partially-holed tile still records a feature"
);
}
#[test]
fn attribute_rides_through() {
let mut s = PolygonSlicerOne::<Coord<i32>, &str>::new(25, 0, TileId::new(0, 0)).unwrap();
let ext = ring(&[(5, 5), (20, 5), (20, 20), (5, 20), (5, 5)]);
s.add_feature_with(&ext, &[], "lake").unwrap();
assert_eq!(*s.iter_features().next().unwrap().attr(), "lake");
}
}