Skip to content

Commit

Permalink
Merge #289
Browse files Browse the repository at this point in the history
289: wrapper for gdalmdimtranslate r=lnicola a=ChristianBeilschmidt

- [X] I agree to follow the project's [code of conduct](https://github.com/georust/gdal/blob/master/CODE_OF_CONDUCT.md).
- [X] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users.
---

I created a wrapper around `GDALMultiDimTranslate`. I used part of the logic for the wrapper for the VRT program.

Co-authored-by: Christian Beilschmidt <[email protected]>
  • Loading branch information
bors[bot] and ChristianBeilschmidt authored Aug 27, 2022
2 parents 2e063a7 + c5c12c6 commit 803cabd
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Add prebuild bindings for GDAL 3.5

- <https://github.com/georust/gdal/pull/277>

- **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.
Expand Down Expand Up @@ -75,6 +76,10 @@

- <https://github.com/georust/gdal/pull/284>

- Added program wrapper for `GDALMultiDimTranslate`

- <https://github.com/georust/gdal/pull/289>

## 0.12

- Bump Rust edition to 2021
Expand Down
295 changes: 295 additions & 0 deletions src/programs/raster/mdimtranslate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
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,
ffi::CString,
mem::ManuallyDrop,
path::{Path, PathBuf},
ptr::{null, null_mut},
};

/// Wraps a [GDALMultiDimTranslateOptions] object.
///
/// [GDALMultiDimTranslateOptions]: https://gdal.org/api/gdal_utils.html#_CPPv428GDALMultiDimTranslateOptions
///
pub struct MultiDimTranslateOptions {
c_options: *mut GDALMultiDimTranslateOptions,
}

impl MultiDimTranslateOptions {
/// See [GDALMultiDimTranslateOptionsNew].
///
/// [GDALMultiDimTranslateOptionsNew]: https://gdal.org/api/gdal_utils.html#_CPPv431GDALMultiDimTranslateOptionsNewPPcP37GDALMultiDimTranslateOptionsForBinary
///
pub fn new<S: Into<Vec<u8>>, I: IntoIterator<Item = S>>(args: I) -> Result<Self> {
// Convert args to CStrings to add terminating null bytes
let cstr_args = args
.into_iter()
.map(CString::new)
.collect::<std::result::Result<Vec<_>, _>>()?;

Self::_new(&cstr_args)
}

fn _new(cstr_args: &[CString]) -> Result<Self> {
// 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::<Vec<_>>();

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 MultiDimTranslateOptions {
fn drop(&mut self) {
unsafe {
gdal_sys::GDALMultiDimTranslateOptionsFree(self.c_options);
}
}
}

impl TryFrom<Vec<&str>> for MultiDimTranslateOptions {
type Error = GdalError;

fn try_from(value: Vec<&str>) -> Result<Self> {
MultiDimTranslateOptions::new(value)
}
}

pub enum MultiDimTranslateDestination {
Path(CString),
Dataset {
dataset: ManuallyDrop<Dataset>,
drop: bool,
},
}

impl TryFrom<&str> for MultiDimTranslateDestination {
type Error = GdalError;

fn try_from(path: &str) -> Result<Self> {
Self::path(path)
}
}

impl TryFrom<&Path> for MultiDimTranslateDestination {
type Error = GdalError;

fn try_from(path: &Path) -> Result<Self> {
Self::path(path)
}
}

impl TryFrom<PathBuf> for MultiDimTranslateDestination {
type Error = GdalError;

fn try_from(path: PathBuf) -> Result<Self> {
Self::path(path)
}
}

impl From<Dataset> for MultiDimTranslateDestination {
fn from(dataset: Dataset) -> Self {
Self::dataset(dataset)
}
}

impl Drop for MultiDimTranslateDestination {
fn drop(&mut self) {
match self {
Self::Path(_) => {}
Self::Dataset { dataset, drop } => {
if *drop {
unsafe {
ManuallyDrop::drop(dataset);
}
}
}
}
}
}

impl MultiDimTranslateDestination {
pub fn dataset(dataset: Dataset) -> Self {
Self::Dataset {
dataset: ManuallyDrop::new(dataset),
drop: true,
}
}

pub fn path<P: AsRef<Path>>(path: P) -> Result<Self> {
let c_path = _path_to_c_string(path.as_ref())?;
Ok(Self::Path(c_path))
}

unsafe fn do_no_drop_dataset(&mut self) {
match self {
Self::Path(_) => {}
Self::Dataset { dataset: _, drop } => {
*drop = 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<D: Borrow<Dataset>>(
input: &[D],
destination: MultiDimTranslateDestination,
options: Option<MultiDimTranslateOptions>,
) -> Result<Dataset> {
_multi_dim_translate(
&input.iter().map(|x| x.borrow()).collect::<Vec<&Dataset>>(),
destination,
options,
)
}

fn _multi_dim_translate(
input: &[&Dataset],
mut destination: MultiDimTranslateDestination,
options: Option<MultiDimTranslateOptions>,
) -> Result<Dataset> {
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<gdal_sys::GDALDatasetH> =
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],
mem_file_path.try_into().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],
dataset.into(),
Some(
MultiDimTranslateOptions::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'"
);
}
}
7 changes: 7 additions & 0 deletions src/programs/raster/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
#[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, MultiDimTranslateOptions,
};
pub use vrt::*;

0 comments on commit 803cabd

Please sign in to comment.