use geo_types::Coord;
use crate::TileError;
use crate::clip_polyline::{segment_intersects, to_local};
use crate::tile::{TileId, tile_of};
use crate::vertex::Vertex;
const MAX_INDEXED_LEN: usize = u16::MAX as usize + 1;
const MAX_TILE_VISITS: i64 = 1 << 25;
#[inline]
const fn shift(c: Coord<i32>, d: i32) -> Coord<i32> {
Coord {
x: c.x + d,
y: c.y + d,
}
}
pub(crate) trait RouteSink<V: Vertex> {
fn begin_polyline(&mut self);
fn begin_segment(&mut self);
fn emit(&mut self, tile: TileId, origin: Coord<i32>, a: V, c: V) -> Result<(), TileError>;
}
#[derive(Clone, Copy)]
struct Located {
owner: TileId,
core_lo: Coord<i32>,
core_hi: Coord<i32>,
inner_lo: Coord<i32>,
inner_hi: Coord<i32>,
}
impl Located {
fn contains_core(&self, c: Coord<i32>) -> bool {
c.x >= self.core_lo.x
&& c.x <= self.core_hi.x
&& c.y >= self.core_lo.y
&& c.y <= self.core_hi.y
}
fn contains_inner(&self, c: Coord<i32>) -> bool {
c.x >= self.inner_lo.x
&& c.x <= self.inner_hi.x
&& c.y >= self.inner_lo.y
&& c.y <= self.inner_hi.y
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Grid {
extent: i32,
buffer: i32,
}
impl Grid {
pub(crate) const fn new(extent: u32, buffer: u16) -> Result<Self, TileError> {
if extent == 0 || extent > i32::MAX.cast_unsigned() {
return Err(TileError::InvalidExtent);
}
if 2 * (buffer as u32) >= extent {
return Err(TileError::BufferTooLarge);
}
Ok(Self {
extent: extent.cast_signed(),
buffer: buffer as i32,
})
}
pub(crate) fn extent(self) -> u32 {
self.extent.cast_unsigned()
}
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "buffer is always in 0..=u16::MAX (it was built from a u16)"
)]
pub(crate) fn buffer(self) -> u16 {
self.buffer as u16
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(crate) fn slice_one<V: Vertex>(
self,
polyline: &[V],
tile: TileId,
) -> Result<Vec<Vec<V>>, TileError> {
let poly = polyline;
let (min, max) = self.tile_buffered_bounds(tile)?;
let origin = shift(min, self.buffer);
let mut runs = Vec::new();
let mut cur: Vec<V> = Vec::new();
let mut prev: Option<V> = None;
let mut left_hit = false;
for &c in poly {
if let Some(a) = prev {
if a.position() == c.position() {
continue; }
let this_hit = segment_intersects(a.position(), c.position(), min, max);
if left_hit || this_hit {
cur.push(to_local(a, origin)?); } else if cur.len() >= 2 {
runs.push(std::mem::take(&mut cur)); } else {
cur.clear();
}
left_hit = this_hit;
}
prev = Some(c);
}
if left_hit && let Some(a) = prev {
cur.push(to_local(a, origin)?);
}
if cur.len() >= 2 {
runs.push(cur);
}
Ok(runs)
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(crate) fn route<V: Vertex, S: RouteSink<V>>(
self,
polyline: &[V],
sink: &mut S,
) -> Result<(), TileError> {
let poly = polyline;
if poly.len() > MAX_INDEXED_LEN {
return Err(TileError::PolylineTooLarge);
}
let Some(first) = poly.first().map(Vertex::position) else {
return Ok(());
};
let reference = tile_of(first, self.extent);
sink.begin_polyline();
let mut budget: i64 = MAX_TILE_VISITS;
let mut prev: Option<V> = None;
let mut prev_loc: Option<Located> = None;
for v in poly {
let c = v.position();
if let Some(a) = prev {
let a_pos = a.position();
if a_pos == c {
continue; }
sink.begin_segment();
let la = match prev_loc {
Some(l) => l,
None => self.locate(a_pos)?,
};
if la.contains_inner(a_pos) && la.contains_inner(c) {
budget -= 1;
if budget < 0 {
return Err(TileError::TooManyTiles);
}
sink.emit(la.owner, la.core_lo, a, *v)?;
prev_loc = Some(la); } else {
let lo = tile_of(
Coord {
x: (a_pos.x.min(c.x))
.checked_sub(self.buffer)
.ok_or(TileError::Overflow)?,
y: (a_pos.y.min(c.y))
.checked_sub(self.buffer)
.ok_or(TileError::Overflow)?,
},
self.extent,
);
let hi = tile_of(
Coord {
x: (a_pos.x.max(c.x))
.checked_add(self.buffer)
.ok_or(TileError::Overflow)?,
y: (a_pos.y.max(c.y))
.checked_add(self.buffer)
.ok_or(TileError::Overflow)?,
},
self.extent,
);
for (t, r) in [
(lo.x, reference.x),
(hi.x, reference.x),
(lo.y, reference.y),
(hi.y, reference.y),
] {
i16::try_from(i64::from(t) - i64::from(r))
.map_err(|_| TileError::TooManyTiles)?;
}
budget -= (i64::from(hi.x) - i64::from(lo.x) + 1)
* (i64::from(hi.y) - i64::from(lo.y) + 1);
if budget < 0 {
return Err(TileError::TooManyTiles);
}
for ty in lo.y..=hi.y {
for tx in lo.x..=hi.x {
let tile = TileId::new(tx, ty);
let (min, max) = self.tile_buffered_bounds(tile)?;
if segment_intersects(a_pos, c, min, max) {
sink.emit(tile, shift(min, self.buffer), a, *v)?;
}
}
}
prev_loc = Some(if la.contains_core(c) {
la
} else {
self.locate(c)?
});
}
}
prev = Some(*v);
}
Ok(())
}
fn tile_buffered_bounds(self, tile: TileId) -> Result<(Coord<i32>, Coord<i32>), TileError> {
let base_x = tile.x.checked_mul(self.extent).ok_or(TileError::Overflow)?;
let base_y = tile.y.checked_mul(self.extent).ok_or(TileError::Overflow)?;
let reach = (self.extent - 1)
.checked_add(self.buffer)
.ok_or(TileError::Overflow)?;
Ok((
Coord {
x: base_x.checked_sub(self.buffer).ok_or(TileError::Overflow)?,
y: base_y.checked_sub(self.buffer).ok_or(TileError::Overflow)?,
},
Coord {
x: base_x.checked_add(reach).ok_or(TileError::Overflow)?,
y: base_y.checked_add(reach).ok_or(TileError::Overflow)?,
},
))
}
fn locate(self, c: Coord<i32>) -> Result<Located, TileError> {
let owner = tile_of(c, self.extent);
let (min, max) = self.tile_buffered_bounds(owner)?;
Ok(Located {
owner,
core_lo: shift(min, self.buffer),
core_hi: shift(max, -self.buffer),
inner_lo: shift(min, 2 * self.buffer),
inner_hi: shift(max, -2 * self.buffer),
})
}
}