1use std::cmp::Ordering;
2
3use crate::error::{RusterizeError, RusterizeResult};
4use geo::{BoundingRect, Geometry, Rect, coord};
5use ndarray::Array3;
6use num_traits::Num;
7
8#[derive(Clone)]
10pub struct RasterInfo {
11 pub ncols: usize,
12 pub nrows: usize,
13 pub xmin: f64,
14 pub xmax: f64,
15 pub ymin: f64,
16 pub ymax: f64,
17 pub xres: f64,
18 pub yres: f64,
19 pub epsg: Option<u16>,
20}
21
22impl RasterInfo {
23 pub(crate) fn build_raster<N>(&self, bands: usize, background: N) -> Array3<N>
24 where
25 N: Num + Copy,
26 {
27 Array3::from_elem((bands, self.nrows, self.ncols), background)
28 }
29}
30
31#[derive(Default)]
36pub struct RasterInfoBuilder {
37 shape: Option<[usize; 2]>,
38 extent: Option<[f64; 4]>,
39 resolution: Option<[f64; 2]>,
40 tap: bool,
41 epsg: Option<u16>,
42}
43
44impl RasterInfoBuilder {
45 pub fn new() -> Self {
46 RasterInfoBuilder::default()
47 }
48
49 pub fn build(self) -> RusterizeResult<RasterInfo> {
51 match self.extent {
52 Some(extent) => {
53 let is_unspecified_extent = extent.iter().all(|x| matches!(x.total_cmp(&0.0), Ordering::Equal));
54 if is_unspecified_extent {
55 return Err(RusterizeError::ValueError("Unspecified extent (all zeros)."));
56 }
57 self.finalize(extent, false)
58 }
59 None => Err(RusterizeError::RuntimeError(
60 "Extent must be provided for construction. \
61 Use `build_with()` to infer extent from geometries.",
62 )),
63 }
64 }
65
66 pub fn build_with(self, geoms: &[Geometry<f64>]) -> RusterizeResult<RasterInfo> {
68 if self.extent.is_some() {
69 return Err(RusterizeError::RuntimeError(
70 "Extent must be inferred from geometries for construction. \
71 Use `build()` to provide a custom extent.",
72 ));
73 }
74
75 let bounds = geoms.iter().fold(None, |acc, geom| {
76 let bounds = geom.bounding_rect();
77
78 match (acc, bounds) {
79 (None, None) => None,
80 (None, Some(r)) | (Some(r), None) => Some(r),
81 (Some(r1), Some(r2)) => Some(Rect::new(
82 coord! { x: r1.min().x.min(r2.min().x), y: r1.min().y.min(r2.min().y) },
83 coord! { x: r1.max().x.max(r2.max().x), y: r1.max().y.max(r2.max().y) },
84 )),
85 }
86 });
87
88 if let Some(b) = bounds {
89 self.finalize([b.min().x, b.min().y, b.max().x, b.max().y], true)
90 } else {
91 Err(RusterizeError::RuntimeError("Cannot infer bounding box from geometry."))
92 }
93 }
94
95 fn finalize(
96 self,
97 [mut xmin, mut ymin, mut xmax, mut ymax]: [f64; 4],
98 inferred: bool,
99 ) -> RusterizeResult<RasterInfo> {
100 if self.shape.is_none() && self.resolution.is_none() {
101 return Err(RusterizeError::ValueError(
102 "Must set at least one of `shape` or `resolution`",
103 ));
104 }
105 if self.shape.is_some() && self.resolution.is_some() {
106 return Err(RusterizeError::ValueError(
107 "Shape and resolution are mutually exclusive; provide only one",
108 ));
109 }
110 let has_shape = self.shape.is_some();
111 let has_res = self.resolution.is_some();
112 let [mut nrows, mut ncols] = self.shape.unwrap_or_default();
113 let [mut xres, mut yres] = self.resolution.unwrap_or_default();
114
115 if has_shape && (nrows == 0 || ncols == 0) {
116 return Err(RusterizeError::ValueError("Shape values must be > 0."));
117 }
118
119 if has_res && (xres <= 0.0 || yres <= 0.0) {
120 return Err(RusterizeError::ValueError("Resolution values must be > 0."));
121 }
122
123 if inferred && !self.tap && has_res {
124 xmin -= xres / 2.0;
125 xmax += xres / 2.0;
126 ymin -= yres / 2.0;
127 ymax += yres / 2.0;
128 }
129
130 if !has_res {
131 xres = (xmax - xmin) / ncols as f64;
132 yres = (ymax - ymin) / nrows as f64;
133 } else if self.tap {
134 xmin = (xmin / xres).floor() * xres;
135 xmax = (xmax / xres).ceil() * xres;
136 ymin = (ymin / yres).floor() * yres;
137 ymax = (ymax / yres).ceil() * yres;
138 }
139
140 if !has_shape {
141 nrows = (0.5 + (ymax - ymin) / yres) as usize;
142 ncols = (0.5 + (xmax - xmin) / xres) as usize;
143 }
144
145 Ok(RasterInfo {
146 ncols,
147 nrows,
148 xmin,
149 xmax,
150 ymin,
151 ymax,
152 xres,
153 yres,
154 epsg: self.epsg,
155 })
156 }
157
158 pub fn shape(mut self, nrows: usize, ncols: usize) -> Self {
159 self.shape = Some([nrows, ncols]);
160 self
161 }
162
163 pub fn extent(mut self, xmin: f64, ymin: f64, xmax: f64, ymax: f64) -> Self {
164 self.extent = Some([xmin, ymin, xmax, ymax]);
165 self
166 }
167
168 pub fn resolution(mut self, xres: f64, yres: f64) -> Self {
169 self.resolution = Some([xres, yres]);
170 self
171 }
172
173 pub fn with_target_aligned_pixels(mut self) -> Self {
174 self.tap = true;
175 self
176 }
177
178 pub fn epsg(mut self, epsg: u16) -> Self {
179 self.epsg = Some(epsg);
180 self
181 }
182}