use crate::error::{RusterizeError, RusterizeResult};
use geo::{BoundingRect, Geometry, Rect, coord};
use ndarray::Array3;
use num_traits::Num;
#[derive(Clone)]
pub struct RasterInfo {
pub ncols: usize,
pub nrows: usize,
pub xmin: f64,
pub xmax: f64,
pub ymin: f64,
pub ymax: f64,
pub xres: f64,
pub yres: f64,
pub epsg: Option<u16>,
}
impl RasterInfo {
pub(crate) fn build_raster<N>(&self, bands: usize, background: N) -> Array3<N>
where
N: Num + Copy,
{
Array3::from_elem((bands, self.nrows, self.ncols), background)
}
}
#[derive(Default)]
pub struct RasterInfoBuilder {
shape: Option<[usize; 2]>,
extent: Option<[f64; 4]>,
resolution: Option<[f64; 2]>,
tap: bool,
epsg: Option<u16>,
}
impl RasterInfoBuilder {
pub fn new() -> Self {
RasterInfoBuilder::default()
}
pub fn build(self) -> RusterizeResult<RasterInfo> {
match self.extent {
Some(extent) => self.finalize(extent, false),
None => Err(RusterizeError::RuntimeError(
"Extent must be provided for construction. \
Use `build_with()` to infer extent from geometries.",
)),
}
}
pub fn build_with(self, geoms: &[Geometry<f64>]) -> RusterizeResult<RasterInfo> {
if self.extent.is_some() {
return Err(RusterizeError::RuntimeError(
"Extent must be inferred from geometries for construction. \
Use `build()` to provide a custom extent.",
));
}
let bounds = geoms.iter().fold(None, |acc, geom| {
let bounds = geom.bounding_rect();
match (acc, bounds) {
(None, None) => None,
(None, Some(r)) | (Some(r), None) => Some(r),
(Some(r1), Some(r2)) => Some(Rect::new(
coord! { x: r1.min().x.min(r2.min().x), y: r1.min().y.min(r2.min().y) },
coord! { x: r1.max().x.max(r2.max().x), y: r1.max().y.max(r2.max().y) },
)),
}
});
if let Some(b) = bounds {
self.finalize([b.min().x, b.min().y, b.max().x, b.max().y], true)
} else {
return Err(RusterizeError::RuntimeError("Cannot infer bounding box from geometry."));
}
}
fn finalize(
self,
[mut xmin, mut ymin, mut xmax, mut ymax]: [f64; 4],
inferred: bool,
) -> RusterizeResult<RasterInfo> {
if self.shape.is_none() && self.resolution.is_none() {
return Err(RusterizeError::ValueError(
"Must set at least one of `shape` or `resolution`",
));
}
let has_shape = self.shape.is_some();
let has_res = self.resolution.is_some();
let [mut nrows, mut ncols] = self.shape.unwrap_or_default();
let [mut xres, mut yres] = self.resolution.unwrap_or_default();
if inferred && !self.tap && has_res {
xmin -= xres / 2.0;
xmax += xres / 2.0;
ymin -= yres / 2.0;
ymax += yres / 2.0;
}
if !has_res {
xres = (xmax - xmin) / ncols as f64;
yres = (ymax - ymin) / nrows as f64;
} else if self.tap {
xmin = (xmin / xres).floor() * xres;
xmax = (xmax / xres).ceil() * xres;
ymin = (ymin / yres).floor() * yres;
ymax = (ymax / yres).ceil() * yres;
}
if !has_shape {
nrows = (0.5 + (ymax - ymin) / yres) as usize;
ncols = (0.5 + (xmax - xmin) / xres) as usize;
}
Ok(RasterInfo {
ncols,
nrows,
xmin,
xmax,
ymin,
ymax,
xres,
yres,
epsg: self.epsg,
})
}
pub fn shape(mut self, nrows: usize, ncols: usize) -> Self {
self.shape = Some([nrows, ncols]);
self
}
pub fn extent(mut self, xmin: f64, ymin: f64, xmax: f64, ymax: f64) -> Self {
self.extent = Some([xmin, ymin, xmax, ymax]);
self
}
pub fn resolution(mut self, xres: f64, yres: f64) -> Self {
self.resolution = Some([xres, yres]);
self
}
pub fn with_target_align_pixel(mut self) -> Self {
self.tap = true;
self
}
pub fn epsg(mut self, epsg: u16) -> Self {
self.epsg = Some(epsg);
self
}
}