Skip to content

Commit 9cdcb52

Browse files
committed
Add a Dataset::read_as method that reads a whole image at once
This is similar to RasterBand::read_as, but it is reading the whole image at once. Similar to how GDAL has RasterIO on both the dataset and band level.
1 parent 84fbaf3 commit 9cdcb52

File tree

4 files changed

+151
-4
lines changed

4 files changed

+151
-4
lines changed

CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
- Added `Dataset::read_as`
6+
7+
- <https://github.com/georust/gdal/pull/374>
8+
59
- Added `CslStringList::add_string`
610

711
- <https://github.com/georust/gdal/pull/364>

src/dataset.rs

+131-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use ptr::null_mut;
22
use std::convert::TryInto;
3-
use std::mem::MaybeUninit;
3+
use std::mem::{size_of, MaybeUninit};
44
use std::{
55
ffi::NulError,
66
ffi::{CStr, CString},
@@ -12,6 +12,7 @@ use std::{
1212
use crate::cpl::CslStringList;
1313
use crate::errors::*;
1414
use crate::raster::RasterCreationOption;
15+
use crate::raster::{Buffer3D, GdalType, RasterIOExtraArg, ResampleAlg};
1516
use crate::utils::{_last_cpl_err, _last_null_pointer_err, _path_to_c_string, _string};
1617
use crate::vector::{sql, Geometry, OwnedLayer};
1718
use crate::{
@@ -20,10 +21,11 @@ use crate::{
2021
};
2122

2223
use gdal_sys::{
23-
self, CPLErr, GDALAccess, GDALDatasetH, GDALMajorObjectH, OGRErr, OGRLayerH, OGRwkbGeometryType,
24+
self, CPLErr, GDALAccess, GDALDatasetH, GDALMajorObjectH, GDALRWFlag, GDALRasterIOExtraArg,
25+
OGRErr, OGRLayerH, OGRwkbGeometryType,
2426
};
2527
use gdal_sys::{GDALFlushCache, OGRGeometryH};
26-
use libc::{c_double, c_int, c_uint};
28+
use libc::{c_double, c_int, c_uint, c_void};
2729

2830
#[cfg(all(major_ge_3, minor_ge_1))]
2931
use crate::raster::Group;
@@ -702,6 +704,94 @@ impl Dataset {
702704
Ok(self.child_layer(c_layer))
703705
}
704706

707+
/// Read a [`Buffer<T>`] from this dataset, where `T` implements [`GdalType`].
708+
///
709+
/// # Arguments
710+
/// * `window` - the window position from top left
711+
/// * `window_size` - the window size (GDAL will interpolate data if `window_size` != `buffer_size`)
712+
/// * `buffer_size` - the desired size of the 'Buffer'
713+
/// * `e_resample_alg` - the resample algorithm used for the interpolation. Default: `NearestNeighbor`.
714+
///
715+
/// The returned buffer contains the image data in HWC order.
716+
///
717+
/// # Example
718+
///
719+
/// ```rust
720+
/// # fn main() -> gdal::errors::Result<()> {
721+
/// use gdal::Dataset;
722+
/// use gdal::raster::ResampleAlg;
723+
/// let dataset = Dataset::open("fixtures/m_3607824_se_17_1_20160620_sub.tif")?;
724+
/// let size = 2;
725+
/// let buf = dataset.read_as::<u8>((0, 0), dataset.raster_size(), (size, size), Some(ResampleAlg::Bilinear))?;
726+
/// assert_eq!(buf.size, (size, size, dataset.raster_count() as usize));
727+
/// assert_eq!(buf.data, [103, 116, 101, 169, 92, 108, 94, 163, 92, 112, 93, 179, 89, 109, 91, 181]);
728+
/// # Ok(())
729+
/// # }
730+
/// ```
731+
pub fn read_as<T: Copy + GdalType>(
732+
&self,
733+
window: (isize, isize),
734+
window_size: (usize, usize),
735+
buffer_size: (usize, usize),
736+
e_resample_alg: Option<ResampleAlg>,
737+
) -> Result<Buffer3D<T>> {
738+
let band_count = self.raster_count() as usize;
739+
let pixels = buffer_size.0 * buffer_size.1 * band_count;
740+
let mut data: Vec<T> = Vec::with_capacity(pixels);
741+
742+
let resample_alg = e_resample_alg.unwrap_or(ResampleAlg::NearestNeighbour);
743+
744+
let mut options: GDALRasterIOExtraArg = RasterIOExtraArg {
745+
e_resample_alg: resample_alg,
746+
..Default::default()
747+
}
748+
.into();
749+
750+
let options_ptr: *mut GDALRasterIOExtraArg = &mut options;
751+
752+
let mut bands: Vec<i32> = (1_i32..band_count as i32 + 1_i32).collect();
753+
754+
let size_t = size_of::<T>() as i64;
755+
// Safety: the GDALDatasetRasterIOEx writes
756+
// exactly pixel elements into the slice, before we
757+
// read from this slice. This paradigm is suggested
758+
// in the rust std docs
759+
// (https://doc.rust-lang.org/std/vec/struct.Vec.html#examples-18)
760+
let rv = unsafe {
761+
gdal_sys::GDALDatasetRasterIOEx(
762+
self.c_dataset,
763+
GDALRWFlag::GF_Read,
764+
window.0 as c_int,
765+
window.1 as c_int,
766+
window_size.0 as c_int,
767+
window_size.1 as c_int,
768+
data.as_mut_ptr() as *mut c_void,
769+
buffer_size.0 as c_int,
770+
buffer_size.1 as c_int,
771+
T::gdal_ordinal(),
772+
band_count as i32,
773+
bands.as_mut_ptr() as *mut c_int,
774+
// We want to read a HWC
775+
size_t * band_count as i64,
776+
buffer_size.1 as i64 * size_t * band_count as i64,
777+
size_t,
778+
options_ptr,
779+
)
780+
};
781+
if rv != CPLErr::CE_None {
782+
return Err(_last_cpl_err(rv));
783+
}
784+
785+
unsafe {
786+
data.set_len(pixels);
787+
};
788+
789+
Ok(Buffer3D {
790+
size: (buffer_size.0, buffer_size.1, band_count),
791+
data,
792+
})
793+
}
794+
705795
/// Set the [`Dataset`]'s affine transformation; also called a _geo-transformation_.
706796
///
707797
/// This is like a linear transformation preserves points, straight lines and planes.
@@ -1288,4 +1378,42 @@ mod tests {
12881378
let mut ds = Dataset::open(fixture("roads.geojson")).unwrap();
12891379
assert!(ds.start_transaction().is_err());
12901380
}
1381+
1382+
#[test]
1383+
fn test_dataset_read_as() {
1384+
let ds = Dataset::open(fixture("m_3607824_se_17_1_20160620_sub.tif")).unwrap();
1385+
let size: usize = 4;
1386+
let band_count = ds.raster_count() as usize;
1387+
// Compare reading the whole dataset at once to reading each band individually
1388+
let ds_buf = ds
1389+
.read_as::<u8>(
1390+
(0, 0),
1391+
ds.raster_size(),
1392+
(size, size),
1393+
Some(ResampleAlg::Bilinear),
1394+
)
1395+
.unwrap();
1396+
assert_eq!(ds_buf.size, (size, size, band_count));
1397+
1398+
for band_index in 0..band_count {
1399+
let band = ds.rasterband(band_index as isize + 1).unwrap();
1400+
let band_buf = band
1401+
.read_as::<u8>(
1402+
(0, 0),
1403+
ds.raster_size(),
1404+
(size, size),
1405+
Some(ResampleAlg::Bilinear),
1406+
)
1407+
.unwrap();
1408+
assert_eq!(band_buf.size, (size, size));
1409+
for i in 0..size {
1410+
for j in 0..size {
1411+
assert_eq!(
1412+
band_buf.data[i * size + j],
1413+
ds_buf.data[i * size * band_count + j * band_count + band_index],
1414+
);
1415+
}
1416+
}
1417+
}
1418+
}
12911419
}

src/raster/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ pub use rasterband::{
9090
PaletteInterpretation, RasterBand, RgbaEntry, StatisticsAll, StatisticsMinMax,
9191
};
9292
pub use rasterize::{rasterize, BurnSource, MergeAlgorithm, OptimizeMode, RasterizeOptions};
93-
pub use types::{AdjustedValue, Buffer, ByteBuffer, GdalDataType, GdalType, ResampleAlg};
93+
pub use types::{
94+
AdjustedValue, Buffer, Buffer3D, ByteBuffer, GdalDataType, GdalType, RasterIOExtraArg,
95+
ResampleAlg,
96+
};
9497
pub use warp::reproject;
9598

9699
/// Key/value pair for passing driver-specific creation options to

src/raster/types.rs

+12
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,18 @@ impl<T: GdalType> Buffer<T> {
460460

461461
pub type ByteBuffer = Buffer<u8>;
462462

463+
#[derive(Debug)]
464+
pub struct Buffer3D<T: GdalType> {
465+
pub size: (usize, usize, usize),
466+
pub data: Vec<T>,
467+
}
468+
469+
impl<T: GdalType> Buffer3D<T> {
470+
pub fn new(size: (usize, usize, usize), data: Vec<T>) -> Buffer3D<T> {
471+
Buffer3D { size, data }
472+
}
473+
}
474+
463475
/// Extra options used to read a raster.
464476
///
465477
/// For documentation, see `gdal_sys::GDALRasterIOExtraArg`.

0 commit comments

Comments
 (0)