Skip to content

Commit a11a53a

Browse files
committed
Add a Dataset::read_as method that reads the 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 f3c9c4d commit a11a53a

File tree

4 files changed

+276
-7
lines changed

4 files changed

+276
-7
lines changed

src/dataset.rs

Lines changed: 258 additions & 4 deletions
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,10 @@ use crate::{
2021
};
2122

2223
use gdal_sys::{
23-
self, CPLErr, GDALAccess, GDALDatasetH, GDALMajorObjectH, OGRErr, OGRGeometryH, OGRLayerH,
24-
OGRwkbGeometryType,
24+
self, CPLErr, GDALAccess, GDALDatasetH, GDALMajorObjectH, GDALRWFlag, GDALRasterIOExtraArg,
25+
OGRErr, OGRGeometryH, OGRLayerH, OGRwkbGeometryType,
2526
};
26-
use libc::{c_double, c_int, c_uint};
27+
use libc::{c_double, c_int, c_uint, c_void};
2728

2829
#[cfg(all(major_ge_3, minor_ge_1))]
2930
use crate::raster::Group;
@@ -282,6 +283,23 @@ impl<'a> Default for LayerOptions<'a> {
282283
}
283284
}
284285

286+
// This defines multiple ways to layout an image in memory, based on GDAL Python bindings
287+
// which have either 'band' or 'pixel' interleave:
288+
// https://github.com/OSGeo/gdal/blob/301f31b9b74cd67edcdc555f7e7a58db87cbadb2/swig/include/gdal_array.i#L2300
289+
pub enum ImageInterleaving {
290+
/// This means the image is stored in memory with first the first band,
291+
/// then second band and so on
292+
Band,
293+
/// This means the image is stored in memory with first the value of all bands
294+
/// for the first pixel, then the same for second pixel and so on
295+
Pixel,
296+
}
297+
298+
pub enum BandSelection {
299+
Subset(Vec<i32>),
300+
All,
301+
}
302+
285303
// GDAL Docs state: The returned dataset should only be accessed by one thread at a time.
286304
// See: https://gdal.org/api/raster_c_api.html#_CPPv48GDALOpenPKc10GDALAccess
287305
// Additionally, VRT Datasets are not safe before GDAL 2.3.
@@ -741,6 +759,115 @@ impl Dataset {
741759
Ok(self.child_layer(c_layer))
742760
}
743761

762+
/// Read a [`Buffer<T>`] from this dataset, where `T` implements [`GdalType`].
763+
///
764+
/// # Arguments
765+
/// * `window` - the window position from top left
766+
/// * `window_size` - the window size (width, height). GDAL will interpolate data if `window_size` != `buffer_size`
767+
/// * `buffer_size` - the desired size of the 'Buffer' (width, height)
768+
/// * `e_resample_alg` - the resample algorithm used for the interpolation. Default: `NearestNeighbor`.
769+
/// * `interleaving`- The output buffer image layout (see `ImageInterleaving`)
770+
/// * `bands` - A subset of bands to select or BandSelection::All to read all bands
771+
///
772+
/// # Example
773+
///
774+
/// ```rust
775+
/// # fn main() -> gdal::errors::Result<()> {
776+
/// use gdal::{Dataset, ImageInterleaving, BandSelection};
777+
/// use gdal::raster::ResampleAlg;
778+
/// let dataset = Dataset::open("fixtures/m_3607824_se_17_1_20160620_sub.tif")?;
779+
/// let size = 2;
780+
/// let buf = dataset.read_as::<u8>((0, 0), dataset.raster_size(), (size, size), Some(ResampleAlg::Bilinear), ImageInterleaving::Pixel, BandSelection::All)?;
781+
/// assert_eq!(buf.size, (size, size, dataset.raster_count() as usize));
782+
/// assert_eq!(buf.data, [103, 116, 101, 169, 92, 108, 94, 163, 92, 112, 93, 179, 89, 109, 91, 181]);
783+
/// let buf = dataset.read_as::<u8>((0, 0), dataset.raster_size(), (size, size), Some(ResampleAlg::Bilinear), ImageInterleaving::Band, BandSelection::All)?;
784+
/// assert_eq!(buf.data, [103, 92, 92, 89, 116, 108, 112, 109, 101, 94, 93, 91, 169, 163, 179, 181]);
785+
/// # Ok(())
786+
/// # }
787+
/// ```
788+
pub fn read_as<T: Copy + GdalType>(
789+
&self,
790+
window: (isize, isize),
791+
window_size: (usize, usize),
792+
buffer_size: (usize, usize),
793+
e_resample_alg: Option<ResampleAlg>,
794+
interleaving: ImageInterleaving,
795+
bands: BandSelection,
796+
) -> Result<Buffer3D<T>> {
797+
let resample_alg = e_resample_alg.unwrap_or(ResampleAlg::NearestNeighbour);
798+
799+
let mut options: GDALRasterIOExtraArg = RasterIOExtraArg {
800+
e_resample_alg: resample_alg,
801+
..Default::default()
802+
}
803+
.into();
804+
805+
let options_ptr: *mut GDALRasterIOExtraArg = &mut options;
806+
807+
let (mut bands, band_count) = match bands {
808+
BandSelection::Subset(bands) => {
809+
let band_count = bands.len();
810+
(bands, band_count)
811+
}
812+
BandSelection::All => {
813+
let band_count = self.raster_count() as usize;
814+
let bands = (1_i32..band_count as i32 + 1_i32).collect();
815+
(bands, band_count)
816+
}
817+
};
818+
819+
let pixels = buffer_size.0 * buffer_size.1 * band_count;
820+
let mut data: Vec<T> = Vec::with_capacity(pixels);
821+
let size_t = size_of::<T>() as i64;
822+
823+
let (pixel_space, line_space, band_space) = match interleaving {
824+
ImageInterleaving::Band => (0, 0, 0),
825+
ImageInterleaving::Pixel => (
826+
size_t * band_count as i64,
827+
buffer_size.0 as i64 * size_t * band_count as i64,
828+
size_t,
829+
),
830+
};
831+
832+
// Safety: the GDALRasterIOEx writes
833+
// exactly pixel elements into the slice, before we
834+
// read from this slice. This paradigm is suggested
835+
// in the rust std docs
836+
// (https://doc.rust-lang.org/std/vec/struct.Vec.html#examples-18)
837+
let rv = unsafe {
838+
gdal_sys::GDALDatasetRasterIOEx(
839+
self.c_dataset,
840+
GDALRWFlag::GF_Read,
841+
window.0 as c_int,
842+
window.1 as c_int,
843+
window_size.0 as c_int,
844+
window_size.1 as c_int,
845+
data.as_mut_ptr() as *mut c_void,
846+
buffer_size.0 as c_int,
847+
buffer_size.1 as c_int,
848+
T::gdal_ordinal(),
849+
band_count as i32,
850+
bands.as_mut_ptr() as *mut c_int,
851+
pixel_space,
852+
line_space,
853+
band_space,
854+
options_ptr,
855+
)
856+
};
857+
if rv != CPLErr::CE_None {
858+
return Err(_last_cpl_err(rv));
859+
}
860+
861+
unsafe {
862+
data.set_len(pixels);
863+
};
864+
865+
Ok(Buffer3D {
866+
size: (buffer_size.0, buffer_size.1, band_count),
867+
data,
868+
})
869+
}
870+
744871
/// Set the [`Dataset`]'s affine transformation; also called a _geo-transformation_.
745872
///
746873
/// This is like a linear transformation preserves points, straight lines and planes.
@@ -1327,4 +1454,131 @@ mod tests {
13271454
let mut ds = Dataset::open(fixture("roads.geojson")).unwrap();
13281455
assert!(ds.start_transaction().is_err());
13291456
}
1457+
1458+
#[test]
1459+
fn test_dataset_read_as_pixel_interleaving() {
1460+
let ds = Dataset::open(fixture("m_3607824_se_17_1_20160620_sub.tif")).unwrap();
1461+
print!("{:?}", ds.raster_size());
1462+
let (width, height) = (4, 7);
1463+
let band_count = ds.raster_count() as usize;
1464+
1465+
// We compare a single dataset.read_as() to reading band-by-band using
1466+
// band.read_as()
1467+
let ds_buf = ds
1468+
.read_as::<u8>(
1469+
(0, 0),
1470+
(width, height),
1471+
(width, height),
1472+
Some(ResampleAlg::Bilinear),
1473+
ImageInterleaving::Pixel,
1474+
BandSelection::All,
1475+
)
1476+
.unwrap();
1477+
assert_eq!(ds_buf.size, (width, height, band_count));
1478+
1479+
for band_index in 0..band_count {
1480+
let band = ds.rasterband(band_index as isize + 1).unwrap();
1481+
let band_buf = band
1482+
.read_as::<u8>(
1483+
(0, 0),
1484+
(width, height),
1485+
(width, height),
1486+
Some(ResampleAlg::Bilinear),
1487+
)
1488+
.unwrap();
1489+
assert_eq!(band_buf.size, (width, height));
1490+
for i in 0..height {
1491+
for j in 0..width {
1492+
assert_eq!(
1493+
band_buf.data[i * width + j],
1494+
ds_buf.data[i * width * band_count + j * band_count + band_index],
1495+
);
1496+
}
1497+
}
1498+
}
1499+
}
1500+
1501+
#[test]
1502+
fn test_dataset_read_as_band_interleaving() {
1503+
let ds = Dataset::open(fixture("m_3607824_se_17_1_20160620_sub.tif")).unwrap();
1504+
let size: (usize, usize) = (4, 7);
1505+
let band_count = ds.raster_count() as usize;
1506+
// We compare a single dataset.read_as() to reading band-by-band using
1507+
// band.read_as()
1508+
let ds_buf = ds
1509+
.read_as::<u8>(
1510+
(0, 0),
1511+
size,
1512+
size,
1513+
Some(ResampleAlg::Bilinear),
1514+
ImageInterleaving::Band,
1515+
BandSelection::All,
1516+
)
1517+
.unwrap();
1518+
assert_eq!(ds_buf.size, (size.0, size.1, band_count));
1519+
1520+
for band_index in 0..band_count {
1521+
let band = ds.rasterband(band_index as isize + 1).unwrap();
1522+
let band_buf = band
1523+
.read_as::<u8>((0, 0), size, size, Some(ResampleAlg::Bilinear))
1524+
.unwrap();
1525+
assert_eq!(band_buf.size, size);
1526+
assert_eq!(
1527+
band_buf.data,
1528+
ds_buf.data[band_index * size.0 * size.1..(band_index + 1) * size.0 * size.1]
1529+
);
1530+
}
1531+
}
1532+
1533+
#[test]
1534+
fn test_dataset_read_as_band_selection() {
1535+
let ds = Dataset::open(fixture("m_3607824_se_17_1_20160620_sub.tif")).unwrap();
1536+
let size: (usize, usize) = (4, 7);
1537+
// We compare a single dataset.read_as() to reading band-by-band using
1538+
// band.read_as()
1539+
let ds_buf = ds
1540+
.read_as::<u8>(
1541+
(0, 0),
1542+
size,
1543+
size,
1544+
Some(ResampleAlg::Bilinear),
1545+
ImageInterleaving::Band,
1546+
BandSelection::Subset(vec![1, 3]),
1547+
)
1548+
.unwrap();
1549+
assert_eq!(ds_buf.size, (size.0, size.1, 2));
1550+
1551+
for (i, band_index) in vec![1, 3].iter().enumerate() {
1552+
let band = ds.rasterband(*band_index as isize).unwrap();
1553+
let band_buf = band
1554+
.read_as::<u8>((0, 0), size, size, Some(ResampleAlg::Bilinear))
1555+
.unwrap();
1556+
assert_eq!(band_buf.size, size);
1557+
assert_eq!(
1558+
band_buf.data,
1559+
ds_buf.data[i * size.0 * size.1..(i + 1) * size.0 * size.1]
1560+
);
1561+
}
1562+
}
1563+
1564+
#[test]
1565+
fn test_dataset_read_as_buffer_size() {
1566+
let ds = Dataset::open(fixture("m_3607824_se_17_1_20160620_sub.tif")).unwrap();
1567+
let size: (usize, usize) = (4, 7);
1568+
let buffer_size: (usize, usize) = (8, 14);
1569+
let band_count = ds.raster_count() as usize;
1570+
let ds_buf = ds
1571+
.read_as::<u8>(
1572+
(0, 0),
1573+
size,
1574+
buffer_size,
1575+
Some(ResampleAlg::Bilinear),
1576+
ImageInterleaving::Band,
1577+
BandSelection::All,
1578+
)
1579+
.unwrap();
1580+
// We only assert that we get the right buffer size back because checking for explicit
1581+
// values is convoluted since GDAL handles the decimation by doing some interpolation
1582+
assert_eq!(ds_buf.size, (buffer_size.0, buffer_size.1, band_count));
1583+
}
13301584
}

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ pub mod version;
120120
pub mod vsi;
121121

122122
pub use dataset::{
123-
Dataset, DatasetOptions, GdalOpenFlags, GeoTransform, GeoTransformEx, LayerIterator,
124-
LayerOptions, Transaction,
123+
BandSelection, Dataset, DatasetOptions, GdalOpenFlags, GeoTransform, GeoTransformEx,
124+
ImageInterleaving, LayerIterator, LayerOptions, Transaction,
125125
};
126126
pub use driver::{Driver, DriverManager};
127127
pub use gcp::{Gcp, GcpRef};

src/raster/mod.rs

Lines changed: 4 additions & 1 deletion
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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,18 @@ impl<T: GdalType> Buffer<T> {
472472

473473
pub type ByteBuffer = Buffer<u8>;
474474

475+
#[derive(Debug)]
476+
pub struct Buffer3D<T: GdalType> {
477+
pub size: (usize, usize, usize),
478+
pub data: Vec<T>,
479+
}
480+
481+
impl<T: GdalType> Buffer3D<T> {
482+
pub fn new(size: (usize, usize, usize), data: Vec<T>) -> Buffer3D<T> {
483+
Buffer3D { size, data }
484+
}
485+
}
486+
475487
/// Extra options used to read a raster.
476488
///
477489
/// For documentation, see `gdal_sys::GDALRasterIOExtraArg`.

0 commit comments

Comments
 (0)