From 999bb05de51804d0e8005666c9d0652d746a7d5c Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Wed, 24 Aug 2022 15:11:23 +0200 Subject: [PATCH 1/6] wrapper for gdalmdimtranslate --- src/programs/raster/mdimtranslate.rs | 266 +++++++++++++++++++++++++++ src/programs/raster/mod.rs | 5 + 2 files changed, 271 insertions(+) create mode 100644 src/programs/raster/mdimtranslate.rs diff --git a/src/programs/raster/mdimtranslate.rs b/src/programs/raster/mdimtranslate.rs new file mode 100644 index 000000000..4e94c06e0 --- /dev/null +++ b/src/programs/raster/mdimtranslate.rs @@ -0,0 +1,266 @@ +use crate::{ + errors::*, + utils::{_last_null_pointer_err, _path_to_c_string}, + Dataset, +}; +use gdal_sys::{GDALMultiDimTranslate, GDALMultiDimTranslateOptions}; +use libc::{c_char, c_int}; +use std::{ + borrow::Borrow, + cell::Cell, + ffi::CString, + mem::ManuallyDrop, + path::Path, + ptr::{null, null_mut}, +}; + +/// Wraps a [GDALMultiDimTranslateOptions] object. +/// +/// [GDALMultiDimTranslateOptions]: https://gdal.org/api/gdal_utils.html#_CPPv428GDALMultiDimTranslateOptions +/// +pub struct MultiDimTranslateOption { + c_options: *mut GDALMultiDimTranslateOptions, +} + +impl MultiDimTranslateOption { + /// See [GDALMultiDimTranslateOptionsNew]. + /// + /// [GDALMultiDimTranslateOptionsNew]: https://gdal.org/api/gdal_utils.html#_CPPv431GDALMultiDimTranslateOptionsNewPPcP37GDALMultiDimTranslateOptionsForBinary + /// + pub fn new>, I: IntoIterator>(args: I) -> Result { + // Convert args to CStrings to add terminating null bytes + let cstr_args = args + .into_iter() + .map(CString::new) + .collect::, _>>()?; + + Self::_new(&cstr_args) + } + + fn _new(cstr_args: &[CString]) -> Result { + // Get pointers to the strings + let mut c_args = cstr_args + .iter() + .map(|x| x.as_ptr() as *mut c_char) // These strings don't actually get modified, the C API is just not const-correct + .chain(std::iter::once(null_mut())) // Null-terminate the list + .collect::>(); + + unsafe { + Ok(Self { + c_options: gdal_sys::GDALMultiDimTranslateOptionsNew( + c_args.as_mut_ptr(), + null_mut(), + ), + }) + } + } + + /// Returns the wrapped C pointer + /// + /// # Safety + /// This method returns a raw C pointer + /// + pub unsafe fn c_options(&self) -> *mut GDALMultiDimTranslateOptions { + self.c_options + } +} + +impl Drop for MultiDimTranslateOption { + fn drop(&mut self) { + unsafe { + gdal_sys::GDALMultiDimTranslateOptionsFree(self.c_options); + } + } +} + +impl TryFrom> for MultiDimTranslateOption { + type Error = GdalError; + + fn try_from(value: Vec<&str>) -> std::result::Result { + MultiDimTranslateOption::new(value) + } +} + +pub enum MultiDimTranslateDestination { + Path(CString), + Dataset { + dataset: ManuallyDrop, + drop: Cell, + }, +} + +impl Drop for MultiDimTranslateDestination { + fn drop(&mut self) { + match self { + Self::Path(_) => {} + Self::Dataset { dataset, drop } => { + if drop.get() { + unsafe { + ManuallyDrop::drop(dataset); + } + } + } + } + } +} + +impl MultiDimTranslateDestination { + pub fn dataset(dataset: Dataset) -> Self { + Self::Dataset { + dataset: ManuallyDrop::new(dataset), + drop: Cell::new(true), + } + } + + pub fn path>(path: P) -> Result { + let c_path = _path_to_c_string(path.as_ref())?; + Ok(Self::Path(c_path)) + } + + unsafe fn do_no_drop_dataset(&self) { + match self { + Self::Path(_) => {} + Self::Dataset { dataset: _, drop } => { + drop.set(false); + } + } + } +} + +/// Converts raster data between different formats. +/// +/// Wraps [GDALMultiDimTranslate]. +/// See the [program docs] for more details. +/// +/// [GDALMultiDimTranslate]: https://gdal.org/api/gdal_utils.html#_CPPv421GDALMultiDimTranslatePKc12GDALDatasetHiP12GDALDatasetHPK28GDALMultiDimTranslateOptionsPi +/// [program docs]: https://gdal.org/programs/gdalmdimtranslate.html +/// +pub fn multi_dim_translate>( + input: &[D], + destination: MultiDimTranslateDestination, + options: Option, +) -> Result { + _multi_dim_translate( + &input.iter().map(|x| x.borrow()).collect::>(), + destination, + options, + ) +} + +fn _multi_dim_translate( + input: &[&Dataset], + destination: MultiDimTranslateDestination, + options: Option, +) -> Result { + let (psz_dest_option, h_dst_ds) = match &destination { + MultiDimTranslateDestination::Path(c_path) => (Some(c_path), null_mut()), + MultiDimTranslateDestination::Dataset { dataset, .. } => { + (None, unsafe { dataset.c_dataset() }) + } + }; + + let psz_dest = psz_dest_option.map(|x| x.as_ptr()).unwrap_or_else(null); + + let mut pah_src_ds: Vec = + input.iter().map(|x| unsafe { x.c_dataset() }).collect(); + + let ps_options = options + .as_ref() + .map(|x| x.c_options as *const GDALMultiDimTranslateOptions) + .unwrap_or(null()); + + let mut pb_usage_error: c_int = 0; + + let dataset_out = unsafe { + let data = GDALMultiDimTranslate( + psz_dest, + h_dst_ds, + pah_src_ds.len() as c_int, + pah_src_ds.as_mut_ptr(), + ps_options, + &mut pb_usage_error as *mut c_int, + ); + + // GDAL takes the ownership of `h_dst_ds` + destination.do_no_drop_dataset(); + + data + }; + + if dataset_out.is_null() { + return Err(_last_null_pointer_err("GDALMultiDimTranslate")); + } + + let result = unsafe { Dataset::from_c_dataset(dataset_out) }; + + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::{DatasetOptions, Driver, GdalOpenFlags}; + + #[test] + fn test_build_tiff_from_path() { + let dataset_options = DatasetOptions { + open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER, + allowed_drivers: None, + open_options: None, + sibling_files: None, + }; + let dataset = Dataset::open_ex("fixtures/cf_nasa_4326.nc", dataset_options).unwrap(); + + let mem_file_path = "/vsimem/2d3e9124-a7a0-413e-97b5-e79d46e50ff8"; + + let dataset = multi_dim_translate( + &[dataset], + MultiDimTranslateDestination::path(mem_file_path).unwrap(), + Some( + vec![ + "-array", + "name=/science/grids/imagingGeometry/lookAngle,view=[2,:,:]", + ] + .try_into() + .unwrap(), + ), + ) + .unwrap(); + + assert_eq!(dataset.raster_size(), (5, 7)); + assert_eq!(dataset.raster_count(), 1); + } + + #[test] + fn test_build_tiff_from_dataset() { + let dataset_options = DatasetOptions { + open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER, + allowed_drivers: None, + open_options: None, + sibling_files: None, + }; + let dataset = Dataset::open_ex("fixtures/cf_nasa_4326.nc", dataset_options).unwrap(); + + let driver = Driver::get_by_name("MEM").unwrap(); + let output_dataset = driver.create("", 5, 7, 1).unwrap(); + + let error = multi_dim_translate( + &[output_dataset], + MultiDimTranslateDestination::dataset(dataset), + Some( + MultiDimTranslateOption::new(vec![ + "-array", + "name=/science/grids/imagingGeometry/lookAngle,view=[2,:,:]", + ]) + .unwrap(), + ), + ) + .unwrap_err(); + + assert_eq!( + error.to_string(), + "GDAL method 'GDALMultiDimTranslate' returned a NULL pointer. Error msg: 'Update of existing file not supported yet'" + ); + } +} diff --git a/src/programs/raster/mod.rs b/src/programs/raster/mod.rs index 3032ab57a..3b14ec3c2 100644 --- a/src/programs/raster/mod.rs +++ b/src/programs/raster/mod.rs @@ -1,2 +1,7 @@ +mod mdimtranslate; mod vrt; + +pub use mdimtranslate::{ + multi_dim_translate, MultiDimTranslateDestination, MultiDimTranslateOption, +}; pub use vrt::*; From 2f5deb2df291b57cfb03cfdf21b79236242977c6 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Wed, 24 Aug 2022 15:19:38 +0200 Subject: [PATCH 2/6] added PR info to changelog --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b186307d8..41cf76c04 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - Add prebuild bindings for GDAL 3.5 + - - **Breaking**: Add `gdal::vector::OwnedLayer`, `gdal::vector::LayerAccess` and `gdal::vector::layer::OwnedFeatureIterator`. This requires importing `gdal::vector::LayerAccess` for using most vector layer methods. @@ -66,6 +67,10 @@ - +- Added program wrapper for `GDALMultiDimTranslate` + + - + ## 0.12 - Bump Rust edition to 2021 From 52c905bfc42db000d200973cd4f73d3034dd20f0 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Wed, 24 Aug 2022 15:21:33 +0200 Subject: [PATCH 3/6] restrict `multi_dim_translate` to gdal >= 3.1 --- src/programs/raster/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/programs/raster/mod.rs b/src/programs/raster/mod.rs index 3b14ec3c2..30d3081ea 100644 --- a/src/programs/raster/mod.rs +++ b/src/programs/raster/mod.rs @@ -1,6 +1,8 @@ +#[cfg(all(major_ge_3, minor_ge_1))] mod mdimtranslate; mod vrt; +#[cfg(all(major_ge_3, minor_ge_1))] pub use mdimtranslate::{ multi_dim_translate, MultiDimTranslateDestination, MultiDimTranslateOption, }; From 23957df6195fc5557422ffd81aa724c7aa4aaaf1 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Wed, 24 Aug 2022 15:37:50 +0200 Subject: [PATCH 4/6] name changes & prettifying code --- src/programs/raster/mdimtranslate.rs | 18 +++++++++--------- src/programs/raster/mod.rs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/programs/raster/mdimtranslate.rs b/src/programs/raster/mdimtranslate.rs index 4e94c06e0..7af0394d4 100644 --- a/src/programs/raster/mdimtranslate.rs +++ b/src/programs/raster/mdimtranslate.rs @@ -18,11 +18,11 @@ use std::{ /// /// [GDALMultiDimTranslateOptions]: https://gdal.org/api/gdal_utils.html#_CPPv428GDALMultiDimTranslateOptions /// -pub struct MultiDimTranslateOption { +pub struct MultiDimTranslateOptions { c_options: *mut GDALMultiDimTranslateOptions, } -impl MultiDimTranslateOption { +impl MultiDimTranslateOptions { /// See [GDALMultiDimTranslateOptionsNew]. /// /// [GDALMultiDimTranslateOptionsNew]: https://gdal.org/api/gdal_utils.html#_CPPv431GDALMultiDimTranslateOptionsNewPPcP37GDALMultiDimTranslateOptionsForBinary @@ -65,7 +65,7 @@ impl MultiDimTranslateOption { } } -impl Drop for MultiDimTranslateOption { +impl Drop for MultiDimTranslateOptions { fn drop(&mut self) { unsafe { gdal_sys::GDALMultiDimTranslateOptionsFree(self.c_options); @@ -73,11 +73,11 @@ impl Drop for MultiDimTranslateOption { } } -impl TryFrom> for MultiDimTranslateOption { +impl TryFrom> for MultiDimTranslateOptions { type Error = GdalError; - fn try_from(value: Vec<&str>) -> std::result::Result { - MultiDimTranslateOption::new(value) + fn try_from(value: Vec<&str>) -> Result { + MultiDimTranslateOptions::new(value) } } @@ -138,7 +138,7 @@ impl MultiDimTranslateDestination { pub fn multi_dim_translate>( input: &[D], destination: MultiDimTranslateDestination, - options: Option, + options: Option, ) -> Result { _multi_dim_translate( &input.iter().map(|x| x.borrow()).collect::>(), @@ -150,7 +150,7 @@ pub fn multi_dim_translate>( fn _multi_dim_translate( input: &[&Dataset], destination: MultiDimTranslateDestination, - options: Option, + options: Option, ) -> Result { let (psz_dest_option, h_dst_ds) = match &destination { MultiDimTranslateDestination::Path(c_path) => (Some(c_path), null_mut()), @@ -249,7 +249,7 @@ mod tests { &[output_dataset], MultiDimTranslateDestination::dataset(dataset), Some( - MultiDimTranslateOption::new(vec![ + MultiDimTranslateOptions::new(vec![ "-array", "name=/science/grids/imagingGeometry/lookAngle,view=[2,:,:]", ]) diff --git a/src/programs/raster/mod.rs b/src/programs/raster/mod.rs index 30d3081ea..b3a53e11f 100644 --- a/src/programs/raster/mod.rs +++ b/src/programs/raster/mod.rs @@ -4,6 +4,6 @@ mod vrt; #[cfg(all(major_ge_3, minor_ge_1))] pub use mdimtranslate::{ - multi_dim_translate, MultiDimTranslateDestination, MultiDimTranslateOption, + multi_dim_translate, MultiDimTranslateDestination, MultiDimTranslateOptions, }; pub use vrt::*; From 40dc7830cd586f33451b1777a928ef15c6bc9742 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Wed, 24 Aug 2022 15:50:15 +0200 Subject: [PATCH 5/6] replaced cell with plain boolean --- src/programs/raster/mdimtranslate.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/programs/raster/mdimtranslate.rs b/src/programs/raster/mdimtranslate.rs index 7af0394d4..92542b5f9 100644 --- a/src/programs/raster/mdimtranslate.rs +++ b/src/programs/raster/mdimtranslate.rs @@ -7,7 +7,6 @@ use gdal_sys::{GDALMultiDimTranslate, GDALMultiDimTranslateOptions}; use libc::{c_char, c_int}; use std::{ borrow::Borrow, - cell::Cell, ffi::CString, mem::ManuallyDrop, path::Path, @@ -85,7 +84,7 @@ pub enum MultiDimTranslateDestination { Path(CString), Dataset { dataset: ManuallyDrop, - drop: Cell, + drop: bool, }, } @@ -94,7 +93,7 @@ impl Drop for MultiDimTranslateDestination { match self { Self::Path(_) => {} Self::Dataset { dataset, drop } => { - if drop.get() { + if *drop { unsafe { ManuallyDrop::drop(dataset); } @@ -108,7 +107,7 @@ impl MultiDimTranslateDestination { pub fn dataset(dataset: Dataset) -> Self { Self::Dataset { dataset: ManuallyDrop::new(dataset), - drop: Cell::new(true), + drop: true, } } @@ -117,11 +116,11 @@ impl MultiDimTranslateDestination { Ok(Self::Path(c_path)) } - unsafe fn do_no_drop_dataset(&self) { + unsafe fn do_no_drop_dataset(&mut self) { match self { Self::Path(_) => {} Self::Dataset { dataset: _, drop } => { - drop.set(false); + *drop = false; } } } @@ -149,7 +148,7 @@ pub fn multi_dim_translate>( fn _multi_dim_translate( input: &[&Dataset], - destination: MultiDimTranslateDestination, + mut destination: MultiDimTranslateDestination, options: Option, ) -> Result { let (psz_dest_option, h_dst_ds) = match &destination { From c5c12c6a7b3cde4258e807cc5deeca8802fc19c6 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Fri, 26 Aug 2022 11:50:11 +0200 Subject: [PATCH 6/6] more convenience functions --- src/programs/raster/mdimtranslate.rs | 36 +++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/programs/raster/mdimtranslate.rs b/src/programs/raster/mdimtranslate.rs index 92542b5f9..c59404541 100644 --- a/src/programs/raster/mdimtranslate.rs +++ b/src/programs/raster/mdimtranslate.rs @@ -9,7 +9,7 @@ use std::{ borrow::Borrow, ffi::CString, mem::ManuallyDrop, - path::Path, + path::{Path, PathBuf}, ptr::{null, null_mut}, }; @@ -88,6 +88,36 @@ pub enum MultiDimTranslateDestination { }, } +impl TryFrom<&str> for MultiDimTranslateDestination { + type Error = GdalError; + + fn try_from(path: &str) -> Result { + Self::path(path) + } +} + +impl TryFrom<&Path> for MultiDimTranslateDestination { + type Error = GdalError; + + fn try_from(path: &Path) -> Result { + Self::path(path) + } +} + +impl TryFrom for MultiDimTranslateDestination { + type Error = GdalError; + + fn try_from(path: PathBuf) -> Result { + Self::path(path) + } +} + +impl From for MultiDimTranslateDestination { + fn from(dataset: Dataset) -> Self { + Self::dataset(dataset) + } +} + impl Drop for MultiDimTranslateDestination { fn drop(&mut self) { match self { @@ -215,7 +245,7 @@ mod tests { let dataset = multi_dim_translate( &[dataset], - MultiDimTranslateDestination::path(mem_file_path).unwrap(), + mem_file_path.try_into().unwrap(), Some( vec![ "-array", @@ -246,7 +276,7 @@ mod tests { let error = multi_dim_translate( &[output_dataset], - MultiDimTranslateDestination::dataset(dataset), + dataset.into(), Some( MultiDimTranslateOptions::new(vec![ "-array",