Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrapper for gdalmdimtranslate #289

Merged
merged 9 commits into from Sep 2, 2022
4 changes: 4 additions & 0 deletions CHANGES.md
Expand Up @@ -80,6 +80,10 @@

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

- Added program wrapper for `GDALMultiDimTranslate`

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

- Test that `GdalError` is `Send`

- <https://github.com/georust/gdal/pull/293>
Expand Down
295 changes: 295 additions & 0 deletions src/programs/raster/mdimtranslate.rs
@@ -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
@@ -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::*;