diff --git a/CHANGES.md b/CHANGES.md index 6efd38e5..24a82abd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,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` and `Rasterband::write_block` now require a `&mut Buffer` to handle possible case of drivers temporarily mutating input buffer. + + - + - Implemented `Feature::set_field_null` - @@ -15,7 +24,7 @@ - -- 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 new file mode 100644 index 00000000..db8947df --- /dev/null +++ b/src/raster/buffer.rs @@ -0,0 +1,304 @@ +use crate::raster::GdalType; +use std::ops::{Index, IndexMut}; +use std::slice::{Iter, IterMut}; +use std::vec::IntoIter; + +#[cfg(feature = "ndarray")] +use ndarray::Array2; + +/// `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), + data: Vec, +} + +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 { + assert_eq!( + shape.0 * shape.1, + data.len(), + "shape {}*{}={} does not match length {}", + shape.0, + shape.1, + shape.0 * shape.1, + data.len() + ); + 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)` + /// + /// # Notes + /// The elements of `shape` are in reverse order from what is used in `ndarray`. + 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")] + #[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( + (self.shape.1, self.shape.0), + self.data, + )?) + } + + #[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 || coord.1 >= self.shape.1 { + Self::panic_bad_index(self.shape, coord); + } + 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 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>; + + fn into_iter(self) -> Self::IntoIter { + self.data.iter() + } +} + +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")] +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(); + let (cols, rows) = (shape[1], shape[0]); + let data: Vec = if value.is_standard_layout() { + value.into_raw_vec() + } else { + value.iter().copied().collect() + }; + + Buffer::new((cols, rows), data) + } +} + +#[cfg(feature = "ndarray")] +#[cfg(test)] +mod tests { + use crate::raster::Buffer; + 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), |(y, x)| y as i32 * 5 + x 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 * 5 + x 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()); + 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() { + // Iter on ref + 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)]); + + // 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] + #[should_panic] + fn index_bounds_panic() { + let b = Buffer::new((5, 7), (0..5 * 7).collect()); + let _ = b[(5, 0)]; + } +} diff --git a/src/raster/mdarray.rs b/src/raster/mdarray.rs index d88c5036..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 'Array2' from this band. T implements 'GdalType'. + /// Read an [`ArrayD`] from this band. T implements [`GdalType`]. /// /// # Arguments /// * `window` - the window position from top left diff --git a/src/raster/mod.rs b/src/raster/mod.rs index d921bc9b..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(()) //! # } @@ -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 6e09420f..675e16e2 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, }; @@ -104,7 +102,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(()) /// # } /// ``` @@ -390,13 +388,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, - buffer.as_mut_ptr() as GDALRasterBandH, - size.0 as c_int, - size.1 as c_int, + 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.try_into()?, + size.1.try_into()?, T::gdal_ordinal(), 0, 0, @@ -429,8 +427,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(()) /// # } /// ``` @@ -438,10 +436,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); @@ -463,13 +461,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, - data.as_mut_ptr() as GDALRasterBandH, - size.0 as c_int, - size.1 as c_int, + 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, + shape.0.try_into()?, + shape.1.try_into()?, T::gdal_ordinal(), 0, 0, @@ -484,46 +482,16 @@ impl<'a> RasterBand<'a> { data.set_len(pixels); }; - 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, - )?) + Ok(Buffer::new(shape, data)) } /// 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) } - #[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 @@ -549,11 +517,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.shape(), (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(), @@ -564,13 +532,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, + block_index.0.try_into()?, + block_index.1.try_into()?, + data.as_mut_ptr() as *mut c_void, ) }; if rv != CPLErr::CE_None { @@ -581,15 +548,14 @@ impl<'a> RasterBand<'a> { data.set_len(pixels); }; - Array2::from_shape_vec((size.1, size.0), data).map_err(Into::into) + Ok(Buffer::new(size, data)) } - #[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 + /// * `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. @@ -600,6 +566,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. /// @@ -608,8 +578,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 = [ @@ -635,15 +604,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), &mut arr.into())?; /// # Ok(()) /// # } /// ``` pub fn write_block( &mut self, block_index: (usize, usize), - block: Array2, + block: &mut Buffer, ) -> Result<()> { if T::gdal_ordinal() != self.band_type() as u32 { return Err(GdalError::BadArgument( @@ -651,13 +620,12 @@ 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, + block_index.0.try_into()?, + block_index.1.try_into()?, + block.data_mut().as_mut_ptr() as *mut c_void, ) }; if rv != CPLErr::CE_None { @@ -672,30 +640,34 @@ impl<'a> RasterBand<'a> { /// * `window` - the window position from top left /// * `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<()> { - if buffer.data.len() != buffer.size.0 * buffer.size.1 { - return Err(GdalError::BufferSizeMismatch( - buffer.data.len(), - buffer.size, - )); + let shape = buffer.shape(); + if buffer.len() != shape.0 * shape.1 { + return Err(GdalError::BufferSizeMismatch(buffer.len(), shape)); } 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, - buffer.data.as_ptr() as GDALRasterBandH, - buffer.size.0 as c_int, - buffer.size.1 as c_int, + 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.try_into()?, + shape.1.try_into()?, T::gdal_ordinal(), 0, 0, @@ -1170,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 b4550d0a..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,25 +100,22 @@ fn test_write_raster() { let dataset = driver.create("", 20, 10, 1).unwrap(); // create a 2x1 raster - let raster = ByteBuffer { - size: (2, 1), - data: vec![50u8, 20u8], - }; + let mut raster = ByteBuffer::new((2, 1), 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()); // 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] @@ -338,7 +332,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 +347,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); } @@ -375,8 +369,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::(block).unwrap(); - assert_eq!(array.dim(), (27, 100)); + let buff = rasterband.read_block::(block).unwrap(); + assert_eq!(buff.shape(), (100, 27)); } #[test] @@ -386,11 +380,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::(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,18 +421,20 @@ 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), &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).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).unwrap(); + band.write_block((1, 1), &mut block_22.into()).unwrap(); block_11.append(Axis(0), block_12.view()).unwrap(); 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); } @@ -657,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,] ); }