1use crate::errors::MapEngineError;
3use crate::{
4 cmap::{Composite, HandleGet},
5 tiles::TILE_SIZE,
6};
7use gdal::raster::GdalType;
8use ndarray::{Array, Array3, Axis};
9use num_traits::{Num, NumCast};
10use std::fs::File;
11use std::io::{BufWriter, Write};
12use std::path::Path;
13
14pub struct RawPixels<P>
16where
17 P: GdalType + Copy + Num + NumCast,
18 P: ndarray::ScalarOperand,
19{
20 data: Array3<P>,
21 driver: Box<dyn driver::Style<P>>,
22}
23
24impl<P> RawPixels<P>
25where
26 P: GdalType + Copy + Num + NumCast,
27 P: ndarray::ScalarOperand,
28{
29 pub(super) fn new(data: Array3<P>, driver: &str) -> Self {
30 match driver {
31 driver::MBTILES => {
32 let driver = Box::new(driver::Mbtile {});
33
34 Self { data, driver }
35 }
36 _ => {
37 let driver = Box::new(driver::Generic {});
38 Self { data, driver }
39 }
40 }
41 }
42
43 pub fn style(
50 self,
51 cmap: Composite,
52 no_data_values: Vec<f64>,
53 ) -> Result<StyledPixels, MapEngineError>
54 where
55 P: std::fmt::Debug,
56 {
57 self.driver.style(&self, cmap, no_data_values)
58 }
59
60 pub fn as_array(&self) -> &Array3<P> {
61 &self.data
62 }
63}
64
65pub struct StyledPixels {
67 data: Array3<u8>,
68 driver: driver::Driver,
69}
70
71impl StyledPixels {
72 pub fn new(data: Array3<u8>, driver: driver::Driver) -> Self {
73 Self { data, driver }
74 }
75
76 pub fn into_png(self) -> Result<Vec<u8>, MapEngineError> {
77 let mut buffer = Vec::<u8>::new();
78 let mut w: BufWriter<&mut Vec<u8>> = BufWriter::new(buffer.as_mut());
79 {
80 let mut encoder = png::Encoder::new(&mut w, TILE_SIZE as u32, TILE_SIZE as u32);
81 encoder.set_color(png::ColorType::Rgba);
82 encoder.set_depth(png::BitDepth::Eight);
83 let mut writer = encoder.write_header()?;
84 match self.driver {
85 driver::Driver::Generic => writer.write_image_data(&self.data.into_raw_vec())?,
86 driver::Driver::Mbtile => {
87 writer.write_image_data(&self.data.into_iter().collect::<Vec<u8>>()[..])?
88 }
89 }
90 }
91 w.flush()?;
92 drop(w);
93 Ok(buffer)
94 }
95
96 pub fn write_to_disk(self, out_path: &Path) -> Result<(), MapEngineError> {
102 let png_data = self.into_png()?;
103 let mut file = File::create(out_path)?;
104 file.write_all(&png_data[..])?;
105 Ok(())
106 }
107
108 #[allow(dead_code)]
109 fn as_array(&self) -> &Array3<u8> {
110 &self.data
111 }
112
113 #[allow(dead_code)]
114 fn into_array(self) -> Array3<u8> {
115 self.data
116 }
117}
118
119impl Default for StyledPixels {
120 fn default() -> Self {
121 Self::new(
122 Array::zeros((4, TILE_SIZE, TILE_SIZE)),
123 driver::Driver::Generic,
124 )
125 }
126}
127
128pub(crate) mod driver {
129 use super::*;
130
131 pub struct Mbtile;
132 pub struct Generic;
133 pub const MBTILES: &str = "MBTiles";
134 pub enum Driver {
135 Mbtile,
136 Generic,
137 }
138
139 pub trait Style<P>
140 where
141 P: GdalType + Copy + Num + NumCast,
142 P: ndarray::ScalarOperand,
143 {
144 fn style(
145 &self,
146 arr: &RawPixels<P>,
147 cmap: Composite,
148 no_data_values: Vec<f64>,
149 ) -> Result<StyledPixels, MapEngineError>;
150 }
151
152 impl<P> Style<P> for Generic
153 where
154 P: GdalType + Copy + Num + NumCast,
155 P: ndarray::ScalarOperand,
156 {
157 fn style(
158 &self,
159 raw: &RawPixels<P>,
160 cmap: Composite,
161 no_data_values: Vec<f64>,
162 ) -> Result<StyledPixels, MapEngineError> {
163 let arr_f64 = unsafe { raw.data.raw_view().cast::<f64>().deref_into_view() };
164 let v = if cmap.is_contiguous() {
165 arr_f64
166 .lanes(Axis(0))
167 .into_iter()
168 .map(|v| cmap.get(v.as_slice().unwrap(), Some(&no_data_values)))
169 .flatten()
170 .collect()
171 } else {
172 arr_f64
173 .lanes(Axis(0))
174 .into_iter()
175 .map(|v| cmap.get(&v.to_vec(), Some(&no_data_values)))
176 .flatten()
177 .collect()
178 };
179 let arr = unsafe {
180 Array::from_shape_vec_unchecked((raw.data.shape()[1], raw.data.shape()[2], 4), v)
181 };
182 Ok(StyledPixels::new(arr, driver::Driver::Generic))
183 }
184 }
185
186 impl<P> Style<P> for Mbtile
187 where
188 P: GdalType + Copy + Num + NumCast,
189 P: ndarray::ScalarOperand,
190 {
191 fn style(
192 &self,
193 raw: &RawPixels<P>,
194 _: Composite,
195 _: Vec<f64>,
196 ) -> Result<StyledPixels, MapEngineError> {
197 Ok(StyledPixels::new(
198 raw.data.mapv(|v| v.to_u8().expect("Cannot parse to u8")),
200 driver::Driver::Mbtile,
201 ))
202 }
203 }
204}
205
206#[cfg(test)]
207mod test {
208 use super::*;
209 use crate::cmap::viridis;
210 use crate::raster::Raster;
211 use crate::tiles::Tile;
212 use gdal::Dataset;
213 use ndarray::{arr3, s};
214 use std::path::PathBuf;
215
216 #[test]
217 fn test_write() {
218 let path = PathBuf::from("src/tests/data/chile_optimised.tif");
219 let src = Dataset::open(&path).unwrap();
220 let band = src.rasterband(1).unwrap();
221 let no_data_value = band.no_data_value().unwrap_or(0.0);
222 let (vmin, vmax) = (0.0, 27412.0); let raster = Raster::new(path).unwrap();
225 let tile = Tile::new(304, 624, 10);
226 let arr: RawPixels<f64> = raster.read_tile(&tile, Some(&[1]), None).unwrap();
227 let styled = arr
228 .style(
229 Composite::new_gradient(vmin, vmax, &viridis),
230 vec![no_data_value],
231 )
232 .unwrap();
233 let out_path = format!("/tmp/tile_{}_{}_{}.png", tile.x, tile.y, tile.z);
234 let out_path = Path::new(&out_path);
235 styled.write_to_disk(out_path).unwrap();
236 }
237
238 #[test]
239 fn test_style_tile() {
240 let arr = RawPixels::new(arr3(&[[[0.0, 0.25], [0.5, 1.]]]), "");
242 let styled = arr
243 .style(Composite::new_gradient(0.0, 1., &viridis), vec![0.25])
244 .unwrap();
245 assert_eq!(
246 styled.as_array().slice(s![0, 0, ..]).to_vec(),
247 [68, 1, 84, 255]
248 );
249 assert_eq!(styled.as_array()[[0, 1, 3]], 0); }
251
252 #[test]
253 fn test_style_rgb_tile() {
254 let arr = RawPixels::new(
255 arr3(&[
256 [[1.0, 0.0], [0.0, 0.25]],
257 [[0.0, 1.0], [0.0, 0.0]],
258 [[0.0, 0.0], [1.0, 0.0]],
259 ]),
260 "",
261 );
262 let styled = arr
263 .style(
264 Composite::new_rgb(vec![0.0, 0.0, 0.0], vec![1.0, 1.0, 1.0]),
265 vec![0.25, 0.25, 0.25],
266 )
267 .unwrap();
268 assert_eq!(
269 styled.as_array().slice(s![0, 0, ..]).to_vec(),
270 [255, 0, 0, 255]
271 ); assert_eq!(
273 styled.as_array().slice(s![0, 1, ..]).to_vec(),
274 [0, 255, 0, 255]
275 ); assert_eq!(
277 styled.as_array().slice(s![1, 0, ..]).to_vec(),
278 [0, 0, 255, 255]
279 ); assert_eq!(styled.as_array().slice(s![1, 1, ..]).to_vec()[3], 0); }
282
283 #[test]
284 #[should_panic]
285 fn test_style_tile_with_gradient_fails() {
286 let arr = RawPixels::new(arr3(&[[[1.0, 0.0], [0.0, 0.25]]]), "");
287 arr.style(
289 Composite::new_gradient(0.0, 1., &viridis),
290 vec![0.25, 0.25, 0.25], )
292 .unwrap();
293 }
294
295 #[test]
296 #[should_panic]
297 fn test_style_rgb_tile_fails() {
298 let arr = RawPixels::new(
299 arr3(&[
300 [[1.0, 0.0], [0.0, 0.25]],
301 [[0.0, 1.0], [0.0, 0.0]],
302 [[0.0, 0.0], [1.0, 0.0]],
303 ]),
304 "",
305 );
306 arr.style(
307 Composite::new_rgb(vec![0.0, 0.0, 0.0], vec![1.0, 1.0, 1.0]),
308 vec![0.25], )
310 .unwrap();
311 }
312}