use crate::{
encoding::writers::PixelWriter,
geo::{
edges::{LineEdge, PointEdge, PolyEdge},
raster::RasterInfo,
},
};
use num_traits::Num;
const EPSILON_INTERSECT: f64 = 1e-4;
const TOLERANCE: f64 = 1e-9;
pub struct Standard<const DEDUP: bool = false>;
pub struct AllTouchedBase<const DEDUP: bool>;
pub type AllTouched = AllTouchedBase<false>;
pub type AllTouchedCached = AllTouchedBase<true>;
pub(crate) trait LineBurnStrategy {
const IS_ALL_TOUCHED: bool;
const REQUIRES_DEDUP: bool;
fn burn_line<N, W>(linedges: &[LineEdge], raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N)
where
N: Num + Copy,
W: PixelWriter<N>;
}
impl<const DEDUP: bool> LineBurnStrategy for Standard<DEDUP> {
const IS_ALL_TOUCHED: bool = false;
const REQUIRES_DEDUP: bool = DEDUP;
#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn burn_line<N, W>(linedges: &[LineEdge], raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N)
where
N: Num + Copy,
W: PixelWriter<N>,
{
if linedges.is_empty() {
return;
}
let nrows = raster_info.nrows as isize;
let ncols = raster_info.ncols as isize;
let last_idx = linedges.len() - 1;
for (idx, edge) in linedges.iter().enumerate() {
let mut ix0 = edge.x0.floor() as isize;
let ix1 = edge.x1.floor() as isize;
let mut iy0 = edge.y0.floor() as isize;
let iy1 = edge.y1.floor() as isize;
let dx = (ix1 - ix0).abs();
let dy = -(iy1 - iy0).abs();
let sx = if ix0 < ix1 { 1 } else { -1 };
let sy = if iy0 < iy1 { 1 } else { -1 };
let mut err = dx + dy;
while ix0 != ix1 || iy0 != iy1 {
if ix0 >= 0 && ix0 < ncols && iy0 >= 0 && iy0 < nrows {
writer.write(iy0 as usize, ix0 as usize, field_value, background);
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
ix0 += sx;
}
if e2 <= dx {
err += dx;
iy0 += sy;
}
}
if idx == last_idx && !edge.is_closed && ix0 >= 0 && ix0 < ncols && iy0 >= 0 && iy0 < nrows {
writer.write(iy0 as usize, ix0 as usize, field_value, background);
}
}
}
}
impl<const DEDUP: bool> LineBurnStrategy for AllTouchedBase<DEDUP> {
const IS_ALL_TOUCHED: bool = true;
const REQUIRES_DEDUP: bool = DEDUP;
#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn burn_line<N, W>(linedges: &[LineEdge], raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N)
where
N: Num + Copy,
W: PixelWriter<N>,
{
if linedges.is_empty() {
return;
}
let nrows = raster_info.nrows as isize;
let ncols = raster_info.ncols as isize;
let nrows_f64 = raster_info.nrows as f64;
let ncols_f64 = raster_info.ncols as f64;
for edge in linedges.iter() {
let mut df_x = edge.x0;
let mut df_y = edge.y0;
let mut df_x_end = edge.x1;
let mut df_y_end = edge.y1;
if df_x > df_x_end {
std::mem::swap(&mut df_x, &mut df_x_end);
std::mem::swap(&mut df_y, &mut df_y_end);
}
if (df_x - df_x_end).abs() < 0.01 {
if df_y_end < df_y {
std::mem::swap(&mut df_y, &mut df_y_end);
}
let ix = df_x_end.floor() as isize;
let mut iy = df_y.floor() as isize;
let mut iy_end = (df_y_end - EPSILON_INTERSECT).floor() as isize;
if ix < 0 || ix >= ncols {
continue;
}
iy = iy.max(0);
iy_end = iy_end.min(nrows - 1);
for y in iy..=iy_end {
writer.write(y as usize, ix as usize, field_value, background);
}
continue;
}
if (df_y - df_y_end).abs() < 0.01 {
if df_x_end < df_x {
std::mem::swap(&mut df_x, &mut df_x_end);
}
let mut ix = df_x.floor() as isize;
let iy = df_y.floor() as isize;
let mut ix_end = (df_x_end - EPSILON_INTERSECT).floor() as isize;
if iy < 0 || iy >= nrows {
continue;
}
ix = ix.max(0);
ix_end = ix_end.min(ncols - 1);
for x in ix..=ix_end {
writer.write(iy as usize, x as usize, field_value, background);
}
continue;
}
let slope = (df_y_end - df_y) / (df_x_end - df_x);
let inv_slope = 1.0 / slope;
if df_x < 0.0 {
df_y += (0.0 - df_x) * slope;
df_x = 0.0;
}
if df_x_end > ncols_f64 {
df_y_end += (ncols_f64 - df_x_end) * slope;
df_x_end = ncols_f64;
}
if df_y < 0.0 {
df_x += (0.0 - df_y) * inv_slope;
df_y = 0.0;
} else if df_y > nrows_f64 {
df_x += (nrows_f64 - df_y) * inv_slope;
df_y = nrows_f64;
}
if df_y_end < 0.0 {
df_x_end += (0.0 - df_y_end) * inv_slope;
} else if df_y_end > nrows_f64 {
df_x_end += (nrows_f64 - df_y_end) * inv_slope;
}
df_x = df_x.clamp(0.0, ncols_f64);
df_x_end = df_x_end.clamp(0.0, ncols_f64);
while df_x >= 0.0 && df_x < df_x_end {
let ix = df_x.floor() as isize;
let iy = df_y.floor() as isize;
if ix >= 0 && ix < ncols && iy >= 0 && iy < nrows {
writer.write(iy as usize, ix as usize, field_value, background);
}
let mut sx = (df_x + 1.0).floor() - df_x;
let mut sy = sx * slope;
if (df_y + sy).floor() as isize == iy {
df_x += sx;
df_y += sy;
} else if slope < 0.0 {
sy = iy as f64 - df_y;
if sy > -TOLERANCE {
sy = -TOLERANCE;
}
sx = sy / slope;
df_x += sx;
df_y += sy;
} else {
sy = (iy + 1) as f64 - df_y;
if sy < TOLERANCE {
sy = TOLERANCE;
}
sx = sy / slope;
df_x += sx;
df_y += sy;
}
}
}
}
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(super) fn burn_point<N, W>(pointedges: &[PointEdge], field_value: N, writer: &mut W, background: N)
where
N: Num + Copy,
W: PixelWriter<N>,
{
for point in pointedges {
writer.write(point.y, point.x, field_value, background);
}
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(super) fn burn_polygon<N, W>(
polyedges: &mut Vec<PolyEdge>,
raster_info: &RasterInfo,
field_value: N,
writer: &mut W,
background: N,
) where
N: Num + Copy,
W: PixelWriter<N>,
{
if polyedges.is_empty() {
return;
}
polyedges.sort_unstable_by(|a, b| a.ystart.cmp(&b.ystart));
let mut yline = polyedges[0].ystart;
let mut active_edges = Vec::new();
let ncols = raster_info.ncols as f64;
while yline < raster_info.nrows && (!active_edges.is_empty() || !polyedges.is_empty()) {
let split_idx = polyedges.partition_point(|edge| edge.ystart <= yline);
active_edges.extend(polyedges.drain(..split_idx));
active_edges.retain(|edge| edge.yend > yline);
if active_edges.is_empty() {
yline += 1;
continue;
}
for edge in active_edges.iter_mut() {
edge.x_at_yline = edge.intersect_at(yline);
}
active_edges.sort_unstable_by(|a, b| a.x_at_yline.total_cmp(&b.x_at_yline));
for chunk in active_edges.chunks_exact(2) {
let x1 = &chunk[0].x_at_yline;
let x2 = &chunk[1].x_at_yline;
let xstart = (x1 + 0.5).floor().clamp(0.0, ncols) as usize;
let xend = (x2 + 0.5).floor().clamp(0.0, ncols) as usize;
for xpix in xstart..xend {
writer.write(yline, xpix, field_value, background);
}
}
yline += 1;
}
}