From 39249c12123777e3f9d0565168e47a31dbbd29e0 Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Sun, 17 Dec 2023 14:58:09 -0500 Subject: [PATCH 01/14] Implemented `RasterBand::read_block` using the `Buffer` API, enabling block reading without `array` feature. `Rasterband::read_block` renamed `Rasterband::read_block_as_array` to be consistent with `read_as` vs. `read_as_array`. --- CHANGES.md | 5 +++++ src/raster/mdarray.rs | 2 +- src/raster/rasterband.rs | 48 +++++++++++++++++++++++++++++++++++----- src/raster/tests.rs | 6 ++--- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b88b2160..938d0e1b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,11 @@ ## Unreleased +- **Breaking**: `Rasterband::read_block` renamed `Rasterband::read_block_as_array` to be consistent with `read_as` vs. `read_as_array`. +- Implemented `RasterBand::read_block` using the `Buffer` API, enabling block reading without `array` feature. + + - + - Defers the gdal_i.lib missing message until after the pkg-config check and outputs pkg-config metadata in case of a static build. - diff --git a/src/raster/mdarray.rs b/src/raster/mdarray.rs index d88c5036..8097c0ab 100644 --- a/src/raster/mdarray.rs +++ b/src/raster/mdarray.rs @@ -248,7 +248,7 @@ impl<'a> MDArray<'a> { #[cfg(feature = "ndarray")] #[cfg_attr(docsrs, doc(cfg(feature = "array")))] - /// Read a 'Array2' from this band. T implements 'GdalType'. + /// Read a [`ArrayD`] from this band. T implements [`GdalType`]. /// /// # Arguments /// * `window` - the window position from top left diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index c3b462e1..afc38a19 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -522,9 +522,7 @@ impl<'a> RasterBand<'a> { self.read_as::((0, 0), (size.0, size.1), (size.0, size.1), None) } - #[cfg(feature = "ndarray")] - #[cfg_attr(docsrs, doc(cfg(feature = "array")))] - /// Read a [`Array2`] from a [`Dataset`] block, where `T` implements [`GdalType`]. + /// Read a [`Buffer`] from a [`Dataset`] block, where `T` implements [`GdalType`]. /// /// # Arguments /// * `block_index` - the block index @@ -550,11 +548,11 @@ impl<'a> RasterBand<'a> { /// let dataset = Dataset::open("fixtures/m_3607824_se_17_1_20160620_sub.tif")?; /// let band1 = dataset.rasterband(1)?; /// let arr = band1.read_block::((0, 0))?; - /// assert_eq!(arr.shape(), &[300, 6]); + /// assert_eq!(arr.size, (300, 6)); /// # Ok(()) /// # } /// ``` - pub fn read_block(&self, block_index: (usize, usize)) -> Result> { + pub fn read_block(&self, block_index: (usize, usize)) -> Result> { if T::gdal_ordinal() != self.band_type() as u32 { return Err(GdalError::BadArgument( "result array type must match band data type".to_string(), @@ -582,6 +580,46 @@ impl<'a> RasterBand<'a> { data.set_len(pixels); }; + Ok(Buffer::new(size, data)) + } + + #[cfg(feature = "ndarray")] + #[cfg_attr(docsrs, doc(cfg(feature = "array")))] + /// Read a [`Array2`] from a [`Dataset`] block, where `T` implements [`GdalType`]. + /// + /// # Arguments + /// * `block_index` - the block index + /// + /// # Notes + /// Blocks indexes start from 0 and are of form (x, y), where x grows in the horizontal direction. + /// + /// The matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis). + /// + /// The block size of the band can be determined using [`RasterBand::block_size`]. + /// The last blocks in both directions can be smaller. + /// [`RasterBand::actual_block_size`] will report the correct dimensions of a block. + /// + /// # Errors + /// If the block index is not valid, GDAL will return an error. + /// + /// # Example + /// + /// ```rust, no_run + /// # fn main() -> gdal::errors::Result<()> { + /// use gdal::Dataset; + /// + /// let dataset = Dataset::open("fixtures/m_3607824_se_17_1_20160620_sub.tif")?; + /// let band1 = dataset.rasterband(1)?; + /// let arr = band1.read_block_as_array::((0, 0))?; + /// assert_eq!(arr.shape(), &[300, 6]); + /// # Ok(()) + /// # } + /// ``` + pub fn read_block_as_array( + &self, + block_index: (usize, usize), + ) -> Result> { + let Buffer { size, data } = self.read_block(block_index)?; Array2::from_shape_vec((size.1, size.0), data).map_err(Into::into) } diff --git a/src/raster/tests.rs b/src/raster/tests.rs index b4550d0a..a8560774 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -364,7 +364,7 @@ fn test_read_block_as_array() { let block_index = (0, 0); let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rasterband = dataset.rasterband(band_index).unwrap(); - let result = rasterband.read_block::(block_index); + let result = rasterband.read_block_as_array::(block_index); assert!(result.is_ok()); } @@ -375,7 +375,7 @@ fn test_read_block_dimension() { let block = (0, 0); let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rasterband = dataset.rasterband(band_index).unwrap(); - let array = rasterband.read_block::(block).unwrap(); + let array = rasterband.read_block_as_array::(block).unwrap(); assert_eq!(array.dim(), (27, 100)); } @@ -386,7 +386,7 @@ fn test_read_block_data() { let block = (0, 0); let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rasterband = dataset.rasterband(band_index).unwrap(); - let array = rasterband.read_block::(block).unwrap(); + let array = rasterband.read_block_as_array::(block).unwrap(); assert_eq!(array[[0, 0]], 0); assert_eq!(array[[0, 1]], 9); assert_eq!(array[[0, 98]], 24); From c344b938ac1ef88205c3094caccacd7d30bcaedb Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Sun, 17 Dec 2023 17:28:59 -0500 Subject: [PATCH 02/14] Converted all `ndarray`-dependent I/O methods in Rasterband to use internal `Buffer` type, and implemented conversion traits between the types. --- CHANGES.md | 6 +- src/raster/buffer.rs | 86 +++++++++++++++++++++++++++++ src/raster/mod.rs | 28 +++++----- src/raster/rasterband.rs | 116 ++++++--------------------------------- src/raster/tests.rs | 28 +++++----- 5 files changed, 133 insertions(+), 131 deletions(-) create mode 100644 src/raster/buffer.rs diff --git a/CHANGES.md b/CHANGES.md index 938d0e1b..facdde17 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,9 @@ ## Unreleased -- **Breaking**: `Rasterband::read_block` renamed `Rasterband::read_block_as_array` to be consistent with `read_as` vs. `read_as_array`. -- Implemented `RasterBand::read_block` using the `Buffer` API, enabling block reading without `array` feature. - +- Added ability to convert between `Buffer` and `ndarray::Array2`. +- **Breaking**: Removed `Rasterband::read_as_array`, changed signature of `Rasterband::read_block` to return a `Buffer`. +- **Breaking**: `Rasterband::write_block` now takes a `&Buffer`. - - Defers the gdal_i.lib missing message until after the pkg-config check and outputs pkg-config metadata in case of a static build. diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs new file mode 100644 index 00000000..04014dcf --- /dev/null +++ b/src/raster/buffer.rs @@ -0,0 +1,86 @@ +use crate::raster::GdalType; + +#[cfg(feature = "ndarray")] +use ndarray::Array2; + +/// A 2-D array backed by it's `size` (cols, rows) and a row-major `Vec` and it's dimensions. +#[derive(Debug, Clone, PartialEq)] +pub struct Buffer { + pub size: (usize, usize), + pub data: Vec, +} + +impl Buffer { + /// Construct a new buffer from `size` (`(cols, rows)`) and `Vec`. + /// + /// # Notes + /// Assumes `size.0 * size.1 == data.len()`. + pub fn new(size: (usize, usize), data: Vec) -> Buffer { + debug_assert!( + size.0 * size.1 == data.len(), + "size {:?} does not match length {}", + size, + data.len() + ); + Buffer { size, data } + } + + #[cfg(feature = "ndarray")] + /// Convert `self` into an [`ndarray::Array2`]. + pub fn to_array(self) -> crate::errors::Result> { + // Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis) + Ok(Array2::from_shape_vec( + (self.size.1, self.size.0), + self.data, + )?) + } +} + +pub type ByteBuffer = Buffer; + +#[cfg(feature = "ndarray")] +impl TryFrom> for Array2 { + type Error = crate::errors::GdalError; + + fn try_from(value: Buffer) -> Result { + value.to_array() + } +} + +#[cfg(feature = "ndarray")] +impl From> for Buffer { + fn from(value: Array2) -> Self { + // Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis) + let shape = value.shape().to_vec(); + let data: Vec = if value.is_standard_layout() { + value.into_raw_vec() + } else { + value.iter().copied().collect() + }; + + Buffer::new((shape[1], shape[0]), data) + } +} + +#[cfg(feature = "ndarray")] +#[cfg(test)] +mod tests { + use crate::raster::Buffer; + use ndarray::Array2; + + #[test] + fn convert_to() { + let b = Buffer::new((5, 10), (0..5 * 10).collect()); + let a = b.clone().to_array().unwrap(); + let b2: Buffer<_> = a.into(); + assert_eq!(b, b2); + } + + #[test] + fn convert_from() { + let a = Array2::from_shape_fn((10, 5), |(y, x)| y as i32 * 10 + x as i32); + let b: Buffer<_> = a.clone().into(); + let a2 = b.to_array().unwrap(); + assert_eq!(a, a2); + } +} diff --git a/src/raster/mod.rs b/src/raster/mod.rs index d921bc9b..16aa2c97 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -74,27 +74,30 @@ //! ... //! ``` -#[cfg(all(major_ge_3, minor_ge_1))] -mod mdarray; -pub mod processing; -mod rasterband; -mod rasterize; -mod types; -mod warp; - +pub use buffer::{Buffer, ByteBuffer}; #[cfg(all(major_ge_3, minor_ge_1))] pub use mdarray::{ Attribute, Dimension, ExtendedDataType, ExtendedDataTypeClass, Group, MDArray, MdStatisticsAll, }; pub use rasterband::{ - Buffer, ByteBuffer, CmykEntry, ColorEntry, ColorInterpretation, ColorTable, GrayEntry, - Histogram, HlsEntry, PaletteInterpretation, RasterBand, ResampleAlg, RgbaEntry, StatisticsAll, - StatisticsMinMax, + CmykEntry, ColorEntry, ColorInterpretation, ColorTable, GrayEntry, Histogram, HlsEntry, + PaletteInterpretation, RasterBand, ResampleAlg, RgbaEntry, StatisticsAll, StatisticsMinMax, }; pub use rasterize::{rasterize, BurnSource, MergeAlgorithm, OptimizeMode, RasterizeOptions}; pub use types::{AdjustedValue, GdalDataType, GdalType}; pub use warp::reproject; +mod buffer; +#[cfg(all(major_ge_3, minor_ge_1))] +mod mdarray; +pub mod processing; +mod rasterband; +mod rasterize; +#[cfg(test)] +mod tests; +mod types; +mod warp; + /// Key/value pair for passing driver-specific creation options to /// [`Driver::create_with_band_type_wth_options`](crate::Driver::create_with_band_type_with_options`). /// @@ -104,6 +107,3 @@ pub struct RasterCreationOption<'a> { pub key: &'a str, pub value: &'a str, } - -#[cfg(test)] -mod tests; diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index afc38a19..30f2699d 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -11,15 +11,13 @@ use gdal_sys::{ GDALRasterIOExtraArg, GDALSetColorEntry, GDALSetDefaultHistogramEx, GDALSetRasterColorTable, }; use libc::c_int; -use std::ffi::CString; +use std::ffi::{c_void, CString}; use std::fmt::{Debug, Display, Formatter}; use std::marker::PhantomData; use std::str::FromStr; -#[cfg(feature = "ndarray")] -use ndarray::Array2; - use crate::errors::*; +use crate::raster::buffer::Buffer; use crate::raster::ResampleAlg::{ Average, Bilinear, Cubic, CubicSpline, Gauss, Lanczos, Mode, NearestNeighbour, }; @@ -395,7 +393,7 @@ impl<'a> RasterBand<'a> { window.1 as c_int, window_size.0 as c_int, window_size.1 as c_int, - buffer.as_mut_ptr() as GDALRasterBandH, + buffer.as_mut_ptr() as *mut c_void, size.0 as c_int, size.1 as c_int, T::gdal_ordinal(), @@ -468,7 +466,7 @@ impl<'a> RasterBand<'a> { window.1 as c_int, window_size.0 as c_int, window_size.1 as c_int, - data.as_mut_ptr() as GDALRasterBandH, + data.as_mut_ptr() as *mut c_void, size.0 as c_int, size.1 as c_int, T::gdal_ordinal(), @@ -488,34 +486,6 @@ impl<'a> RasterBand<'a> { Ok(Buffer { size, data }) } - #[cfg(feature = "ndarray")] - #[cfg_attr(docsrs, doc(cfg(feature = "array")))] - /// Read a [`Array2`] from this band, where `T` implements [`GdalType`]. - /// - /// # Arguments - /// * `window` - the window position from top left - /// * `window_size` - the window size (GDAL will interpolate data if window_size != array_size) - /// * `array_size` - the desired size of the 'Array' - /// * `e_resample_alg` - the resample algorithm used for the interpolation - /// - /// # Note - /// The Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis). - pub fn read_as_array( - &self, - window: (isize, isize), - window_size: (usize, usize), - array_size: (usize, usize), - e_resample_alg: Option, - ) -> Result> { - let data = self.read_as::(window, window_size, array_size, e_resample_alg)?; - - // Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis) - Ok(Array2::from_shape_vec( - (array_size.1, array_size.0), - data.data, - )?) - } - /// Read the full band as a [`Buffer`], where `T` implements [`GdalType`]. pub fn read_band_as(&self) -> Result> { let size = self.size(); @@ -563,13 +533,12 @@ impl<'a> RasterBand<'a> { let pixels = size.0 * size.1; let mut data: Vec = Vec::with_capacity(pixels); - //let no_data: let rv = unsafe { gdal_sys::GDALReadBlock( self.c_rasterband, block_index.0 as c_int, block_index.1 as c_int, - data.as_mut_ptr() as GDALRasterBandH, + data.as_mut_ptr() as *mut c_void, ) }; if rv != CPLErr::CE_None { @@ -583,49 +552,7 @@ impl<'a> RasterBand<'a> { Ok(Buffer::new(size, data)) } - #[cfg(feature = "ndarray")] - #[cfg_attr(docsrs, doc(cfg(feature = "array")))] - /// Read a [`Array2`] from a [`Dataset`] block, where `T` implements [`GdalType`]. - /// - /// # Arguments - /// * `block_index` - the block index - /// - /// # Notes - /// Blocks indexes start from 0 and are of form (x, y), where x grows in the horizontal direction. - /// - /// The matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis). - /// - /// The block size of the band can be determined using [`RasterBand::block_size`]. - /// The last blocks in both directions can be smaller. - /// [`RasterBand::actual_block_size`] will report the correct dimensions of a block. - /// - /// # Errors - /// If the block index is not valid, GDAL will return an error. - /// - /// # Example - /// - /// ```rust, no_run - /// # fn main() -> gdal::errors::Result<()> { - /// use gdal::Dataset; - /// - /// let dataset = Dataset::open("fixtures/m_3607824_se_17_1_20160620_sub.tif")?; - /// let band1 = dataset.rasterband(1)?; - /// let arr = band1.read_block_as_array::((0, 0))?; - /// assert_eq!(arr.shape(), &[300, 6]); - /// # Ok(()) - /// # } - /// ``` - pub fn read_block_as_array( - &self, - block_index: (usize, usize), - ) -> Result> { - let Buffer { size, data } = self.read_block(block_index)?; - Array2::from_shape_vec((size.1, size.0), data).map_err(Into::into) - } - - #[cfg(feature = "ndarray")] - #[cfg_attr(docsrs, doc(cfg(feature = "array")))] - /// Write a [`Array2`] from a [`Dataset`] block, where `T` implements [`GdalType`]. + /// Write a [`Buffer`] from a [`Dataset`] block, where `T` implements [`GdalType`]. /// /// # Arguments /// * `block_index` - the block index @@ -647,8 +574,7 @@ impl<'a> RasterBand<'a> { /// ```rust, no_run /// # fn main() -> gdal::errors::Result<()> { /// use gdal::DriverManager; - /// use gdal::raster::RasterCreationOption; - /// use ndarray::Array2; + /// use gdal::raster::{Buffer, RasterCreationOption}; /// /// let driver = DriverManager::get_driver_by_name("GTiff").unwrap(); /// let options = [ @@ -674,15 +600,15 @@ impl<'a> RasterBand<'a> { /// &options, /// )?; /// let mut band1 = dataset.rasterband(1)?; - /// let arr = Array2::from_shape_fn((16, 16), |(y, x)| y as u16 * 16 + x as u16); - /// band1.write_block((0, 0), arr)?; + /// let arr = Buffer::new((16, 16), (0..16*16).collect()); + /// band1.write_block((0, 0), &arr.into())?; /// # Ok(()) /// # } /// ``` pub fn write_block( &mut self, block_index: (usize, usize), - block: Array2, + block: &Buffer, ) -> Result<()> { if T::gdal_ordinal() != self.band_type() as u32 { return Err(GdalError::BadArgument( @@ -690,13 +616,16 @@ impl<'a> RasterBand<'a> { )); } - let mut data = block.into_raw_vec(); let rv = unsafe { gdal_sys::GDALWriteBlock( self.c_rasterband, block_index.0 as c_int, block_index.1 as c_int, - data.as_mut_ptr() as GDALRasterBandH, + // This parameter is marked as `* mut c_void` because the C/C++ API for some reason + // doesn't mark it as `const void *`. From code inspection starting at the link + // below, it appears to be a read-only array. + // https://github.com/OSGeo/gdal/blob/b5d004fb9e3fb576b3ccf5f9740531b0bfa87ef4/gcore/gdalrasterband.cpp#L688 + block.data.as_ptr() as *mut c_void, ) }; if rv != CPLErr::CE_None { @@ -727,7 +656,7 @@ impl<'a> RasterBand<'a> { window.1 as c_int, window_size.0 as c_int, window_size.1 as c_int, - buffer.data.as_ptr() as GDALRasterBandH, + buffer.data.as_ptr() as *mut c_void, buffer.size.0 as c_int, buffer.size.1 as c_int, T::gdal_ordinal(), @@ -1213,19 +1142,6 @@ impl<'a> MajorObject for RasterBand<'a> { impl<'a> Metadata for RasterBand<'a> {} -pub struct Buffer { - pub size: (usize, usize), - pub data: Vec, -} - -impl Buffer { - pub fn new(size: (usize, usize), data: Vec) -> Buffer { - Buffer { size, data } - } -} - -pub type ByteBuffer = Buffer; - /// Represents a color interpretation of a RasterBand #[derive(Debug, PartialEq, Eq)] pub enum ColorInterpretation { diff --git a/src/raster/tests.rs b/src/raster/tests.rs index a8560774..1ef5a4ed 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -338,7 +338,7 @@ fn test_read_raster_as_array() { let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rb = dataset.rasterband(band_index).unwrap(); let values = rb - .read_as_array::( + .read_as::( (left, top), (window_size_x, window_size_y), (array_size_x, array_size_y), @@ -353,7 +353,7 @@ fn test_read_raster_as_array() { [171, 189, 192], ]); - assert_eq!(values, data); + assert_eq!(values.to_array().unwrap(), data); assert_eq!(rb.band_type(), GdalDataType::UInt8); } @@ -364,7 +364,7 @@ fn test_read_block_as_array() { let block_index = (0, 0); let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rasterband = dataset.rasterband(band_index).unwrap(); - let result = rasterband.read_block_as_array::(block_index); + let result = rasterband.read_block::(block_index); assert!(result.is_ok()); } @@ -375,8 +375,8 @@ fn test_read_block_dimension() { let block = (0, 0); let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rasterband = dataset.rasterband(band_index).unwrap(); - let array = rasterband.read_block_as_array::(block).unwrap(); - assert_eq!(array.dim(), (27, 100)); + let buff = rasterband.read_block::(block).unwrap(); + assert_eq!(buff.size, (100, 27)); } #[test] @@ -386,11 +386,11 @@ fn test_read_block_data() { let block = (0, 0); let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rasterband = dataset.rasterband(band_index).unwrap(); - let array = rasterband.read_block_as_array::(block).unwrap(); - assert_eq!(array[[0, 0]], 0); - assert_eq!(array[[0, 1]], 9); - assert_eq!(array[[0, 98]], 24); - assert_eq!(array[[0, 99]], 51); + let buf = rasterband.read_block::(block).unwrap(); + assert_eq!(buf.data[0], 0); + assert_eq!(buf.data[1], 9); + assert_eq!(buf.data[98], 24); + assert_eq!(buf.data[99], 51); } #[test] @@ -427,12 +427,12 @@ fn test_write_block() { let block_22 = Array2::from_shape_fn((16, 16), |(y, x)| y as u16 * 16 + x as u16 + 4000u16); let mut band = dataset.rasterband(1).unwrap(); - band.write_block((0, 0), block_11.clone()).unwrap(); - band.write_block((0, 1), block_12.clone()).unwrap(); + band.write_block((0, 0), &block_11.clone().into()).unwrap(); + band.write_block((0, 1), &block_12.clone().into()).unwrap(); block_11.append(Axis(1), block_21.view()).unwrap(); - band.write_block((1, 0), block_21).unwrap(); + band.write_block((1, 0), &block_21.into()).unwrap(); block_12.append(Axis(1), block_22.view()).unwrap(); - band.write_block((1, 1), block_22).unwrap(); + band.write_block((1, 1), &block_22.into()).unwrap(); block_11.append(Axis(0), block_12.view()).unwrap(); let buf = band From caf5aad622a8c4814d02740b9d630f49b861844d Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Mon, 18 Dec 2023 14:32:33 -0500 Subject: [PATCH 03/14] PR feedback. --- src/raster/buffer.rs | 29 +++++++++++++++-------------- src/raster/rasterband.rs | 24 +++++++++++++++--------- src/raster/tests.rs | 16 +++++++++------- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index 04014dcf..60ff4f70 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -4,7 +4,7 @@ use crate::raster::GdalType; use ndarray::Array2; /// A 2-D array backed by it's `size` (cols, rows) and a row-major `Vec` and it's dimensions. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub struct Buffer { pub size: (usize, usize), pub data: Vec, @@ -13,11 +13,12 @@ pub struct Buffer { impl Buffer { /// Construct a new buffer from `size` (`(cols, rows)`) and `Vec`. /// - /// # Notes - /// Assumes `size.0 * size.1 == data.len()`. - pub fn new(size: (usize, usize), data: Vec) -> Buffer { - debug_assert!( - size.0 * size.1 == data.len(), + /// # Panic + /// Will panic if `size.0 * size.1 != data.len()`. + pub fn new(size: (usize, usize), data: Vec) -> Self { + assert_eq!( + size.0 * size.1, + data.len(), "size {:?} does not match length {}", size, data.len() @@ -51,14 +52,14 @@ impl TryFrom> for Array2 { impl From> for Buffer { fn from(value: Array2) -> Self { // Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis) - let shape = value.shape().to_vec(); - let data: Vec = if value.is_standard_layout() { - value.into_raw_vec() - } else { - value.iter().copied().collect() - }; - - Buffer::new((shape[1], shape[0]), data) + let shape = value.shape(); + let (rows, cols) = (shape[0], shape[1]); + let data = value + .as_standard_layout() + .iter() + .copied() + .collect::>(); + Buffer::new((cols, rows), data) } } diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index 30f2699d..b0f95ed2 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -556,6 +556,7 @@ impl<'a> RasterBand<'a> { /// /// # Arguments /// * `block_index` - the block index + /// * `block` - Data buffer to write to block. /// /// # Notes /// Blocks indexes start from 0 and are of form (x, y), where x grows in the horizontal direction. @@ -566,6 +567,10 @@ impl<'a> RasterBand<'a> { /// The last blocks in both directions can be smaller. /// [`RasterBand::actual_block_size`] will report the correct dimensions of a block. /// + /// While drivers make sure that the content of the `block` buffer before and after the call + /// is equal, some drivers might temporarily modify it, e.g. to do byte swapping. Therefore + /// a `&mut` parameter is required. + /// /// # Errors /// If the block index is not valid, GDAL will return an error. /// @@ -601,14 +606,14 @@ impl<'a> RasterBand<'a> { /// )?; /// let mut band1 = dataset.rasterband(1)?; /// let arr = Buffer::new((16, 16), (0..16*16).collect()); - /// band1.write_block((0, 0), &arr.into())?; + /// band1.write_block((0, 0), &mut arr.into())?; /// # Ok(()) /// # } /// ``` pub fn write_block( &mut self, block_index: (usize, usize), - block: &Buffer, + block: &mut Buffer, ) -> Result<()> { if T::gdal_ordinal() != self.band_type() as u32 { return Err(GdalError::BadArgument( @@ -621,11 +626,7 @@ impl<'a> RasterBand<'a> { self.c_rasterband, block_index.0 as c_int, block_index.1 as c_int, - // This parameter is marked as `* mut c_void` because the C/C++ API for some reason - // doesn't mark it as `const void *`. From code inspection starting at the link - // below, it appears to be a read-only array. - // https://github.com/OSGeo/gdal/blob/b5d004fb9e3fb576b3ccf5f9740531b0bfa87ef4/gcore/gdalrasterband.cpp#L688 - block.data.as_ptr() as *mut c_void, + block.data.as_mut_ptr() as *mut c_void, ) }; if rv != CPLErr::CE_None { @@ -641,11 +642,16 @@ impl<'a> RasterBand<'a> { /// * `window_size` - the window size (GDAL will interpolate data if window_size != Buffer.size) /// * `buffer` - the data to write into the window /// + /// # Notes + /// + /// While drivers make sure that the content of the `block` buffer before and after the call + /// is equal, some drivers might temporarily modify it, e.g. to do byte swapping. Therefore + /// a `&mut` parameter is required. pub fn write( &mut self, window: (isize, isize), window_size: (usize, usize), - buffer: &Buffer, + buffer: &mut Buffer, ) -> Result<()> { assert_eq!(buffer.data.len(), buffer.size.0 * buffer.size.1); let rv = unsafe { @@ -656,7 +662,7 @@ impl<'a> RasterBand<'a> { window.1 as c_int, window_size.0 as c_int, window_size.1 as c_int, - buffer.data.as_ptr() as *mut c_void, + buffer.data.as_mut_ptr() as *mut c_void, buffer.size.0 as c_int, buffer.size.1 as c_int, T::gdal_ordinal(), diff --git a/src/raster/tests.rs b/src/raster/tests.rs index 1ef5a4ed..6867a624 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -102,15 +102,15 @@ fn test_write_raster() { let dataset = driver.create("", 20, 10, 1).unwrap(); // create a 2x1 raster - let raster = ByteBuffer { + let mut raster = ByteBuffer { size: (2, 1), data: vec![50u8, 20u8], }; - // epand it to fill the image (20x10) + // expand it to fill the image (20x10) let mut rb = dataset.rasterband(1).unwrap(); - let res = rb.write((0, 0), (20, 10), &raster); + let res = rb.write((0, 0), (20, 10), &mut raster); assert!(res.is_ok()); @@ -427,12 +427,14 @@ fn test_write_block() { let block_22 = Array2::from_shape_fn((16, 16), |(y, x)| y as u16 * 16 + x as u16 + 4000u16); let mut band = dataset.rasterband(1).unwrap(); - band.write_block((0, 0), &block_11.clone().into()).unwrap(); - band.write_block((0, 1), &block_12.clone().into()).unwrap(); + band.write_block((0, 0), &mut block_11.clone().into()) + .unwrap(); + band.write_block((0, 1), &mut block_12.clone().into()) + .unwrap(); block_11.append(Axis(1), block_21.view()).unwrap(); - band.write_block((1, 0), &block_21.into()).unwrap(); + band.write_block((1, 0), &mut block_21.into()).unwrap(); block_12.append(Axis(1), block_22.view()).unwrap(); - band.write_block((1, 1), &block_22.into()).unwrap(); + band.write_block((1, 1), &mut block_22.into()).unwrap(); block_11.append(Axis(0), block_12.view()).unwrap(); let buf = band From 9801492c6626a8aa31a3582dd205e608bb805825 Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Mon, 18 Dec 2023 16:15:13 -0500 Subject: [PATCH 04/14] Implemented `IntoIterator`, `Index` and `IndexMut` for `Buffer` Made fields of `Buffer` private. --- CHANGES.md | 8 ++- examples/rasterband.rs | 2 +- src/raster/buffer.rs | 131 +++++++++++++++++++++++++++++++++++---- src/raster/mod.rs | 4 +- src/raster/rasterband.rs | 28 ++++----- src/raster/tests.rs | 60 ++++++++---------- 6 files changed, 168 insertions(+), 65 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index facdde17..1697661e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,11 +3,15 @@ ## Unreleased - Added ability to convert between `Buffer` and `ndarray::Array2`. +- Implemented `IntoIterator`, `Index` and `IndexMut` for `Buffer`. +- **Breaking**: `Buffer::size` is now private and accessed via `Buffer::shape(). +- **Breaking**: `Buffer::data` is now private and accessed via `Buffer::data(). - **Breaking**: Removed `Rasterband::read_as_array`, changed signature of `Rasterband::read_block` to return a `Buffer`. -- **Breaking**: `Rasterband::write_block` now takes a `&Buffer`. +- **Breaking**: `Rasterband::write` and `Rasterband::write_block` now require a `&mut Buffer` to handle possible case of drivers temporarily mutating input buffer. + - -- Defers the gdal_i.lib missing message until after the pkg-config check and outputs pkg-config metadata in case of a static build. +- Defers the `gdal_i.lib` missing message until after the `pkg-config` check and outputs `pkg-config` metadata in case of a static build. - diff --git a/examples/rasterband.rs b/examples/rasterband.rs index a0b3d847..98fde76d 100644 --- a/examples/rasterband.rs +++ b/examples/rasterband.rs @@ -14,6 +14,6 @@ fn main() { println!("rasterband scale: {:?}", rasterband.scale()); println!("rasterband offset: {:?}", rasterband.offset()); if let Ok(rv) = rasterband.read_as::((20, 30), (2, 3), (2, 3), None) { - println!("{:?}", rv.data); + println!("{:?}", rv.data()); } } diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index 60ff4f70..038e410d 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -1,4 +1,6 @@ use crate::raster::GdalType; +use std::ops::{Index, IndexMut}; +use std::slice::Iter; #[cfg(feature = "ndarray")] use ndarray::Array2; @@ -6,24 +8,56 @@ use ndarray::Array2; /// A 2-D array backed by it's `size` (cols, rows) and a row-major `Vec` and it's dimensions. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub struct Buffer { - pub size: (usize, usize), - pub data: Vec, + shape: (usize, usize), + data: Vec, } impl Buffer { /// Construct a new buffer from `size` (`(cols, rows)`) and `Vec`. /// - /// # Panic + /// # Panics /// Will panic if `size.0 * size.1 != data.len()`. - pub fn new(size: (usize, usize), data: Vec) -> Self { + pub fn new(shape: (usize, usize), data: Vec) -> Self { assert_eq!( - size.0 * size.1, + shape.0 * shape.1, data.len(), "size {:?} does not match length {}", - size, + shape, data.len() ); - Buffer { size, data } + Buffer { shape, data } + } + + /// Destructures `self` into constituent parts. + pub fn into_shape_and_vec(self) -> ((usize, usize), Vec) { + (self.shape, self.data) + } + + /// Gets the 2-d shape of the buffer. + /// + /// Returns `(cols, rows)` + pub fn shape(&self) -> (usize, usize) { + self.shape + } + + /// Get a slice over the buffer contents. + pub fn data(&self) -> &[T] { + self.data.as_slice() + } + + /// Get a mutable slice over the buffer contents. + pub fn data_mut(&mut self) -> &mut [T] { + self.data.as_mut_slice() + } + + /// Get the number of elements in the buffer + pub fn len(&self) -> usize { + self.data.len() + } + + /// Determine if the buffer has no elements. + pub fn is_empty(&self) -> bool { + self.data.is_empty() } #[cfg(feature = "ndarray")] @@ -31,10 +65,50 @@ impl Buffer { pub fn to_array(self) -> crate::errors::Result> { // Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis) Ok(Array2::from_shape_vec( - (self.size.1, self.size.0), + (self.shape.1, self.shape.0), self.data, )?) } + + #[inline] + pub(self) fn vec_index_for(&self, coord: (usize, usize)) -> usize { + if coord.0 >= self.shape.0 { + panic!( + "index out of bounds: buffer has {} columns but row {} was requested", + self.shape.0, coord.0 + ); + } + if coord.1 >= self.shape.1 { + panic!( + "index out of bounds: buffer has {} rows but row {} was requested", + self.shape.1, coord.1 + ); + } + coord.0 * self.shape.0 + coord.1 + } +} + +impl Index<(usize, usize)> for Buffer { + type Output = T; + fn index(&self, index: (usize, usize)) -> &Self::Output { + &self.data[self.vec_index_for(index)] + } +} + +impl IndexMut<(usize, usize)> for Buffer { + fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output { + let idx = self.vec_index_for(index); + &mut self.data[idx] + } +} + +impl<'a, T: GdalType> IntoIterator for &'a Buffer { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.data.iter() + } } pub type ByteBuffer = Buffer; @@ -54,11 +128,12 @@ impl From> for Buffer { // Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis) let shape = value.shape(); let (rows, cols) = (shape[0], shape[1]); - let data = value - .as_standard_layout() - .iter() - .copied() - .collect::>(); + let data: Vec = if value.is_standard_layout() { + value.into_raw_vec() + } else { + value.iter().copied().collect() + }; + Buffer::new((cols, rows), data) } } @@ -84,4 +159,34 @@ mod tests { let a2 = b.to_array().unwrap(); assert_eq!(a, a2); } + + #[test] + fn index() { + let b = Buffer::new((5, 7), (0..5 * 7).collect()); + assert_eq!(b[(0, 0)], 0); + assert_eq!(b[(1, 1)], 5 + 1); + assert_eq!(b[(4, 5)], 4 * 5 + 5); + + let mut b = b; + b[(2, 2)] = 99; + assert_eq!(b[(2, 1)], 2 * 5 + 1); + assert_eq!(b[(2, 2)], 99); + assert_eq!(b[(2, 3)], 2 * 5 + 3); + } + + #[test] + fn iter() { + let b = Buffer::new((5, 7), (0..5 * 7).collect()); + let mut iter = b.into_iter(); + let _ = iter.next().unwrap(); + let v = iter.next().unwrap(); + assert_eq!(*v, b[(0, 1)]); + } + + #[test] + #[should_panic] + fn index_bounds() { + let b = Buffer::new((5, 7), (0..5 * 7).collect()); + let _ = b[(5, 0)]; + } } diff --git a/src/raster/mod.rs b/src/raster/mod.rs index 16aa2c97..f0401637 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -50,8 +50,8 @@ //! let rv = band.read_as::(window, window_size, size, resample_alg)?; //! // `Rasterband::read_as` returns a `Buffer` struct, which contains the shape of the output //! // `(cols, rows)` and a `Vec<_>` containing the pixel values. -//! println!(" Data size: {:?}", rv.size); -//! println!(" Data values: {:?}", rv.data); +//! println!(" Data size: {:?}", rv.shape()); +//! println!(" Data values: {:?}", rv.data()); //! } //! # Ok(()) //! # } diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index b0f95ed2..03877ce8 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -105,7 +105,7 @@ impl Dataset { /// // Down-sample a image using cubic-spline interpolation /// let buf = band1.read_as::((0, 0), ds.raster_size(), (2, 2), Some(ResampleAlg::CubicSpline))?; /// // In this particular image, resulting data should be close to the overall average. -/// assert!(buf.data.iter().all(|c| (c - stats.mean).abs() < stats.std_dev / 2.0)); +/// assert!(buf.data().iter().all(|c| (c - stats.mean).abs() < stats.std_dev / 2.0)); /// # Ok(()) /// # } /// ``` @@ -428,8 +428,8 @@ impl<'a> RasterBand<'a> { /// assert_eq!(band1.band_type(), GdalDataType::UInt8); /// let size = 4; /// let buf = band1.read_as::((0, 0), band1.size(), (size, size), Some(ResampleAlg::Bilinear))?; - /// assert_eq!(buf.size, (size, size)); - /// assert_eq!(buf.data, [101u8, 119, 94, 87, 92, 110, 92, 87, 91, 90, 89, 87, 92, 91, 88, 88]); + /// assert_eq!(buf.shape(), (size, size)); + /// assert_eq!(buf.data(), [101u8, 119, 94, 87, 92, 110, 92, 87, 91, 90, 89, 87, 92, 91, 88, 88]); /// # Ok(()) /// # } /// ``` @@ -437,10 +437,10 @@ impl<'a> RasterBand<'a> { &self, window: (isize, isize), window_size: (usize, usize), - size: (usize, usize), + shape: (usize, usize), e_resample_alg: Option, ) -> Result> { - let pixels = size.0 * size.1; + let pixels = shape.0 * shape.1; let mut data: Vec = Vec::with_capacity(pixels); let resample_alg = e_resample_alg.unwrap_or(ResampleAlg::NearestNeighbour); @@ -467,8 +467,8 @@ impl<'a> RasterBand<'a> { window_size.0 as c_int, window_size.1 as c_int, data.as_mut_ptr() as *mut c_void, - size.0 as c_int, - size.1 as c_int, + shape.0 as c_int, + shape.1 as c_int, T::gdal_ordinal(), 0, 0, @@ -483,7 +483,7 @@ impl<'a> RasterBand<'a> { data.set_len(pixels); }; - Ok(Buffer { size, data }) + Ok(Buffer::new(shape, data)) } /// Read the full band as a [`Buffer`], where `T` implements [`GdalType`]. @@ -518,7 +518,7 @@ impl<'a> RasterBand<'a> { /// let dataset = Dataset::open("fixtures/m_3607824_se_17_1_20160620_sub.tif")?; /// let band1 = dataset.rasterband(1)?; /// let arr = band1.read_block::((0, 0))?; - /// assert_eq!(arr.size, (300, 6)); + /// assert_eq!(arr.shape(), (300, 6)); /// # Ok(()) /// # } /// ``` @@ -626,7 +626,7 @@ impl<'a> RasterBand<'a> { self.c_rasterband, block_index.0 as c_int, block_index.1 as c_int, - block.data.as_mut_ptr() as *mut c_void, + block.data_mut().as_mut_ptr() as *mut c_void, ) }; if rv != CPLErr::CE_None { @@ -653,7 +653,7 @@ impl<'a> RasterBand<'a> { window_size: (usize, usize), buffer: &mut Buffer, ) -> Result<()> { - assert_eq!(buffer.data.len(), buffer.size.0 * buffer.size.1); + let shape = buffer.shape(); let rv = unsafe { gdal_sys::GDALRasterIO( self.c_rasterband, @@ -662,9 +662,9 @@ impl<'a> RasterBand<'a> { window.1 as c_int, window_size.0 as c_int, window_size.1 as c_int, - buffer.data.as_mut_ptr() as *mut c_void, - buffer.size.0 as c_int, - buffer.size.1 as c_int, + buffer.data_mut().as_mut_ptr() as *mut c_void, + shape.0 as c_int, + shape.1 as c_int, T::gdal_ordinal(), 0, 0, diff --git a/src/raster/tests.rs b/src/raster/tests.rs index 6867a624..774de90c 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -53,14 +53,14 @@ fn test_read_raster() { let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rb = dataset.rasterband(1).unwrap(); let rv = rb.read_as::((20, 30), (2, 3), (2, 3), None).unwrap(); - assert_eq!(rv.size.0, 2); - assert_eq!(rv.size.1, 3); - assert_eq!(rv.data, vec!(7, 7, 7, 10, 8, 12)); + assert_eq!(rv.shape().0, 2); + assert_eq!(rv.shape().1, 3); + assert_eq!(rv.data(), vec!(7, 7, 7, 10, 8, 12)); let mut buf = rv; - rb.read_into_slice((20, 30), (2, 3), (2, 3), &mut buf.data, None) + rb.read_into_slice((20, 30), (2, 3), (2, 3), buf.data_mut(), None) .unwrap(); - assert_eq!(buf.data, vec!(7, 7, 7, 10, 8, 12)); + assert_eq!(buf.data(), vec!(7, 7, 7, 10, 8, 12)); } #[test] @@ -68,14 +68,13 @@ fn test_read_raster_with_default_resample() { let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rb = dataset.rasterband(1).unwrap(); let rv = rb.read_as::((20, 30), (4, 4), (2, 2), None).unwrap(); - assert_eq!(rv.size.0, 2); - assert_eq!(rv.size.1, 2); - assert_eq!(rv.data, vec!(8, 7, 8, 11)); + assert_eq!(rv.shape(), (2, 2)); + assert_eq!(rv.data(), vec!(8, 7, 8, 11)); let mut buf = rv; - rb.read_into_slice((20, 30), (4, 4), (2, 2), &mut buf.data, None) + rb.read_into_slice((20, 30), (4, 4), (2, 2), buf.data_mut(), None) .unwrap(); - assert_eq!(buf.data, vec!(8, 7, 8, 11)); + assert_eq!(buf.data(), vec!(8, 7, 8, 11)); } #[test] @@ -86,14 +85,13 @@ fn test_read_raster_with_average_resample() { let rv = rb .read_as::((20, 30), (4, 4), (2, 2), Some(resample_alg)) .unwrap(); - assert_eq!(rv.size.0, 2); - assert_eq!(rv.size.1, 2); - assert_eq!(rv.data, vec!(8, 7, 8, 11)); + assert_eq!(rv.shape(), (2, 2)); + assert_eq!(rv.data(), vec!(8, 7, 8, 11)); let mut buf = rv; - rb.read_into_slice((20, 30), (4, 4), (2, 2), &mut buf.data, Some(resample_alg)) + rb.read_into_slice((20, 30), (4, 4), (2, 2), buf.data_mut(), Some(resample_alg)) .unwrap(); - assert_eq!(buf.data, vec!(8, 7, 8, 11)); + assert_eq!(buf.data(), vec!(8, 7, 8, 11)); } #[test] @@ -102,10 +100,7 @@ fn test_write_raster() { let dataset = driver.create("", 20, 10, 1).unwrap(); // create a 2x1 raster - let mut raster = ByteBuffer { - size: (2, 1), - data: vec![50u8, 20u8], - }; + let mut raster = ByteBuffer::new((2, 1), vec![50u8, 20u8]); // expand it to fill the image (20x10) let mut rb = dataset.rasterband(1).unwrap(); @@ -116,11 +111,11 @@ fn test_write_raster() { // read a pixel from the left side let left = rb.read_as::((5, 5), (1, 1), (1, 1), None).unwrap(); - assert_eq!(left.data[0], 50u8); + assert_eq!(left.data()[0], 50u8); // read a pixel from the right side let right = rb.read_as::((15, 5), (1, 1), (1, 1), None).unwrap(); - assert_eq!(right.data[0], 20u8); + assert_eq!(right.data()[0], 20u8); } #[test] @@ -290,9 +285,8 @@ fn test_read_raster_as() { let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rb = dataset.rasterband(1).unwrap(); let rv = rb.read_as::((20, 30), (2, 3), (2, 3), None).unwrap(); - assert_eq!(rv.data, vec!(7, 7, 7, 10, 8, 12)); - assert_eq!(rv.size.0, 2); - assert_eq!(rv.size.1, 3); + assert_eq!(rv.data(), vec!(7, 7, 7, 10, 8, 12)); + assert_eq!(rv.shape(), (2, 3)); assert_eq!(rb.band_type(), GdalDataType::UInt8); } @@ -313,7 +307,7 @@ fn open_mask_band() { let rb = dataset.rasterband(1).unwrap(); let mb = rb.open_mask_band().unwrap(); let mask_values = mb.read_as::((20, 30), (2, 3), (2, 3), None).unwrap(); - assert_eq!(mask_values.data, [255u8; 6]) + assert_eq!(mask_values.data(), [255u8; 6]) } #[test] @@ -325,7 +319,7 @@ fn create_mask_band() { let mb = rb.open_mask_band().unwrap(); let mask_values = mb.read_as::((0, 0), (20, 10), (20, 10), None).unwrap(); - assert_eq!(mask_values.data, [0; 200]) + assert_eq!(mask_values.data(), [0; 200]) } #[test] @@ -376,7 +370,7 @@ fn test_read_block_dimension() { let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rasterband = dataset.rasterband(band_index).unwrap(); let buff = rasterband.read_block::(block).unwrap(); - assert_eq!(buff.size, (100, 27)); + assert_eq!(buff.shape(), (100, 27)); } #[test] @@ -387,10 +381,10 @@ fn test_read_block_data() { let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap(); let rasterband = dataset.rasterband(band_index).unwrap(); let buf = rasterband.read_block::(block).unwrap(); - assert_eq!(buf.data[0], 0); - assert_eq!(buf.data[1], 9); - assert_eq!(buf.data[98], 24); - assert_eq!(buf.data[99], 51); + assert_eq!(buf.data()[0], 0); + assert_eq!(buf.data()[1], 9); + assert_eq!(buf.data()[98], 24); + assert_eq!(buf.data()[99], 51); } #[test] @@ -440,7 +434,7 @@ fn test_write_block() { let buf = band .read_as::((0, 0), (32, 32), (32, 32), None) .unwrap(); - let arr = ndarray::Array2::from_shape_vec((32, 32), buf.data).unwrap(); + let arr = buf.to_array().unwrap(); assert_eq!(arr, block_11); } @@ -659,7 +653,7 @@ fn test_rasterize() { let rb = dataset.rasterband(1).unwrap(); let values = rb.read_as::((0, 0), (5, 5), (5, 5), None).unwrap(); assert_eq!( - values.data, + values.data(), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,] ); } From e35aa8c4f4aa3dbc138aa6384545b482c666cf63 Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Mon, 18 Dec 2023 16:58:21 -0500 Subject: [PATCH 05/14] Additional tests around row-major vs col-major ndarrays. --- src/raster/buffer.rs | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index 038e410d..f5bbb445 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -15,6 +15,9 @@ pub struct Buffer { impl Buffer { /// Construct a new buffer from `size` (`(cols, rows)`) and `Vec`. /// + /// # Notes + /// The elements of `shape` are in reverse order from what is used in `ndarray`. + /// /// # Panics /// Will panic if `size.0 * size.1 != data.len()`. pub fn new(shape: (usize, usize), data: Vec) -> Self { @@ -28,14 +31,17 @@ impl Buffer { Buffer { shape, data } } - /// Destructures `self` into constituent parts. + /// De-structures `self` into constituent parts. pub fn into_shape_and_vec(self) -> ((usize, usize), Vec) { (self.shape, self.data) } /// Gets the 2-d shape of the buffer. - /// + // /// Returns `(cols, rows)` + /// + /// # Notes + /// The elements of `shape` are in reverse order from what is used in `ndarray`. pub fn shape(&self) -> (usize, usize) { self.shape } @@ -142,24 +148,48 @@ impl From> for Buffer { #[cfg(test)] mod tests { use crate::raster::Buffer; - use ndarray::Array2; + use ndarray::{Array2, ShapeBuilder}; #[test] fn convert_to() { let b = Buffer::new((5, 10), (0..5 * 10).collect()); let a = b.clone().to_array().unwrap(); + let expected = Array2::from_shape_fn((10, 5), |(row, col)| row as i32 * 5 + col as i32); + assert_eq!(a, expected); let b2: Buffer<_> = a.into(); assert_eq!(b, b2); } #[test] fn convert_from() { - let a = Array2::from_shape_fn((10, 5), |(y, x)| y as i32 * 10 + x as i32); + let a = Array2::from_shape_fn((10, 5), |(row, col)| row as i32 * 5 + col as i32); let b: Buffer<_> = a.clone().into(); + let expected = Buffer::new((5, 10), (0..5 * 10).collect()); + assert_eq!(b, expected); + let a2 = b.to_array().unwrap(); assert_eq!(a, a2); } + #[test] + fn shapes() { + let s1 = (10, 5).into_shape().set_f(true); + let s2 = (10, 5).into_shape().set_f(false); + + let a1 = Array2::from_shape_fn(s1, |(y, x)| y as i32 * 5 + x as i32); + let a2 = Array2::from_shape_fn(s2, |(y, x)| y as i32 * 5 + x as i32); + + let expected = Buffer::new((5, 10), (0..5 * 10).collect()); + + assert_eq!(a1, expected.clone().to_array().unwrap()); + + let b1: Buffer<_> = a1.into(); + let b2: Buffer<_> = a2.into(); + + assert_eq!(b1, expected); + assert_eq!(b2, expected); + } + #[test] fn index() { let b = Buffer::new((5, 7), (0..5 * 7).collect()); @@ -185,7 +215,7 @@ mod tests { #[test] #[should_panic] - fn index_bounds() { + fn index_bounds_panic() { let b = Buffer::new((5, 7), (0..5 * 7).collect()); let _ = b[(5, 0)]; } From e835fbb9d14cc17573bfd2abf224572663ee788b Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Tue, 19 Dec 2023 16:30:32 -0500 Subject: [PATCH 06/14] Improved Buffer::new assertion message. --- src/raster/buffer.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index f5bbb445..63eaa878 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -24,8 +24,10 @@ impl Buffer { assert_eq!( shape.0 * shape.1, data.len(), - "size {:?} does not match length {}", - shape, + "shape {}*{}={} does not match length {}", + shape.0, + shape.1, + shape.0 * shape.1, data.len() ); Buffer { shape, data } From a040de4dc20c2f3806c70ef1b0f632f1bdadce41 Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Wed, 20 Dec 2023 16:15:34 -0500 Subject: [PATCH 07/14] Update src/raster/buffer.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Laurențiu Nicola --- src/raster/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index 63eaa878..5562bfe6 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -135,7 +135,7 @@ impl From> for Buffer { fn from(value: Array2) -> Self { // Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis) let shape = value.shape(); - let (rows, cols) = (shape[0], shape[1]); + let (cols, rows) = (shape[1], shape[0]); let data: Vec = if value.is_standard_layout() { value.into_raw_vec() } else { From 8345e7005a4a916e890bc9d71a4b5a982d6908aa Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Wed, 20 Dec 2023 16:52:16 -0500 Subject: [PATCH 08/14] Added `IntoIter` for `Buffer` and `&mut Buffer`. --- src/raster/buffer.rs | 47 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index 63eaa878..5eb80736 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -1,6 +1,7 @@ use crate::raster::GdalType; use std::ops::{Index, IndexMut}; -use std::slice::Iter; +use std::slice::{Iter, IterMut}; +use std::vec::IntoIter; #[cfg(feature = "ndarray")] use ndarray::Array2; @@ -33,7 +34,7 @@ impl Buffer { Buffer { shape, data } } - /// De-structures `self` into constituent parts. + /// Destructures `self` into constituent parts. pub fn into_shape_and_vec(self) -> ((usize, usize), Vec) { (self.shape, self.data) } @@ -79,7 +80,7 @@ impl Buffer { } #[inline] - pub(self) fn vec_index_for(&self, coord: (usize, usize)) -> usize { + fn vec_index_for(&self, coord: (usize, usize)) -> usize { if coord.0 >= self.shape.0 { panic!( "index out of bounds: buffer has {} columns but row {} was requested", @@ -110,6 +111,15 @@ impl IndexMut<(usize, usize)> for Buffer { } } +impl IntoIterator for Buffer { + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.data.into_iter() + } +} + impl<'a, T: GdalType> IntoIterator for &'a Buffer { type Item = &'a T; type IntoIter = Iter<'a, T>; @@ -119,6 +129,15 @@ impl<'a, T: GdalType> IntoIterator for &'a Buffer { } } +impl<'a, T: GdalType> IntoIterator for &'a mut Buffer { + type Item = &'a mut T; + type IntoIter = IterMut<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.data.iter_mut() + } +} + pub type ByteBuffer = Buffer; #[cfg(feature = "ndarray")] @@ -156,7 +175,7 @@ mod tests { fn convert_to() { let b = Buffer::new((5, 10), (0..5 * 10).collect()); let a = b.clone().to_array().unwrap(); - let expected = Array2::from_shape_fn((10, 5), |(row, col)| row as i32 * 5 + col as i32); + let expected = Array2::from_shape_fn((10, 5), |(y, x)| y as i32 * 5 + x as i32); assert_eq!(a, expected); let b2: Buffer<_> = a.into(); assert_eq!(b, b2); @@ -164,7 +183,7 @@ mod tests { #[test] fn convert_from() { - let a = Array2::from_shape_fn((10, 5), |(row, col)| row as i32 * 5 + col as i32); + let a = Array2::from_shape_fn((10, 5), |(y, x)| y as i32 * 5 + x as i32); let b: Buffer<_> = a.clone().into(); let expected = Buffer::new((5, 10), (0..5 * 10).collect()); assert_eq!(b, expected); @@ -208,11 +227,27 @@ mod tests { #[test] fn iter() { + // Iter on ref let b = Buffer::new((5, 7), (0..5 * 7).collect()); - let mut iter = b.into_iter(); + let mut iter = (&b).into_iter(); let _ = iter.next().unwrap(); let v = iter.next().unwrap(); assert_eq!(*v, b[(0, 1)]); + + // Iter on owned + let mut iter = b.clone().into_iter(); + let _ = iter.next().unwrap(); + let v = iter.next().unwrap(); + assert_eq!(v, b[(0, 1)]); + + // Iter on mut + let mut b = b; + let mut iter = (&mut b).into_iter(); + let _ = iter.next().unwrap(); + let v = iter.next().unwrap(); + *v = 99; + + assert_eq!(99, b[(0, 1)]); } #[test] From 0cdd3fe1bc2e006fe3ca885cbff278056e24971a Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Thu, 21 Dec 2023 14:40:32 -0500 Subject: [PATCH 09/14] Additional overflow checking on usize -> c_int conversions. --- src/raster/rasterband.rs | 66 +++++++++++++++++++++++++--------------- src/utils.rs | 9 ++++++ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index 7a902d20..6e71996d 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -2,7 +2,7 @@ use crate::dataset::Dataset; use crate::gdal_major_object::MajorObject; use crate::metadata::Metadata; use crate::raster::{GdalDataType, GdalType}; -use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string}; +use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string, coerce_size_tuple}; use gdal_sys::{ self, CPLErr, GDALColorEntry, GDALColorInterp, GDALColorTableH, GDALComputeRasterMinMax, GDALCreateColorRamp, GDALCreateColorTable, GDALDestroyColorTable, GDALGetDefaultHistogramEx, @@ -374,6 +374,10 @@ impl<'a> RasterBand<'a> { return Err(GdalError::BufferSizeMismatch(buffer.len(), size)); } + let window = coerce_size_tuple(window)?; + let window_size = coerce_size_tuple(window_size)?; + let size = coerce_size_tuple(size)?; + let resample_alg = e_resample_alg.unwrap_or(ResampleAlg::NearestNeighbour); let mut options: GDALRasterIOExtraArg = RasterIOExtraArg { @@ -388,13 +392,13 @@ impl<'a> RasterBand<'a> { gdal_sys::GDALRasterIOEx( self.c_rasterband, GDALRWFlag::GF_Read, - window.0 as c_int, - window.1 as c_int, - window_size.0 as c_int, - window_size.1 as c_int, + window.0, + window.1, + window_size.0, + window_size.1, buffer.as_mut_ptr() as *mut c_void, - size.0 as c_int, - size.1 as c_int, + size.0, + size.1, T::gdal_ordinal(), 0, 0, @@ -439,7 +443,11 @@ impl<'a> RasterBand<'a> { shape: (usize, usize), e_resample_alg: Option, ) -> Result> { + let window = coerce_size_tuple(window)?; + let window_size = coerce_size_tuple(window_size)?; + let pixels = shape.0 * shape.1; + let c_shape = coerce_size_tuple(shape)?; let mut data: Vec = Vec::with_capacity(pixels); let resample_alg = e_resample_alg.unwrap_or(ResampleAlg::NearestNeighbour); @@ -461,13 +469,13 @@ impl<'a> RasterBand<'a> { gdal_sys::GDALRasterIOEx( self.c_rasterband, GDALRWFlag::GF_Read, - window.0 as c_int, - window.1 as c_int, - window_size.0 as c_int, - window_size.1 as c_int, + window.0, + window.1, + window_size.0, + window_size.1, data.as_mut_ptr() as *mut c_void, - shape.0 as c_int, - shape.1 as c_int, + c_shape.0, + c_shape.1, T::gdal_ordinal(), 0, 0, @@ -488,7 +496,7 @@ impl<'a> RasterBand<'a> { /// Read the full band as a [`Buffer`], where `T` implements [`GdalType`]. pub fn read_band_as(&self) -> Result> { let size = self.size(); - self.read_as::((0, 0), (size.0, size.1), (size.0, size.1), None) + self.read_as::((0, 0), size, size, None) } /// Read a [`Buffer`] from a [`Dataset`] block, where `T` implements [`GdalType`]. @@ -532,11 +540,13 @@ impl<'a> RasterBand<'a> { let pixels = size.0 * size.1; let mut data: Vec = Vec::with_capacity(pixels); + let block_index = coerce_size_tuple(block_index)?; + let rv = unsafe { gdal_sys::GDALReadBlock( self.c_rasterband, - block_index.0 as c_int, - block_index.1 as c_int, + block_index.0, + block_index.1, data.as_mut_ptr() as *mut c_void, ) }; @@ -620,11 +630,13 @@ impl<'a> RasterBand<'a> { )); } + let block_index = coerce_size_tuple(block_index)?; + let rv = unsafe { gdal_sys::GDALWriteBlock( self.c_rasterband, - block_index.0 as c_int, - block_index.1 as c_int, + block_index.0, + block_index.1, block.data_mut().as_mut_ptr() as *mut c_void, ) }; @@ -657,17 +669,21 @@ impl<'a> RasterBand<'a> { return Err(GdalError::BufferSizeMismatch(buffer.len(), shape)); } + let shape = coerce_size_tuple(shape)?; + let window = coerce_size_tuple(window)?; + let window_size = coerce_size_tuple(window_size)?; + let rv = unsafe { gdal_sys::GDALRasterIO( self.c_rasterband, GDALRWFlag::GF_Write, - window.0 as c_int, - window.1 as c_int, - window_size.0 as c_int, - window_size.1 as c_int, + window.0, + window.1, + window_size.0, + window_size.1, buffer.data_mut().as_mut_ptr() as *mut c_void, - shape.0 as c_int, - shape.1 as c_int, + shape.0, + shape.1, T::gdal_ordinal(), 0, 0, @@ -1149,7 +1165,7 @@ pub enum ColorInterpretation { Undefined, /// Grayscale GrayIndex, - /// Paletted (see associated color table) + /// Palette (see associated color table) PaletteIndex, /// Red band of RGBA image RedBand, diff --git a/src/utils.rs b/src/utils.rs index 7670dc7e..d0066382 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -80,3 +80,12 @@ pub fn _path_to_c_string(path: &Path) -> Result { let path_str = path.to_string_lossy(); CString::new(path_str.as_ref()).map_err(Into::into) } + +#[inline] +pub(crate) fn coerce_size_tuple(s: (T, T)) -> Result<(libc::c_int, libc::c_int)> +where + T: TryInto, + GdalError: From<>::Error>, +{ + Ok((s.0.try_into()?, s.1.try_into()?)) +} From ded32489204174e14dcd93640fd3cc226a78cd0b Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Thu, 21 Dec 2023 15:12:49 -0500 Subject: [PATCH 10/14] Extended example for `Buffer`. --- src/raster/buffer.rs | 50 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index 3dd61caf..496a7508 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -6,7 +6,52 @@ use std::vec::IntoIter; #[cfg(feature = "ndarray")] use ndarray::Array2; -/// A 2-D array backed by it's `size` (cols, rows) and a row-major `Vec` and it's dimensions. +/// `Buffer` manages cell values in in raster I/O operations. +/// +/// It conceptually represents a 2-D array backed by a `Vec` with row-major organization +/// to represent `shape` (cols, rows). +/// +/// 2-D indexing is available through `Index<(usize, usize)>` and `IndexMut<(usize, usize)>` +/// implementations. The underlying data can be accessed linearly via [`Buffer::data()`] +/// and [`Buffer::data_mut()`]. [`IntoIterator`] is also provided. +/// +/// If the `ndarray` feature is enabled, a `Buffer` can be converted (without copy) +/// to an `Array2` via [`Buffer::to_array()`]. +/// +/// # Example +/// +/// ```rust, no_run +/// # fn main() -> gdal::errors::Result<()> { +/// use gdal::Dataset; +/// use gdal::raster::Buffer; +/// // Read the band: +/// let ds = Dataset::open("fixtures/dem-hills.tiff")?; +/// let band = ds.rasterband(1)?; +/// let buf: Buffer = band.read_band_as()?; +/// +/// // Read cell in the middle: +/// let size = ds.raster_size(); +/// let center = (size.0/2, size.1/2); +/// let center_value = buf[center]; +/// assert_eq!(center_value.round(), 166.0); +/// +/// // Gather basic statistics: +/// fn min_max(b: &Buffer) -> (f64, f64) { +/// b.into_iter().fold((f64::MAX, f64::MIN), | a, v | (v.min(a.0), v.max(a.1))) +/// } +/// let (min, max) = min_max(&buf); +/// assert_eq!(min, -999999.0); // <--- data has a sentinel value +/// assert_eq!(max.round(), 168.0); +/// +/// // Mutate the buffer and observe the difference: +/// let mut buf = buf; +/// buf[center] = 1e10; +/// let (min, max) = min_max(&buf); +/// assert_eq!(max, 1e10); +/// +/// # Ok(()) +/// # } +/// ``` #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub struct Buffer { shape: (usize, usize), @@ -70,7 +115,8 @@ impl Buffer { } #[cfg(feature = "ndarray")] - /// Convert `self` into an [`ndarray::Array2`]. + #[cfg_attr(docsrs, doc(cfg(feature = "ndarray")))] + /// Convert `self` into an [`ndarray::Array2`]. pub fn to_array(self) -> crate::errors::Result> { // Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis) Ok(Array2::from_shape_vec( From 9ede6665b479d46d8253faea6a121b0ced031413 Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Thu, 21 Dec 2023 16:35:09 -0500 Subject: [PATCH 11/14] Simplified overflow checking. --- src/raster/rasterband.rs | 62 +++++++++++++++------------------------- src/utils.rs | 9 ------ 2 files changed, 23 insertions(+), 48 deletions(-) diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index 6e71996d..5770f36e 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -2,7 +2,7 @@ use crate::dataset::Dataset; use crate::gdal_major_object::MajorObject; use crate::metadata::Metadata; use crate::raster::{GdalDataType, GdalType}; -use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string, coerce_size_tuple}; +use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string}; use gdal_sys::{ self, CPLErr, GDALColorEntry, GDALColorInterp, GDALColorTableH, GDALComputeRasterMinMax, GDALCreateColorRamp, GDALCreateColorTable, GDALDestroyColorTable, GDALGetDefaultHistogramEx, @@ -374,10 +374,6 @@ impl<'a> RasterBand<'a> { return Err(GdalError::BufferSizeMismatch(buffer.len(), size)); } - let window = coerce_size_tuple(window)?; - let window_size = coerce_size_tuple(window_size)?; - let size = coerce_size_tuple(size)?; - let resample_alg = e_resample_alg.unwrap_or(ResampleAlg::NearestNeighbour); let mut options: GDALRasterIOExtraArg = RasterIOExtraArg { @@ -392,13 +388,13 @@ impl<'a> RasterBand<'a> { gdal_sys::GDALRasterIOEx( self.c_rasterband, GDALRWFlag::GF_Read, - window.0, - window.1, - window_size.0, - window_size.1, + window.0.try_into()?, + window.1.try_into()?, + window_size.0.try_into()?, + window_size.1.try_into()?, buffer.as_mut_ptr() as *mut c_void, - size.0, - size.1, + size.0.try_into()?, + size.1.try_into()?, T::gdal_ordinal(), 0, 0, @@ -443,11 +439,7 @@ impl<'a> RasterBand<'a> { shape: (usize, usize), e_resample_alg: Option, ) -> Result> { - let window = coerce_size_tuple(window)?; - let window_size = coerce_size_tuple(window_size)?; - let pixels = shape.0 * shape.1; - let c_shape = coerce_size_tuple(shape)?; let mut data: Vec = Vec::with_capacity(pixels); let resample_alg = e_resample_alg.unwrap_or(ResampleAlg::NearestNeighbour); @@ -469,13 +461,13 @@ impl<'a> RasterBand<'a> { gdal_sys::GDALRasterIOEx( self.c_rasterband, GDALRWFlag::GF_Read, - window.0, - window.1, - window_size.0, - window_size.1, + window.0.try_into()?, + window.1.try_into()?, + window_size.0.try_into()?, + window_size.1.try_into()?, data.as_mut_ptr() as *mut c_void, - c_shape.0, - c_shape.1, + shape.0.try_into()?, + shape.1.try_into()?, T::gdal_ordinal(), 0, 0, @@ -540,13 +532,11 @@ impl<'a> RasterBand<'a> { let pixels = size.0 * size.1; let mut data: Vec = Vec::with_capacity(pixels); - let block_index = coerce_size_tuple(block_index)?; - let rv = unsafe { gdal_sys::GDALReadBlock( self.c_rasterband, - block_index.0, - block_index.1, + block_index.0.try_into()?, + block_index.1.try_into()?, data.as_mut_ptr() as *mut c_void, ) }; @@ -630,13 +620,11 @@ impl<'a> RasterBand<'a> { )); } - let block_index = coerce_size_tuple(block_index)?; - let rv = unsafe { gdal_sys::GDALWriteBlock( self.c_rasterband, - block_index.0, - block_index.1, + block_index.0.try_into()?, + block_index.1.try_into()?, block.data_mut().as_mut_ptr() as *mut c_void, ) }; @@ -669,21 +657,17 @@ impl<'a> RasterBand<'a> { return Err(GdalError::BufferSizeMismatch(buffer.len(), shape)); } - let shape = coerce_size_tuple(shape)?; - let window = coerce_size_tuple(window)?; - let window_size = coerce_size_tuple(window_size)?; - let rv = unsafe { gdal_sys::GDALRasterIO( self.c_rasterband, GDALRWFlag::GF_Write, - window.0, - window.1, - window_size.0, - window_size.1, + window.0.try_into()?, + window.1.try_into()?, + window_size.0.try_into()?, + window_size.1.try_into()?, buffer.data_mut().as_mut_ptr() as *mut c_void, - shape.0, - shape.1, + shape.0.try_into()?, + shape.1.try_into()?, T::gdal_ordinal(), 0, 0, diff --git a/src/utils.rs b/src/utils.rs index d0066382..7670dc7e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -80,12 +80,3 @@ pub fn _path_to_c_string(path: &Path) -> Result { let path_str = path.to_string_lossy(); CString::new(path_str.as_ref()).map_err(Into::into) } - -#[inline] -pub(crate) fn coerce_size_tuple(s: (T, T)) -> Result<(libc::c_int, libc::c_int)> -where - T: TryInto, - GdalError: From<>::Error>, -{ - Ok((s.0.try_into()?, s.1.try_into()?)) -} From 19e4366e0472bc28bd9abdb96f4f99636192753e Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Thu, 21 Dec 2023 16:37:03 -0500 Subject: [PATCH 12/14] Update src/raster/mdarray.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Laurențiu Nicola --- src/raster/mdarray.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raster/mdarray.rs b/src/raster/mdarray.rs index 8097c0ab..594763e9 100644 --- a/src/raster/mdarray.rs +++ b/src/raster/mdarray.rs @@ -248,7 +248,7 @@ impl<'a> MDArray<'a> { #[cfg(feature = "ndarray")] #[cfg_attr(docsrs, doc(cfg(feature = "array")))] - /// Read a [`ArrayD`] from this band. T implements [`GdalType`]. + /// Read an [`ArrayD`] from this band. T implements [`GdalType`]. /// /// # Arguments /// * `window` - the window position from top left From 47c2a4dc9cfa18da6f512dcae94276ee5b17543f Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Thu, 21 Dec 2023 16:54:35 -0500 Subject: [PATCH 13/14] Optimized buffer index checking. --- src/raster/buffer.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index 496a7508..194cbdc1 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -125,19 +125,20 @@ impl Buffer { )?) } + #[cold] + #[inline(never)] + #[track_caller] + fn panic_bad_index(shape: (usize, usize), coord: (usize, usize)) -> ! { + panic!( + "index out of bounds: buffer has shape `{shape:?}` but coordinate `{coord:?}` was requested", + ); + } + #[inline] + #[track_caller] fn vec_index_for(&self, coord: (usize, usize)) -> usize { - if coord.0 >= self.shape.0 { - panic!( - "index out of bounds: buffer has {} columns but row {} was requested", - self.shape.0, coord.0 - ); - } - if coord.1 >= self.shape.1 { - panic!( - "index out of bounds: buffer has {} rows but row {} was requested", - self.shape.1, coord.1 - ); + if coord.0 >= self.shape.0 || coord.1 >= self.shape.1 { + Self::panic_bad_index(self.shape, coord); } coord.0 * self.shape.0 + coord.1 } From 7531219dc089e6c75e9e91273c813305c13e968c Mon Sep 17 00:00:00 2001 From: "Simeon H.K. Fitch" Date: Fri, 22 Dec 2023 09:06:07 -0500 Subject: [PATCH 14/14] Minor formatting. --- src/raster/buffer.rs | 6 ++---- src/raster/rasterband.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/raster/buffer.rs b/src/raster/buffer.rs index 194cbdc1..db8947df 100644 --- a/src/raster/buffer.rs +++ b/src/raster/buffer.rs @@ -37,7 +37,7 @@ use ndarray::Array2; /// /// // Gather basic statistics: /// fn min_max(b: &Buffer) -> (f64, f64) { -/// b.into_iter().fold((f64::MAX, f64::MIN), | a, v | (v.min(a.0), v.max(a.1))) +/// b.into_iter().fold((f64::MAX, f64::MIN), |a, v| (v.min(a.0), v.max(a.1))) /// } /// let (min, max) = min_max(&buf); /// assert_eq!(min, -999999.0); // <--- data has a sentinel value @@ -129,9 +129,7 @@ impl Buffer { #[inline(never)] #[track_caller] fn panic_bad_index(shape: (usize, usize), coord: (usize, usize)) -> ! { - panic!( - "index out of bounds: buffer has shape `{shape:?}` but coordinate `{coord:?}` was requested", - ); + panic!("index out of bounds: buffer has shape `{shape:?}` but coordinate `{coord:?}` was requested"); } #[inline] diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index 5770f36e..675e16e2 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -1149,7 +1149,7 @@ pub enum ColorInterpretation { Undefined, /// Grayscale GrayIndex, - /// Palette (see associated color table) + /// Paletted (see associated color table) PaletteIndex, /// Red band of RGBA image RedBand,