use crate::{
encoding::writers::{FillWriter, LineWriter, PixelWriter},
geo::{
edges::{LineEdge, PolyEdge, extract_line, extract_point, extract_ring},
raster::RasterInfo,
},
rasterization::{
burners::{LineBurnStrategy, burn_point, burn_polygon},
pixel_cache::PixelCache,
},
};
use geo_types::{Geometry, GeometryCollection, LineString, MultiLineString, MultiPolygon, Polygon};
use num_traits::Num;
pub(crate) trait Burn<N, W>
where
N: Num + Copy,
W: PixelWriter<N>,
{
fn burn<S: LineBurnStrategy>(&self, raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N);
}
impl<N, W> Burn<N, W> for Geometry
where
N: Num + Copy,
W: PixelWriter<N>,
{
fn burn<S: LineBurnStrategy>(&self, raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N) {
match self {
Geometry::Point(geom) => {
let mut pointedge = Vec::new();
extract_point(&mut pointedge, geom, raster_info);
burn_point(&pointedge, field_value, writer, background);
}
Geometry::MultiPoint(geom) => {
let mut pointedge = Vec::new();
for point in geom {
extract_point(&mut pointedge, point, raster_info);
}
burn_point(&pointedge, field_value, writer, background);
}
Geometry::Polygon(geom) => geom.burn::<S>(raster_info, field_value, writer, background),
Geometry::MultiPolygon(geom) => geom.burn::<S>(raster_info, field_value, writer, background),
Geometry::LineString(geom) => geom.burn::<S>(raster_info, field_value, writer, background),
Geometry::MultiLineString(geom) => geom.burn::<S>(raster_info, field_value, writer, background),
Geometry::GeometryCollection(geom) => geom.burn::<S>(raster_info, field_value, writer, background),
Geometry::Rect(geom) => geom
.to_polygon()
.burn::<S>(raster_info, field_value, writer, background),
Geometry::Triangle(geom) => geom
.to_polygon()
.burn::<S>(raster_info, field_value, writer, background),
Geometry::Line(geom) => {
let linestring = LineString::new(vec![geom.start, geom.end]);
linestring.burn::<S>(raster_info, field_value, writer, background)
}
}
}
}
impl<N, W> Burn<N, W> for GeometryCollection
where
N: Num + Copy,
W: PixelWriter<N>,
{
fn burn<S: LineBurnStrategy>(&self, raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N) {
for geom in self {
geom.burn::<S>(raster_info, field_value, writer, background)
}
}
}
impl<N, W> Burn<N, W> for Polygon
where
N: Num + Copy,
W: PixelWriter<N>,
{
fn burn<S: LineBurnStrategy>(&self, raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N) {
let mut polyedges = Vec::new();
extract_ring(&mut polyedges, self.exterior(), raster_info);
for hole in self.interiors() {
extract_ring(&mut polyedges, hole, raster_info);
}
let (linedges, mut pixel_cache) = if S::IS_ALL_TOUCHED {
let mut linedges = Vec::new();
extract_line(&mut linedges, self.exterior(), raster_info);
for hole in self.interiors() {
extract_line(&mut linedges, hole, raster_info);
}
let pixel_cache = if S::REQUIRES_DEDUP {
Some(PixelCache::new(&linedges))
} else {
None
};
(Some(linedges), pixel_cache)
} else {
(None, None)
};
handle_polygon::<N, W, S>(
raster_info,
&mut polyedges,
linedges,
&mut pixel_cache,
field_value,
writer,
background,
)
}
}
impl<N, W> Burn<N, W> for MultiPolygon
where
N: Num + Copy,
W: PixelWriter<N>,
{
fn burn<S: LineBurnStrategy>(&self, raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N) {
let mut polyedges = Vec::new();
for polygon in self {
extract_ring(&mut polyedges, polygon.exterior(), raster_info);
for hole in polygon.interiors() {
extract_ring(&mut polyedges, hole, raster_info);
}
}
let (linedges, mut pixel_cache) = if S::IS_ALL_TOUCHED {
let mut linedges = Vec::new();
for polygon in self {
extract_line(&mut linedges, polygon.exterior(), raster_info);
for hole in polygon.interiors() {
extract_line(&mut linedges, hole, raster_info);
}
}
let pixel_cache = if S::REQUIRES_DEDUP {
Some(PixelCache::new(&linedges))
} else {
None
};
(Some(linedges), pixel_cache)
} else {
(None, None)
};
handle_polygon::<N, W, S>(
raster_info,
&mut polyedges,
linedges,
&mut pixel_cache,
field_value,
writer,
background,
)
}
}
impl<N, W> Burn<N, W> for LineString
where
N: Num + Copy,
W: PixelWriter<N>,
{
fn burn<S: LineBurnStrategy>(&self, raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N) {
let mut linedges = Vec::new();
extract_line(&mut linedges, self, raster_info);
if raster_info.xres != raster_info.yres || S::REQUIRES_DEDUP {
let mut cache = PixelCache::new(&linedges);
let mut line_writer = LineWriter::new(writer, &mut cache);
S::burn_line(&linedges, raster_info, field_value, &mut line_writer, background)
} else {
S::burn_line(&linedges, raster_info, field_value, writer, background)
}
}
}
impl<N, W> Burn<N, W> for MultiLineString
where
N: Num + Copy,
W: PixelWriter<N>,
{
fn burn<S: LineBurnStrategy>(&self, raster_info: &RasterInfo, field_value: N, writer: &mut W, background: N) {
let mut linedges = Vec::new();
for line in self {
extract_line(&mut linedges, line, raster_info);
}
if raster_info.xres != raster_info.yres || S::REQUIRES_DEDUP {
let mut cache = PixelCache::new(&linedges);
let mut line_writer = LineWriter::new(writer, &mut cache);
S::burn_line(&linedges, raster_info, field_value, &mut line_writer, background)
} else {
S::burn_line(&linedges, raster_info, field_value, writer, background)
}
}
}
fn handle_polygon<N, W, S>(
raster_info: &RasterInfo,
polyedges: &mut Vec<PolyEdge>,
linedges: Option<Vec<LineEdge>>,
pixel_cache: &mut Option<PixelCache>,
field_value: N,
writer: &mut W,
background: N,
) where
N: Num + Copy,
W: PixelWriter<N>,
S: LineBurnStrategy,
{
match (linedges, pixel_cache) {
(Some(lines), Some(cache)) => {
let mut line_writer = LineWriter::new(writer, cache);
S::burn_line(&lines, raster_info, field_value, &mut line_writer, background);
let mut fill_writer = FillWriter::new(writer, cache);
burn_polygon(polyedges, raster_info, field_value, &mut fill_writer, background);
}
(Some(lines), None) => {
S::burn_line(&lines, raster_info, field_value, writer, background);
burn_polygon(polyedges, raster_info, field_value, writer, background);
}
(None, _) => {
burn_polygon(polyedges, raster_info, field_value, writer, background);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{encoding::writers::PixelWriter, rasterization::burners::Standard};
use geo_types::{Line, MultiPoint, Point, Rect, Triangle, coord};
#[derive(Default)]
struct Collector {
cells: Vec<(usize, usize, f64)>,
}
impl PixelWriter<f64> for Collector {
fn write(&mut self, y: usize, x: usize, value: f64, _background: f64) {
self.cells.push((y, x, value));
}
}
fn raster_10() -> RasterInfo {
RasterInfo {
ncols: 10,
nrows: 10,
xmin: 0.0,
xmax: 10.0,
ymin: 0.0,
ymax: 10.0,
xres: 1.0,
yres: 1.0,
epsg: None,
}
}
fn burn(geom: Geometry<f64>) -> Vec<(usize, usize, f64)> {
let ri = raster_10();
let mut writer = Collector::default();
geom.burn::<Standard>(&ri, 1.0, &mut writer, 0.0);
writer.cells
}
#[test]
fn point_burns_exact_cell() {
let cells = burn(Geometry::Point(Point::new(2.5, 7.5)));
assert_eq!(cells, vec![(2, 2, 1.0)]);
}
#[test]
fn multipoint_burns_each_point() {
let mp = MultiPoint::new(vec![Point::new(1.5, 8.5), Point::new(5.5, 3.5)]);
let mut cells = burn(Geometry::MultiPoint(mp));
cells.sort_by_key(|&(y, x, _)| (y, x));
assert_eq!(cells, vec![(1, 1, 1.0), (6, 5, 1.0)]);
}
#[test]
fn linestring_burns_cells() {
let ls = LineString::from(vec![(1.0, 5.0), (6.0, 5.0)]);
let cells = burn(Geometry::LineString(ls));
assert!(!cells.is_empty(), "horizontal line should burn cells");
assert!(cells.iter().all(|&(y, _, _)| y == 5));
}
#[test]
fn multilinestring_burns_cells() {
let mls = MultiLineString::new(vec![
LineString::from(vec![(1.0, 8.0), (4.0, 8.0)]),
LineString::from(vec![(1.0, 2.0), (4.0, 2.0)]),
]);
let cells = burn(Geometry::MultiLineString(mls));
assert!(cells.iter().any(|&(y, _, _)| y == 2));
assert!(cells.iter().any(|&(y, _, _)| y == 8));
}
#[test]
fn line_burns_diagonal() {
let line = Line::new(coord! { x: 1.0, y: 1.0 }, coord! { x: 6.0, y: 6.0 });
let cells = burn(Geometry::Line(line));
assert!(!cells.is_empty(), "diagonal line should burn cells");
}
#[test]
fn polygon_fills_interior() {
let poly = Polygon::new(
LineString::from(vec![(2.0, 2.0), (6.0, 2.0), (6.0, 6.0), (2.0, 6.0), (2.0, 2.0)]),
vec![],
);
let cells = burn(Geometry::Polygon(poly));
assert!(cells.len() > 4, "square polygon should fill several cells");
}
#[test]
fn multipolygon_fills_both() {
let p = |x: f64, y: f64| {
Polygon::new(
LineString::from(vec![(x, y), (x + 2.0, y), (x + 2.0, y + 2.0), (x, y + 2.0), (x, y)]),
vec![],
)
};
let mp = MultiPolygon::new(vec![p(1.0, 1.0), p(6.0, 6.0)]);
let cells = burn(Geometry::MultiPolygon(mp));
assert!(cells.iter().any(|&(y, _, _)| y >= 6)); assert!(cells.iter().any(|&(y, _, _)| y <= 3)); }
#[test]
fn rect_fills_interior() {
let rect = Rect::new(coord! { x: 1.0, y: 1.0 }, coord! { x: 5.0, y: 5.0 });
let cells = burn(Geometry::Rect(rect));
assert!(cells.len() > 4, "rect should fill several cells");
}
#[test]
fn triangle_fills_interior() {
let tri = Triangle::new(
coord! { x: 1.0, y: 1.0 },
coord! { x: 6.0, y: 1.0 },
coord! { x: 1.0, y: 6.0 },
);
let cells = burn(Geometry::Triangle(tri));
assert!(!cells.is_empty(), "triangle should fill cells");
}
#[test]
fn geometry_collection_burns_all_members() {
let gc = GeometryCollection(vec![
Geometry::Point(Point::new(2.5, 7.5)),
Geometry::Polygon(Polygon::new(
LineString::from(vec![(5.0, 1.0), (8.0, 1.0), (8.0, 4.0), (5.0, 4.0), (5.0, 1.0)]),
vec![],
)),
]);
let cells = burn(Geometry::GeometryCollection(gc));
assert!(cells.contains(&(2, 2, 1.0)), "point member should burn");
assert!(cells.iter().any(|&(y, _, _)| y >= 6), "polygon member should fill");
}
}