1pub mod pixels;
3
4use crate::{
5 affine::GeoTransform,
6 errors::MapEngineError,
7 tiles::{Tile, TILE_SIZE},
8 windows::intersection,
9 windows::Window,
10};
11use gdal::{
12 raster::{GdalType, RasterBand, ResampleAlg},
13 spatial_ref::SpatialRef,
14 Dataset,
15};
16use ndarray::{s, Array, Array2, Array3};
17use num_traits::{Num, NumCast};
18pub use pixels::{driver::MBTILES, RawPixels, StyledPixels};
19use std::cmp;
20use std::path::PathBuf;
21
22#[derive(Debug, Clone)]
24pub struct Raster {
25 path: PathBuf,
26 geo: GeoTransform,
27 epsg_code: i32,
28 driver_name: String,
29 raster_count: isize,
30 raster_size: (usize, usize),
31}
32
33impl Raster {
34 pub fn new(path: PathBuf) -> Result<Self, MapEngineError> {
39 let src = Dataset::open(&path)?;
40 let geo = src.geo_transform()?;
41 let geo = GeoTransform::from_gdal(&geo);
42 Ok(Self {
43 path,
44 geo,
45 epsg_code: src.spatial_ref()?.auth_code()?,
46 driver_name: src.driver().short_name(),
47 raster_count: src.raster_count(),
48 raster_size: src.raster_size(),
49 })
50 }
51
52 pub fn from_src(path: PathBuf, src: &Dataset) -> Result<Self, MapEngineError> {
57 let geo = src.geo_transform()?;
58 let geo = GeoTransform::from_gdal(&geo);
59 Ok(Self {
60 path,
61 geo,
62 epsg_code: src.spatial_ref()?.auth_code()?,
63 driver_name: src.driver().short_name(),
64 raster_count: src.raster_count(),
65 raster_size: src.raster_size(),
66 })
67 }
68
69 pub fn read_tile<P>(
95 &self,
96 tile: &Tile,
97 bands: Option<&[isize]>,
98 e_resample_alg: Option<ResampleAlg>,
99 ) -> Result<RawPixels<P>, MapEngineError>
100 where
101 P: GdalType + Copy + Num + NumCast,
102 P: ndarray::ScalarOperand,
103 {
104 let src = Dataset::open(&self.path)?;
105 let driver_name = self.driver_name();
106 let geo = self.geo();
108 let epsg_code = self.epsg_code;
109 let (mut win, is_skewed) = tile.to_window(self)?;
110
111 let tile_bounds_xy = tile.bounds_xy();
112 if is_skewed {
113 win = win * 2f64.sqrt();
114 };
115
116 let all_bands: Vec<_> = (1..=self.raster_count()).collect();
117 let mut bands = bands.unwrap_or(&all_bands);
118 if driver_name == MBTILES {
119 bands = &all_bands;
120 }
121
122 let mut container_arr = Array3::<P>::zeros((bands.len(), TILE_SIZE, TILE_SIZE));
123
124 for (out_idx, band_index) in bands.iter().enumerate() {
125 let band = src.rasterband(*band_index)?;
126
127 let band_data = try_boundless(
128 &src,
129 &band,
130 &win,
131 geo,
132 epsg_code,
133 tile_bounds_xy,
134 is_skewed,
135 e_resample_alg,
136 );
137 let band_data = if let Some(d) = band_data {
138 d
139 } else {
140 try_overview(
141 &band,
142 &win,
143 geo,
145 epsg_code,
146 tile_bounds_xy,
147 is_skewed,
148 e_resample_alg,
149 )?
150 };
151
152 container_arr
153 .slice_mut(s![out_idx, .., ..])
154 .assign(&band_data);
155 }
156
157 if driver_name == MBTILES {
159 container_arr.swap_axes(0, 1);
160 container_arr.swap_axes(1, 2);
161 }
162 Ok(RawPixels::new(container_arr, driver_name))
163 }
164
165 pub fn geo(&self) -> &GeoTransform {
166 &self.geo
167 }
168
169 pub fn spatial_ref(&self) -> Result<SpatialRef, MapEngineError> {
170 Ok(SpatialRef::from_epsg(self.epsg_code as u32)?)
171 }
172
173 pub fn driver_name(&self) -> &str {
174 &self.driver_name
175 }
176
177 pub fn raster_count(&self) -> isize {
179 self.raster_count
180 }
181
182 pub fn raster_size(&self) -> (usize, usize) {
183 self.raster_size
184 }
185
186 pub fn intersects(&self, tile: &Tile) -> Result<bool, MapEngineError> {
187 let (raster_w, raster_h) = self.raster_size();
188 let raster_win = Window::new(0, 0, raster_w, raster_h);
189 Ok(intersection(&[raster_win, tile.to_window(self)?.0]).is_some())
190 }
191}
192
193fn array_to_mem_dataset<N>(
194 arr: Array2<N>,
195 transform: &GeoTransform,
196 crs: i32,
197 fname: &str,
198) -> Result<Dataset, MapEngineError>
199where
200 N: Clone + GdalType + Copy,
201{
202 let shape = arr.shape();
203 let count = 1;
204 let height = shape[0];
205 let width = shape[1];
206
207 let driver = gdal::Driver::get("MEM")?;
208 let mut dataset = driver
211 .create_with_band_type::<N, _>(fname, height as isize, width as isize, count as isize)
212 .unwrap();
213 let gt = transform.to_tuple();
214 let gt = [gt.0, gt.1, gt.2, gt.3, gt.4, gt.5];
215 dataset.set_geo_transform(>)?;
216 dataset.set_spatial_ref(&SpatialRef::from_epsg(crs as u32)?)?;
217
218 let mut band = dataset.rasterband(1)?;
219
220 let v: Vec<N> = Array::from_iter(arr.into_iter()).to_vec();
221 let buff = gdal::raster::Buffer::<N>::new((height, width), v);
222 band.write((0, 0), (height, width), &buff)?;
223 drop(band);
224 Ok(dataset)
225}
226
227fn reproject<N>(
228 source: Array2<N>,
229 src_transform: &GeoTransform,
230 src_crs: i32,
231 destination: Array2<N>,
232 dst_transform: &GeoTransform,
233 dst_crs: i32,
234) -> Result<Array2<N>, MapEngineError>
235where
236 N: GdalType + Copy,
237{
238 let dst_shape = &destination.shape();
239 let dst_shape = (dst_shape[0], dst_shape[1]);
240 let src_dataset = array_to_mem_dataset(source, src_transform, src_crs, "/tmp/src.tif")?;
241 let dst_dataset = array_to_mem_dataset(destination, dst_transform, dst_crs, "/tmp/dst.tif")?;
242 gdal::raster::reproject(&src_dataset, &dst_dataset)?;
243
244 let dst_band = dst_dataset.rasterband(1)?;
245
246 dst_band
247 .read_as_array::<N>(
248 (0, 0),
249 dst_shape,
250 (TILE_SIZE, TILE_SIZE),
251 Some(gdal::raster::ResampleAlg::NearestNeighbour),
252 )
253 .map_err(From::from)
254}
255
256fn read_and_reproject<N>(
257 band: &RasterBand,
258 win: &Window,
259 geo: &GeoTransform,
260 epsg_code: i32,
261 tile_bounds_xy: (f64, f64, f64, f64),
262 e_resample_alg: Option<ResampleAlg>,
263) -> Result<Array2<N>, MapEngineError>
264where
265 N: GdalType + Copy + Num,
266{
267 let win_geo = win.geotransform(geo).to_gdal();
268
269 let d = band.read_as::<N>(
270 (win.col_off, win.row_off),
271 (win.width as usize, win.height as usize),
272 (win.width as usize, win.height as usize),
273 e_resample_alg,
274 )?;
275
276 let arr = Array::from_iter(d.data).into_shape((win.width as usize, win.height as usize))?;
277
278 let res_x = win_geo.geo[1];
279 let res_y = win_geo.geo[5];
280 let (min_x, max_y, max_x, min_y) = tile_bounds_xy;
281 let mercator_geo = &GeoTransform::new(&[min_x, res_x, 0.0, max_y, 0.0, res_y]);
282 let dst_cols = ((max_x - min_x) / res_x) as usize;
283 let dst_rows = ((max_y - min_y) / -res_y) as usize;
284 let dst_shape = (dst_cols, dst_rows);
285 let dst_arr = Array2::<N>::zeros(dst_shape);
286 reproject(arr, &win_geo, epsg_code, dst_arr, mercator_geo, 3857)
287}
288
289fn try_overview<N>(
290 band: &RasterBand,
291 win: &Window,
292 geo: &GeoTransform,
294 epsg_code: i32,
295 tile_bounds_xy: (f64, f64, f64, f64),
296 is_skewed: bool,
297 e_resample_alg: Option<ResampleAlg>,
298) -> Result<Array2<N>, MapEngineError>
299where
300 N: GdalType + Copy + Num,
301{
302 if is_skewed {
303 read_and_reproject(band, win, geo, epsg_code, tile_bounds_xy, e_resample_alg)
304 } else {
305 band.read_as_array::<N>(
306 (win.col_off, win.row_off),
309 (win.width as usize, win.height as usize),
310 (TILE_SIZE, TILE_SIZE),
311 e_resample_alg,
312 )
313 .map_err(From::from)
314 }
315}
316
317#[allow(clippy::too_many_arguments)]
319fn try_boundless<N>(
320 src: &Dataset,
321 band: &RasterBand,
322 win: &Window,
323 geo: &GeoTransform,
324 epsg_code: i32,
325 tile_bounds_xy: (f64, f64, f64, f64),
326 is_skewed: bool,
327 e_resample_alg: Option<ResampleAlg>,
328) -> Option<Array2<N>>
329where
330 N: GdalType + Copy + Num + NumCast,
331 N: ndarray::ScalarOperand,
332{
333 let (raster_w, raster_h) = src.raster_size();
334 let raster_win = Window::new(0, 0, raster_w, raster_h);
335 let inter = intersection(&[raster_win, *win]);
336 if let Some(inter) = inter {
337 if (inter.height >= win.height || inter.width >= win.width)
338 && (win.col_off >= 0 && win.row_off >= 0)
339 && (win.row_off + win.height as isize) < raster_win.height as isize
340 && (win.col_off + win.width as isize) < raster_win.width as isize
341 {
342 return None;
344 }
345 };
346
347 let mut container_arr = match band.no_data_value() {
348 None => Array2::<N>::zeros((TILE_SIZE, TILE_SIZE)),
349 Some(val) => {
350 let val = N::from(val)?;
351 Array2::<N>::ones((TILE_SIZE, TILE_SIZE)) * val
352 }
353 };
354
355 if inter.is_none() {
356 return Some(container_arr);
357 };
358
359 let inter = inter.unwrap();
360 let factor = (
362 win.width as f64 / inter.width as f64,
363 win.height as f64 / inter.height as f64,
364 );
365 let data = if is_skewed {
366 read_and_reproject(band, &inter, geo, epsg_code, tile_bounds_xy, e_resample_alg)
367 } else {
368 let into_shape = (
369 (TILE_SIZE as f64 / factor.0).floor() as usize,
370 (TILE_SIZE as f64 / factor.1).floor() as usize,
371 );
372 band.read_as_array::<N>(
373 (inter.col_off, inter.row_off),
374 (inter.width, inter.height),
375 into_shape,
376 e_resample_alg,
377 )
378 .map_err(From::from)
379 }
380 .unwrap_or_else(|_| panic!("Cannot read window {:?} from {:?}", inter, src));
381
382 let col_off = if win.col_off < 0 {
383 (TILE_SIZE as f64 * (win.col_off as f64 / win.width as f64)).trunc() as isize
384 } else {
385 0
386 };
387 let row_off = if win.row_off < 0 {
388 (TILE_SIZE as f64 * (win.row_off as f64 / win.height as f64)).trunc() as isize
389 } else {
390 0
391 };
392 let (row_range, col_range) = if is_skewed {
393 let row_range = ((TILE_SIZE - data.shape()[0]) as isize)
394 ..cmp::min(row_off.abs() + data.shape()[0] as isize, TILE_SIZE as isize);
395 let col_range = ((TILE_SIZE - data.shape()[1]) as isize)
396 ..cmp::min(col_off.abs() + data.shape()[1] as isize, TILE_SIZE as isize);
397 (row_range, col_range)
398 } else {
399 let row_range =
400 row_off.abs()..cmp::min(row_off.abs() + data.shape()[0] as isize, TILE_SIZE as isize);
401 let col_range =
402 col_off.abs()..cmp::min(col_off.abs() + data.shape()[1] as isize, TILE_SIZE as isize);
403 (row_range, col_range)
404 };
405 container_arr
406 .slice_mut(s![row_range, col_range])
407 .assign(&data);
408 Some(container_arr)
409}
410
411#[cfg(test)]
412mod test {
413 use super::*;
414 use std::path::Path;
415
416 #[test]
417 fn test_try_boundless() {
418 let path = Path::new("src/tests/data/chile_optimised.tif");
419 let src = Dataset::open(path).unwrap();
420 let epsg_code = src.spatial_ref().unwrap().auth_code().unwrap();
421 let geo = src.geo_transform().unwrap();
422 let geo = GeoTransform::from_gdal(&geo);
423 let band = src.rasterband(1).unwrap();
424 let win = Window::new(0, 0, 2048, 2048); let tile_bounds_xy = win.bounds(&geo);
427 let arr: Array2<f64> = try_boundless(
428 &src,
429 &band,
430 &win,
431 &geo,
432 epsg_code,
433 tile_bounds_xy,
434 false,
435 Some(ResampleAlg::Average),
436 )
437 .unwrap();
438 assert_eq!(arr.shape(), &[256, 256]);
439 let expected = Array::from_iter([2251., 2242., 0., 2251., 2259., 0., 0., 0., 0.])
440 .into_shape((3, 3))
441 .unwrap();
442 assert_eq!(arr.slice(s![62..65, 62..65]), expected);
443 let win = Window::new(-1024, -512, 2048, 2048); let tile_bounds_xy = win.bounds(&geo);
445 let arr: Array2<f64> = try_boundless(
446 &src,
447 &band,
448 &win,
449 &geo,
450 epsg_code,
451 tile_bounds_xy,
452 false,
453 Some(ResampleAlg::Average),
454 )
455 .unwrap();
456 assert_eq!(arr.shape(), &[256, 256]);
457 assert_eq!(arr.slice(s![126..129, 190..193]), expected);
458 let win = Window::new(256, 256, 2048, 2048);
460 let tile_bounds_xy = win.bounds(&geo);
461 let arr: Array2<f64> = try_boundless(
462 &src,
463 &band,
464 &win,
465 &geo,
466 epsg_code,
467 tile_bounds_xy,
468 false,
469 Some(ResampleAlg::Average),
470 )
471 .unwrap();
472 assert_eq!(arr.shape(), &[256, 256]);
473 let expected = Array::from_iter([2251., 2242., 0., 2251., 2259., 0., 0., 0., 0.])
474 .into_shape((3, 3))
475 .unwrap();
476 assert_eq!(arr.slice(s![30..33, 30..33]), expected);
477 let win = Window::new(-1792, 256, 2048, 2048);
479 let tile_bounds_xy = win.bounds(&geo);
480 let arr: Array2<f64> = try_boundless(
481 &src,
482 &band,
483 &win,
484 &geo,
485 epsg_code,
486 tile_bounds_xy,
487 false,
488 Some(ResampleAlg::Average),
489 )
490 .unwrap();
491 assert_eq!(arr.shape(), &[256, 256]);
492 let expected = Array::from_iter([0., 4645., 4527., 0., 4706., 4297., 0., 0., 0.])
493 .into_shape((3, 3))
494 .unwrap();
495 assert_eq!(arr.slice(s![30..33, 223..226]), expected);
496
497 let win = Window::new(0, -512, 512, 512 * 2);
499 let tile_bounds_xy = win.bounds(&geo);
500 let arr: Option<Array2<f64>> = try_boundless(
501 &src,
502 &band,
503 &win,
504 &geo,
505 epsg_code,
506 tile_bounds_xy,
507 false,
508 Some(ResampleAlg::Average),
509 );
510 assert!(arr.is_some());
511
512 let win = Window::new(0, 256, 512, 512 * 2);
513 let tile_bounds_xy = win.bounds(&geo);
514 let arr: Option<Array2<f64>> = try_boundless(
515 &src,
516 &band,
517 &win,
518 &geo,
519 epsg_code,
520 tile_bounds_xy,
521 false,
522 Some(ResampleAlg::Average),
523 );
524 assert!(arr.is_some());
525 }
526
527 #[test]
528 fn test_non_intersecting_returns_no_data_tile() {
529 let path = Path::new("src/tests/data/categorical_optimised.tif");
530 let src = Dataset::open(path).unwrap();
531 let epsg_code = src.spatial_ref().unwrap().auth_code().unwrap();
532 let geo = src.geo_transform().unwrap();
533 let geo = GeoTransform::from_gdal(&geo);
534 let mut band = src.rasterband(1).unwrap();
535
536 let win = Window::new(2048, 2048, 4096, 4096);
538 let tile_bounds_xy = win.bounds(&geo);
539
540 band.set_no_data_value(2f64).unwrap();
541 let arr: Array2<f32> = try_boundless(
544 &src,
545 &band,
546 &win,
547 &geo,
548 epsg_code,
549 tile_bounds_xy,
550 false,
551 None,
552 )
553 .unwrap();
554
555 let expected: Array2<f32> = Array2::ones((256, 256)) * 2f32;
557 assert_eq!(arr, expected);
558
559 }
565
566 #[test]
567 fn test_generic_function_returns_expected_type() {
568 let path = Path::new("src/tests/data/chile_optimised.tif");
569 let src = Dataset::open(path).unwrap();
570 let epsg_code = src.spatial_ref().unwrap().auth_code().unwrap();
571 let geo = src.geo_transform().unwrap();
572 let geo = GeoTransform::from_gdal(&geo);
573 let band = src.rasterband(1).unwrap();
574 let win = Window::new(-1792, 256, 2048, 2048);
575 let tile_bounds_xy = win.bounds(&geo);
576
577 let arr: Array2<f32> = try_boundless(
578 &src,
579 &band,
580 &win,
581 &geo,
582 epsg_code,
583 tile_bounds_xy,
584 false,
585 Some(ResampleAlg::Average),
586 )
587 .unwrap();
588
589 let expected = Array::from_iter([
591 0.0f32, 4645.0f32, 4527.0f32, 0.0f32, 4706.0f32, 4297.0f32, 0.0f32, 0.0f32, 0.0f32,
592 ])
593 .into_shape((3, 3))
594 .unwrap();
595 assert_eq!(arr.slice(s![30..33, 223..226]), expected);
596
597 let arr: Array2<u32> = try_boundless(
598 &src,
599 &band,
600 &win,
601 &geo,
602 epsg_code,
603 tile_bounds_xy,
604 false,
605 Some(ResampleAlg::Average),
606 )
607 .unwrap();
608
609 let expected = Array::from_iter([
611 0u32, 4645u32, 4527u32, 0u32, 4706u32, 4297u32, 0u32, 0u32, 0u32,
612 ])
613 .into_shape((3, 3))
614 .unwrap();
615 assert_eq!(arr.slice(s![30..33, 223..226]), expected);
616
617 }
620
621 #[test]
622 fn test_band_type_does_not_fit_in_resulting_type() {
623 let path = Path::new("src/tests/data/chile_optimised.tif");
624 let src = Dataset::open(path).unwrap();
625 let epsg_code = src.spatial_ref().unwrap().auth_code().unwrap();
626 let geo = src.geo_transform().unwrap();
627 let geo = GeoTransform::from_gdal(&geo);
628 let band = src.rasterband(1).unwrap();
629 let win = Window::new(-1792, 256, 2048, 2048);
630 let tile_bounds_xy = win.bounds(&geo);
631
632 let arr: Array2<u8> = try_boundless(
633 &src,
634 &band,
635 &win,
636 &geo,
637 epsg_code,
638 tile_bounds_xy,
639 false,
640 Some(ResampleAlg::Average),
641 )
642 .unwrap();
643
644 let expected = Array::from_iter([0u8, 255, 255, 0, 255, 255, 0, 0, 0])
648 .into_shape((3, 3))
649 .unwrap();
650
651 assert_eq!(arr.slice(s![30..33, 223..226]), expected);
653
654 let not_expected = Array::from_iter([
657 0u8,
658 4645u32 as u8,
659 4527u32 as u8,
660 0u8,
661 4706u32 as u8,
662 4297u32 as u8,
663 0u8,
664 0u8,
665 0u8,
666 ])
667 .into_shape((3, 3))
668 .unwrap();
669
670 assert_ne!(arr.slice(s![30..33, 223..226]), not_expected);
671 }
672
673 #[test]
674 fn test_intersects() {
675 let path = Path::new("src/tests/data/chile_optimised.tif");
676 let raster = Raster::new(path.into()).unwrap();
677 let tile1 = Tile::new(304, 624, 10);
678 assert!(raster.intersects(&tile1).unwrap());
679 let tile1 = Tile::new(303, 624, 10);
680 assert!(!raster.intersects(&tile1).unwrap());
681 }
682}