use std::collections::BTreeMap;
use geo_types::Coord;
use crate::TileError;
use crate::clip_polyline::to_local;
use crate::grid::{Grid, RouteSink};
use crate::tile::TileId;
use crate::vertex::Vertex;
#[derive(Debug, Clone, PartialEq, Eq)]
struct TileBuf<V, A> {
tile: TileId,
verts: Vec<V>,
run_ends: Vec<u32>,
feat_ends: Vec<u32>,
feat_attrs: Vec<A>,
open_step: u64,
}
impl<V, A> TileBuf<V, A> {
fn new(tile: TileId) -> Self {
Self {
tile,
verts: Vec::new(),
run_ends: Vec::new(),
feat_ends: Vec::new(),
feat_attrs: Vec::new(),
open_step: 0,
}
}
#[expect(
clippy::cast_possible_truncation,
reason = "a tile holds far fewer than u32::MAX vertices"
)]
fn push_run(&mut self, run: Vec<V>) {
self.verts.extend(run);
self.run_ends.push(self.verts.len() as u32);
}
#[expect(
clippy::cast_possible_truncation,
reason = "run count per tile stays far below u32::MAX"
)]
fn absorb(&mut self, runs: Vec<Vec<V>>, attr: A) {
debug_assert!(!runs.is_empty(), "empty features are never recorded");
for run in runs {
self.push_run(run);
}
self.feat_ends.push(self.run_ends.len() as u32);
self.feat_attrs.push(attr);
}
}
fn features_of<V: Vertex, A>(
buf: &TileBuf<V, A>,
) -> impl Iterator<Item = FeatureView<'_, V, A>> + use<'_, V, A> {
let verts = buf.verts.as_slice();
let run_ends = buf.run_ends.as_slice();
let feat_ends = buf.feat_ends.as_slice();
let feat_attrs = buf.feat_attrs.as_slice();
(0..feat_ends.len()).map(move |f| {
let rs = if f == 0 { 0 } else { feat_ends[f - 1] as usize };
let re = feat_ends[f] as usize;
let start = if rs == 0 { 0 } else { run_ends[rs - 1] };
FeatureView {
verts,
start,
run_ends: &run_ends[rs..re],
attr: &feat_attrs[f],
}
})
}
pub struct TileView<'a, V: Vertex, A = ()> {
buf: &'a TileBuf<V, A>,
}
impl<'a, V: Vertex, A> TileView<'a, V, A> {
#[must_use]
pub fn tile_id(&self) -> TileId {
self.buf.tile
}
pub fn iter_features(&self) -> impl Iterator<Item = FeatureView<'a, V, A>> + use<'a, V, A> {
features_of(self.buf)
}
}
pub struct FeatureView<'a, V: Vertex, A = ()> {
verts: &'a [V],
start: u32,
run_ends: &'a [u32],
attr: &'a A,
}
impl<'a, V: Vertex, A> FeatureView<'a, V, A> {
#[must_use]
pub fn attr(&self) -> &'a A {
self.attr
}
pub fn iter_polylines(&self) -> impl Iterator<Item = &'a [V]> + use<'a, V, A> {
let verts = self.verts;
let mut prev = self.start as usize;
self.run_ends.iter().map(move |&end| {
let end = end as usize;
let run = &verts[prev..end];
prev = end;
run
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SlicerAll<V: Vertex = Coord<i32>, A = ()> {
grid: Grid,
tiles: Vec<TileBuf<V, A>>,
index: BTreeMap<TileId, u32>,
step: u64,
feature_start: u64,
last_slot: Option<(TileId, u32)>,
pending_attr: Option<A>,
}
impl<V: Vertex, A> SlicerAll<V, A> {
fn from_grid(grid: Grid) -> Self {
Self {
grid,
tiles: Vec::new(),
index: BTreeMap::new(),
step: 0,
feature_start: 0,
last_slot: None,
pending_attr: None,
}
}
pub fn new(extent: u32, buffer: u16) -> Result<Self, TileError> {
Ok(Self::from_grid(Grid::new(extent, buffer)?))
}
#[must_use]
pub fn extent(&self) -> u32 {
self.grid.extent()
}
#[must_use]
pub fn buffer(&self) -> u16 {
self.grid.buffer()
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn add_feature_with<P: AsRef<[V]>>(
&mut self,
polyline: P,
attr: A,
) -> Result<&mut Self, TileError>
where
A: Clone,
{
let grid = self.grid; let tiles_before = self.tiles.len();
let step_before = self.step;
self.pending_attr = Some(attr);
let result = grid.route(polyline.as_ref(), self);
self.pending_attr = None;
if let Err(err) = result {
self.rollback_feature(tiles_before, step_before);
return Err(err);
}
Ok(self)
}
fn rollback_feature(&mut self, tiles_before: usize, step_before: u64) {
for buf in &mut self.tiles[..tiles_before] {
if buf.open_step > step_before {
buf.feat_ends.pop();
buf.feat_attrs.pop();
let runs_keep = buf.feat_ends.last().copied().unwrap_or(0) as usize;
let verts_keep = if runs_keep == 0 {
0
} else {
buf.run_ends[runs_keep - 1] as usize
};
buf.run_ends.truncate(runs_keep);
buf.verts.truncate(verts_keep);
}
}
for buf in self.tiles.drain(tiles_before..) {
self.index.remove(&buf.tile);
}
self.last_slot = None;
}
#[expect(
clippy::cast_possible_truncation,
reason = "tile count stays far below u32::MAX"
)]
fn tile_slot(&mut self, tile: TileId) -> usize {
if let Some((last, slot)) = self.last_slot
&& last == tile
{
return slot as usize;
}
let slot = if let Some(&slot) = self.index.get(&tile) {
slot
} else {
let slot = self.tiles.len() as u32;
self.tiles.push(TileBuf::new(tile));
self.index.insert(tile, slot);
slot
};
self.last_slot = Some((tile, slot));
slot as usize
}
pub fn iter_tiles(&self) -> impl Iterator<Item = TileView<'_, V, A>> {
let tiles = self.tiles.as_slice();
self.index.values().map(move |&slot| TileView {
buf: &tiles[slot as usize],
})
}
#[must_use]
pub fn len(&self) -> usize {
self.tiles.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tiles.is_empty()
}
pub fn clear(&mut self) {
self.tiles.clear();
self.index.clear();
self.step = 0;
self.feature_start = 0;
self.last_slot = None;
self.pending_attr = None;
}
}
impl<V: Vertex> SlicerAll<V, ()> {
pub fn add_feature<P: AsRef<[V]>>(&mut self, polyline: P) -> Result<&mut Self, TileError> {
self.add_feature_with(polyline, ())
}
}
impl<V: Vertex, A: Clone> RouteSink<V> for SlicerAll<V, A> {
fn begin_polyline(&mut self) {
self.step = self.step.wrapping_add(1);
self.feature_start = self.step;
}
fn begin_segment(&mut self) {
self.step = self.step.wrapping_add(1);
}
#[expect(
clippy::cast_possible_truncation,
reason = "run/vertex counts per tile stay far below u32::MAX"
)]
fn emit(&mut self, tile: TileId, origin: Coord<i32>, a: V, c: V) -> Result<(), TileError> {
let step = self.step;
let feature_start = self.feature_start;
let slot = self.tile_slot(tile);
let buf = &mut self.tiles[slot];
let continues = buf.open_step == step.wrapping_sub(1);
let bridges =
!continues && buf.open_step == step.wrapping_sub(2) && buf.open_step >= feature_start;
let c_local = to_local(c, origin)?;
if continues {
buf.verts.push(c_local);
*buf.run_ends
.last_mut()
.expect("continuing implies an open run") = buf.verts.len() as u32;
} else if bridges {
buf.verts.push(to_local(a, origin)?);
buf.verts.push(c_local);
*buf.run_ends
.last_mut()
.expect("bridging implies an open run") = buf.verts.len() as u32;
} else {
let a_local = to_local(a, origin)?;
buf.verts.push(a_local);
buf.verts.push(c_local);
buf.run_ends.push(buf.verts.len() as u32);
if buf.open_step < feature_start {
buf.feat_ends.push(buf.run_ends.len() as u32);
buf.feat_attrs.push(
self.pending_attr
.as_ref()
.expect("a feature is being routed")
.clone(),
);
} else {
*buf.feat_ends
.last_mut()
.expect("a prior run this feature implies a feature span") =
buf.run_ends.len() as u32;
}
}
buf.open_step = step;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SlicerOne<V: Vertex = Coord<i32>, A = ()> {
grid: Grid,
buf: TileBuf<V, A>,
}
impl<V: Vertex, A> SlicerOne<V, A> {
fn from_grid(grid: Grid, tile: TileId) -> Self {
Self {
grid,
buf: TileBuf::new(tile),
}
}
pub fn new(extent: u32, buffer: u16, tile: TileId) -> Result<Self, TileError> {
Ok(Self::from_grid(Grid::new(extent, buffer)?, tile))
}
#[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.buf.tile
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn add_feature_with<P: AsRef<[V]>>(
&mut self,
polyline: P,
attr: A,
) -> Result<&mut Self, TileError> {
let runs = self.grid.slice_one(polyline.as_ref(), self.buf.tile)?;
if !runs.is_empty() {
self.buf.absorb(runs, attr);
}
Ok(self)
}
pub fn iter_features(&self) -> impl Iterator<Item = FeatureView<'_, V, A>> {
features_of(&self.buf)
}
#[must_use]
pub fn len(&self) -> usize {
self.buf.feat_ends.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.buf.feat_ends.is_empty()
}
pub fn clear(&mut self) {
self.buf = TileBuf::new(self.buf.tile);
}
}
impl<V: Vertex> SlicerOne<V, ()> {
pub fn add_feature<P: AsRef<[V]>>(&mut self, polyline: P) -> Result<&mut Self, TileError> {
self.add_feature_with(polyline, ())
}
}