Skip to content

Commit

Permalink
Converted all ndarray-dependent I/O methods in Rasterband
Browse files Browse the repository at this point in the history
to use internal `Buffer` type, and implemented conversion traits
between the types.
  • Loading branch information
metasim committed Dec 17, 2023
1 parent 39249c1 commit c344b93
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 131 deletions.
6 changes: 3 additions & 3 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` and `ndarray::Array2<T>`.
- **Breaking**: Removed `Rasterband::read_as_array`, changed signature of `Rasterband::read_block` to return a `Buffer<T>`.
- **Breaking**: `Rasterband::write_block` now takes a `&Buffer<T>`.
- <https://github.com/georust/gdal/pull/494>

- Defers the gdal_i.lib missing message until after the pkg-config check and outputs pkg-config metadata in case of a static build.
Expand Down
86 changes: 86 additions & 0 deletions src/raster/buffer.rs
Original file line number Diff line number Diff line change
@@ -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<T>` and it's dimensions.
#[derive(Debug, Clone, PartialEq)]
pub struct Buffer<T> {
pub size: (usize, usize),
pub data: Vec<T>,
}

impl<T: GdalType> Buffer<T> {
/// Construct a new buffer from `size` (`(cols, rows)`) and `Vec<T>`.
///
/// # Notes
/// Assumes `size.0 * size.1 == data.len()`.
pub fn new(size: (usize, usize), data: Vec<T>) -> Buffer<T> {
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<T>> {
// 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<u8>;

#[cfg(feature = "ndarray")]
impl<T: GdalType> TryFrom<Buffer<T>> for Array2<T> {
type Error = crate::errors::GdalError;

fn try_from(value: Buffer<T>) -> Result<Self, Self::Error> {
value.to_array()
}
}

#[cfg(feature = "ndarray")]
impl<T: GdalType + Copy> From<Array2<T>> for Buffer<T> {
fn from(value: Array2<T>) -> 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<T> = 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);
}
}
28 changes: 14 additions & 14 deletions src/raster/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
///
Expand All @@ -104,6 +107,3 @@ pub struct RasterCreationOption<'a> {
pub key: &'a str,
pub value: &'a str,
}

#[cfg(test)]
mod tests;
116 changes: 16 additions & 100 deletions src/raster/rasterband.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand All @@ -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<T>`] 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<T: Copy + GdalType>(
&self,
window: (isize, isize),
window_size: (usize, usize),
array_size: (usize, usize),
e_resample_alg: Option<ResampleAlg>,
) -> Result<Array2<T>> {
let data = self.read_as::<T>(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<T>`], where `T` implements [`GdalType`].
pub fn read_band_as<T: Copy + GdalType>(&self) -> Result<Buffer<T>> {
let size = self.size();
Expand Down Expand Up @@ -563,13 +533,12 @@ impl<'a> RasterBand<'a> {
let pixels = size.0 * size.1;
let mut data: Vec<T> = 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 {
Expand All @@ -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<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::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::<u8>((0, 0))?;
/// assert_eq!(arr.shape(), &[300, 6]);
/// # Ok(())
/// # }
/// ```
pub fn read_block_as_array<T: Copy + GdalType>(
&self,
block_index: (usize, usize),
) -> Result<Array2<T>> {
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<T>`] from a [`Dataset`] block, where `T` implements [`GdalType`].
/// Write a [`Buffer<T>`] from a [`Dataset`] block, where `T` implements [`GdalType`].
///
/// # Arguments
/// * `block_index` - the block index
Expand All @@ -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 = [
Expand All @@ -674,29 +600,32 @@ 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<T: Copy + GdalType>(
&mut self,
block_index: (usize, usize),
block: Array2<T>,
block: &Buffer<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,
// 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 {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -1213,19 +1142,6 @@ impl<'a> MajorObject for RasterBand<'a> {

impl<'a> Metadata for RasterBand<'a> {}

pub struct Buffer<T: GdalType> {
pub size: (usize, usize),
pub data: Vec<T>,
}

impl<T: GdalType> Buffer<T> {
pub fn new(size: (usize, usize), data: Vec<T>) -> Buffer<T> {
Buffer { size, data }
}
}

pub type ByteBuffer = Buffer<u8>;

/// Represents a color interpretation of a RasterBand
#[derive(Debug, PartialEq, Eq)]
pub enum ColorInterpretation {
Expand Down
Loading

0 comments on commit c344b93

Please sign in to comment.