diff --git a/CHANGES.md b/CHANGES.md index c0262736f..de72f211a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## Unreleased +- Added `RasterBand::write_block`. + + - <https://github.com/georust/gdal/pull/490> + - `RasterBand::read_block` now checks that the requested type matches the band type. - <https://github.com/georust/gdal/pull/489> diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index dfdc2a3a6..c3b462e1f 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -530,7 +530,30 @@ impl<'a> RasterBand<'a> { /// * `block_index` - the block index /// /// # Notes - /// The Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis). + /// 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::<u8>((0, 0))?; + /// assert_eq!(arr.shape(), &[300, 6]); + /// # Ok(()) + /// # } + /// ``` pub fn read_block<T: Copy + GdalType>(&self, block_index: (usize, usize)) -> Result<Array2<T>> { if T::gdal_ordinal() != self.band_type() as u32 { return Err(GdalError::BadArgument( @@ -562,6 +585,88 @@ impl<'a> RasterBand<'a> { 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<T>`] 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::DriverManager; + /// use gdal::raster::RasterCreationOption; + /// use ndarray::Array2; + /// + /// let driver = DriverManager::get_driver_by_name("GTiff").unwrap(); + /// let options = [ + /// RasterCreationOption { + /// key: "TILED", + /// value: "YES", + /// }, + /// RasterCreationOption { + /// key: "BLOCKXSIZE", + /// value: "16", + /// }, + /// RasterCreationOption { + /// key: "BLOCKYSIZE", + /// value: "16", + /// }, + /// ]; + /// let dataset = driver + /// .create_with_band_type_with_options::<u16, _>( + /// "/vsimem/test_write_block.tif", + /// 32, + /// 32, + /// 1, + /// &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)?; + /// # Ok(()) + /// # } + /// ``` + pub fn write_block<T: Copy + GdalType>( + &mut self, + block_index: (usize, usize), + block: Array2<T>, + ) -> Result<()> { + if T::gdal_ordinal() != self.band_type() as u32 { + return Err(GdalError::BadArgument( + "array type must match band data type".to_string(), + )); + } + + 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, + ) + }; + if rv != CPLErr::CE_None { + return Err(_last_cpl_err(rv)); + } + Ok(()) + } + /// Write a [`Buffer<T>`] into a [`Dataset`]. /// /// # Arguments diff --git a/src/raster/tests.rs b/src/raster/tests.rs index 7e605b5f6..b4550d0ad 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -12,7 +12,7 @@ use std::path::Path; use std::str::FromStr; #[cfg(feature = "ndarray")] -use ndarray::arr2; +use ndarray::{arr2, Array2, Axis}; #[test] fn test_open() { @@ -393,6 +393,55 @@ fn test_read_block_data() { assert_eq!(array[[0, 99]], 51); } +#[test] +#[cfg(feature = "ndarray")] +fn test_write_block() { + let driver = DriverManager::get_driver_by_name("GTiff").unwrap(); + let options = [ + RasterCreationOption { + key: "TILED", + value: "YES", + }, + RasterCreationOption { + key: "BLOCKXSIZE", + value: "16", + }, + RasterCreationOption { + key: "BLOCKYSIZE", + value: "16", + }, + ]; + let dataset = driver + .create_with_band_type_with_options::<u16, _>( + "/vsimem/test_write_block.tif", + 32, + 32, + 1, + &options, + ) + .unwrap(); + + let mut block_11 = Array2::from_shape_fn((16, 16), |(y, x)| y as u16 * 16 + x as u16 + 1000u16); + let mut block_12 = Array2::from_shape_fn((16, 16), |(y, x)| y as u16 * 16 + x as u16 + 3000u16); + let block_21 = Array2::from_shape_fn((16, 16), |(y, x)| y as u16 * 16 + x as u16 + 2000u16); + 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(); + block_11.append(Axis(1), block_21.view()).unwrap(); + band.write_block((1, 0), block_21).unwrap(); + block_12.append(Axis(1), block_22.view()).unwrap(); + band.write_block((1, 1), block_22).unwrap(); + block_11.append(Axis(0), block_12.view()).unwrap(); + + let buf = band + .read_as::<u16>((0, 0), (32, 32), (32, 32), None) + .unwrap(); + let arr = ndarray::Array2::from_shape_vec((32, 32), buf.data).unwrap(); + assert_eq!(arr, block_11); +} + #[test] fn test_get_band_type() { let driver = DriverManager::get_driver_by_name("MEM").unwrap();