From 6e9c7c74eb4a53fbfb54f072dcdd3fa5ee5c278e Mon Sep 17 00:00:00 2001 From: "Brendan C. Ward" Date: Thu, 12 May 2022 16:40:19 -0700 Subject: [PATCH 01/21] Add CoordTransform::transform_bounds() --- src/spatial_ref/srs.rs | 57 +++++++++++++++++++++++++++++++++++- src/spatial_ref/tests.rs | 62 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/spatial_ref/srs.rs b/src/spatial_ref/srs.rs index b4368dd3..7eca66db 100644 --- a/src/spatial_ref/srs.rs +++ b/src/spatial_ref/srs.rs @@ -1,6 +1,6 @@ use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string}; use gdal_sys::{self, CPLErr, OGRCoordinateTransformationH, OGRErr, OGRSpatialReferenceH}; -use libc::{c_char, c_int}; +use libc::{c_char, c_double, c_int}; use std::ffi::{CStr, CString}; use std::ptr::{self, null_mut}; use std::str::FromStr; @@ -33,6 +33,61 @@ impl CoordTransform { }) } + /// Transform bounding box, densifying the edges to account for nonlinear + /// transformations. + /// + /// # Arguments + /// * bounds - array of [axis0_min, axis1_min, axis0_max, axis1_min], + /// interpreted in the axis order of the source SpatialRef, + /// typically [xmin, ymin, xmax, ymax] + /// * densify_pts - number of points per edge (recommended: 21) + /// + /// # Returns + /// Some([f64; 4]) with bounds in axis order of target SpatialRef + /// None if there is an error. + #[cfg(all(major_ge_3, minor_ge_4))] + pub fn transform_bounds(&self, bounds: &[f64; 4], densify_pts: i32) -> Result<([f64; 4])> { + let mut out_xmin: f64 = 0.; + let mut out_ymin: f64 = 0.; + let mut out_xmax: f64 = 0.; + let mut out_ymax: f64 = 0.; + + let ret_val = unsafe { + gdal_sys::OCTTransformBounds( + self.inner, + bounds[0] as c_double, + bounds[1] as c_double, + bounds[2] as c_double, + bounds[3] as c_double, + &mut out_xmin as *mut c_double, + &mut out_ymin as *mut c_double, + &mut out_xmax as *mut c_double, + &mut out_ymax as *mut c_double, + densify_pts as c_int, + ) == 1 + }; + + if ret_val { + Ok([out_xmin, out_ymin, out_xmax, out_ymax]) + } else { + let err = _last_cpl_err(CPLErr::CE_Failure); + let msg = if let GdalError::CplError { msg, .. } = err { + if msg.trim().is_empty() { + None + } else { + Some(msg) + } + } else { + return Err(err); + }; + Err(GdalError::InvalidCoordinateRange { + from: self.from.clone(), + to: self.to.clone(), + msg, + }) + } + } + /// Transform coordinates in place. /// /// # Arguments diff --git a/src/spatial_ref/tests.rs b/src/spatial_ref/tests.rs index 03e72373..495e0cdc 100644 --- a/src/spatial_ref/tests.rs +++ b/src/spatial_ref/tests.rs @@ -64,6 +64,68 @@ fn comparison() { assert!(spatial_ref5 == spatial_ref4); } +#[cfg(all(major_ge_3, minor_ge_4))] +#[test] +fn transform_bounds() { + let bounds: [f64; 4] = [-180., -80., 180., 80.]; + // bounds for y,x ordered SpatialRefs + let yx_bounds: [f64; 4] = [-80.0, -180.0, 80.0, 180.]; + + let spatial_ref1 = SpatialRef::from_definition("OGC:CRS84").unwrap(); + + // transforming between the same SpatialRef should return existing bounds + let mut transform = CoordTransform::new(&spatial_ref1, &spatial_ref1).unwrap(); + let mut out_bounds = transform.transform_bounds(&bounds, 21).unwrap(); + assert_almost_eq(out_bounds[0], bounds[0]); + assert_almost_eq(out_bounds[1], bounds[1]); + assert_almost_eq(out_bounds[2], bounds[2]); + assert_almost_eq(out_bounds[3], bounds[3]); + + // EPSG:4326 is in y,x order by default; returned bounds are [ymin, xmin, ymax, xmax] + let mut spatial_ref2 = SpatialRef::from_epsg(4326).unwrap(); + transform = CoordTransform::new(&spatial_ref1, &spatial_ref2).unwrap(); + out_bounds = transform.transform_bounds(&bounds, 21).unwrap(); + assert_almost_eq(out_bounds[0], yx_bounds[0]); + assert_almost_eq(out_bounds[1], yx_bounds[1]); + assert_almost_eq(out_bounds[2], yx_bounds[2]); + assert_almost_eq(out_bounds[3], yx_bounds[3]); + + // if source SpatialRef is in y,x order and and target SpatialRef is in x,y order + // input bounds are interpreted as [ymin, xmin, ymax, xmax] and returns + // [xmin, ymin, xmax, ymax] + transform = CoordTransform::new(&spatial_ref2, &spatial_ref1).unwrap(); + out_bounds = transform.transform_bounds(&yx_bounds, 21).unwrap(); + assert_almost_eq(out_bounds[0], bounds[0]); + assert_almost_eq(out_bounds[1], bounds[1]); + assert_almost_eq(out_bounds[2], bounds[2]); + assert_almost_eq(out_bounds[3], bounds[3]); + + // force EPSG:4326 into x,y order to match source SpatialRef + spatial_ref2 + .set_axis_mapping_strategy(gdal_sys::OSRAxisMappingStrategy::OAMS_TRADITIONAL_GIS_ORDER); + transform = CoordTransform::new(&spatial_ref1, &spatial_ref2).unwrap(); + out_bounds = transform.transform_bounds(&bounds, 21).unwrap(); + assert_almost_eq(out_bounds[0], bounds[0]); + assert_almost_eq(out_bounds[1], bounds[1]); + assert_almost_eq(out_bounds[2], bounds[2]); + assert_almost_eq(out_bounds[3], bounds[3]); + + spatial_ref2 = SpatialRef::from_epsg(3857).unwrap(); + transform = CoordTransform::new(&spatial_ref1, &spatial_ref2).unwrap(); + out_bounds = transform.transform_bounds(&bounds, 21).unwrap(); + + let expected_bounds: [f64; 4] = [ + -20037508.342789244, + -15538711.096309224, + 20037508.342789244, + 15538711.09630923, + ]; + assert_almost_eq(out_bounds[0], expected_bounds[0]); + assert_almost_eq(out_bounds[1], expected_bounds[1]); + assert_almost_eq(out_bounds[2], expected_bounds[2]); + assert_almost_eq(out_bounds[3], expected_bounds[3]); +} + #[test] fn transform_coordinates() { let spatial_ref1 = SpatialRef::from_wkt("GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",7030]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY[\"EPSG\",6326]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",8901]],UNIT[\"DMSH\",0.0174532925199433,AUTHORITY[\"EPSG\",9108]],AXIS[\"Lat\",NORTH],AXIS[\"Long\",EAST],AUTHORITY[\"EPSG\",4326]]").unwrap(); From 9061914a31d1447fa1ea36030179d2e36ee74e4a Mon Sep 17 00:00:00 2001 From: "Brendan C. Ward" Date: Thu, 12 May 2022 16:45:34 -0700 Subject: [PATCH 02/21] Add changes --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d4bdd844..28bd36be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,10 @@ - +- Add `gdal::srs::CoordTransform::transform_bounds` as wrapper for `OCTTransformBounds` for GDAL 3.4 + + - + ## 0.12 - Bump Rust edition to 2021 From 7accadcc94d87cff0a87646624aeb5476dde9a76 Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Thu, 18 Nov 2021 15:29:33 +1000 Subject: [PATCH 03/21] added root_group function --- src/dataset.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dataset.rs b/src/dataset.rs index fe7003e7..d5609f42 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -15,7 +15,7 @@ use crate::utils::{_last_cpl_err, _last_null_pointer_err, _path_to_c_string, _st use crate::vector::{sql, Geometry, OwnedLayer}; use crate::{ gdal_major_object::MajorObject, raster::RasterBand, spatial_ref::SpatialRef, vector::Layer, - Driver, Metadata, + Driver, Metadata, raster::mdarray::Group }; use gdal_sys::OGRGeometryH; use gdal_sys::{ @@ -414,6 +414,15 @@ impl Dataset { Ok(RasterBand::from_c_rasterband(self, c_band)) } } + pub fn root_group(&self) -> Result { + unsafe { + let c_group = gdal_sys::GDALDatasetGetRootGroup(self.c_dataset()); + if c_group.is_null() { + return Err(_last_null_pointer_err("GDALGetRasterBand")); + } + Ok(Group::from_c_group(self, c_group)) + } + } /// Builds overviews for the current `Dataset`. See [`GDALBuildOverviews`]. /// From 20beacaf1da4ac88175df75cc02d65a3b37da507 Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Thu, 18 Nov 2021 15:30:08 +1000 Subject: [PATCH 04/21] added mdarray --- src/raster/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/raster/mod.rs b/src/raster/mod.rs index 8c04d7b2..3443c1c3 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -4,6 +4,7 @@ mod rasterband; mod rasterize; mod types; mod warp; +pub(crate) mod mdarray; pub use rasterband::{Buffer, ByteBuffer, ColorInterpretation, RasterBand, ResampleAlg}; pub use rasterize::{rasterize, BurnSource, MergeAlgorithm, OptimizeMode, RasterizeOptions}; From f9a87b9f6e373f228c51f1b244337e0fd5eabcc2 Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Thu, 18 Nov 2021 15:30:51 +1000 Subject: [PATCH 05/21] Added basic support for mdarrays --- src/raster/mdarray.rs | 352 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 src/raster/mdarray.rs diff --git a/src/raster/mdarray.rs b/src/raster/mdarray.rs new file mode 100644 index 00000000..e273fe3c --- /dev/null +++ b/src/raster/mdarray.rs @@ -0,0 +1,352 @@ +use crate::utils::{_last_cpl_err, _string, _string_array}; +use crate::{cpl::CslStringList, Dataset}; +use gdal_sys::{GDALDimensionGetName,GDALDimensionGetSize, CPLErr, GDALDimensionH, GDALDimensionHS, GDALGroupGetMDArrayNames, GDALGroupGetName, GDALGroupH, GDALGroupOpenMDArray, GDALMDArrayGetDataType, GDALMDArrayGetDimensionCount, GDALMDArrayGetDimensions, GDALMDArrayGetTotalElementsCount, GDALMDArrayHS}; +use libc::c_void; +use std::convert::TryInto; +use std::ffi::CString; +use crate::errors::*; +use super::GdalType; + +#[cfg(feature = "ndarray")] +use ndarray::Array2; +use ndarray::ArrayD; +use ndarray::IxDyn; +use std::fmt::Debug; + + +#[cfg(test)] +mod tests { + use crate::{Dataset, DatasetOptions, GdalOpenFlags, cpl}; + + #[test] + fn test_root_group_name() { + let options = DatasetOptions { + open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER, + allowed_drivers: None, + open_options: None, + sibling_files: None, + }; + let dataset = Dataset::open_ex("fixtures/byte_no_cf.nc", options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let root_group_name = root_group.name(); + assert_eq!(root_group_name, "/"); + } + #[test] + fn test_array_names() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let root_group_name = root_group.name(); + let options = cpl::CslStringList::new(); //Driver specific options determining how groups should be retrieved. Pass nullptr for default behavior. + let array_names = root_group.array_names(options); + assert_eq!(array_names, vec!["Band1".to_string()]) + } + + #[test] + fn test_n_dimension() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let root_group_name = root_group.name(); + let array_name = "Band1".to_string(); + let options = cpl::CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. + let md_array = root_group.md_array(array_name, options); + let n_dimension = md_array.n_dimension(); + assert_eq!(2, n_dimension); + } + + #[test] + fn test_n_elements() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let root_group_name = root_group.name(); + let array_name = "Band1".to_string(); + let options = cpl::CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. + let md_array = root_group.md_array(array_name, options); + let n_elements = md_array.n_elements(); + assert_eq!(400, n_elements); + } + + #[test] + fn test_dimension_name() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let root_group_name = root_group.name(); + let array_name = "Band1".to_string(); + let options = cpl::CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. + let md_array = root_group.md_array(array_name, options); + let dimensions = md_array.get_dimensions().unwrap(); + let mut dimension_names = Vec::new(); + for dimension in dimensions{ + dimension_names.push(dimension.name()); + } + assert_eq!(dimension_names, vec!["y".to_string(), "x".to_string()]) + } + #[test] + fn test_dimension_size() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let root_group_name = root_group.name(); + let array_name = "Band1".to_string(); + let options = cpl::CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. + let md_array = root_group.md_array(array_name, options); + let dimensions = md_array.get_dimensions().unwrap(); + let mut dimensions_size = Vec::new(); + for dimension in dimensions{ + dimensions_size.push(dimension.size()); + } + assert_eq!(dimensions_size, vec![20,20]) + } + +} + + +/// Represent an MDArray in a Group +/// +/// This object carries the lifetime of the Group that +/// contains it. This is necessary to prevent the Group +/// from being dropped before the mdarray. +#[derive(Debug)] +pub struct MDArray<'a> { + c_mdarray: *mut GDALMDArrayHS,//H + group: &'a Group<'a>, +} + + +pub struct Dimension<'a>{ + c_dimension: *mut GDALDimensionHS, + md_array: &'a MDArray<'a> +} + +impl<'a> Dimension<'a> { + /// Create a MDArray from a wrapped C pointer + /// + /// # Safety + /// This method operates on a raw C pointer + pub fn from_c_dimension(md_array: &'a MDArray<'a>, c_dimension: *mut GDALDimensionHS) -> Self { + Dimension { c_dimension: (c_dimension), md_array: (md_array) } + } + pub fn size(self) -> usize { + unsafe {GDALDimensionGetSize(self.c_dimension) as usize } + + } + + pub fn name(self) -> String { + _string( unsafe {GDALDimensionGetName(self.c_dimension) } ) + + } +} + +impl<'a> MDArray<'a> { + /// Create a MDArray from a wrapped C pointer + /// + /// # Safety + /// This method operates on a raw C pointer + pub fn from_c_mdarray(group: &'a Group, c_mdarray: *mut GDALMDArrayHS) -> Self { + MDArray { c_mdarray, group } + } + + pub fn n_dimension(&self ) -> usize { + unsafe { GDALMDArrayGetDimensionCount(self.c_mdarray) + } + } + + pub fn n_elements(&self) -> u64 { + unsafe { GDALMDArrayGetTotalElementsCount(self.c_mdarray) } + } + + pub fn get_dimensions(&self) -> Result> + { + // break on ndims not is_null + let n_dimension = self.n_dimension(); + unsafe { + let mut pn_count: usize = 0; + let pn_count_ptr: *mut usize = &mut pn_count; + let c_dimensions = GDALMDArrayGetDimensions(self.c_mdarray, pn_count_ptr); + // if c_group.is_null() { + // return Err(_last_null_pointer_err("GDALGetRasterBand")); + // } + let mut dimensions: Vec = Vec::new(); + let mut i = 0 ; + while i< n_dimension { + let ptr = c_dimensions.add(i); + let next = ptr.read(); + let value = Dimension::from_c_dimension(self, next); + i += 1; + dimensions.push(value); + } + println!("len {:?}", dimensions.len()); + + let size = GDALDimensionGetSize(dimensions[0].c_dimension); + + + Ok(dimensions) + } + } + + + pub fn read_into_slice( + &self, buffer: &mut [T], + array_start_index: Vec, + count: Vec + + + ) -> Result<()> { + + // let array_start_index = [array_start_index.0, array_start_index.1]; + // let count = [count.0, count.1]; + let array_step: *const i64 = std::ptr::null(); + let buffer_stride: *const i64 = std::ptr::null(); + let data_type = unsafe{GDALMDArrayGetDataType(self.c_mdarray)}; + let p_dst_buffer_alloc_start: *mut libc::c_void = std::ptr::null_mut(); + let n_dst_buffer_alloc_size = 0 as usize; + + let rv = unsafe { + gdal_sys::GDALMDArrayRead( + self.c_mdarray, + array_start_index.as_ptr(), + count.as_ptr(), + array_step, + buffer_stride, + data_type, + buffer.as_mut_ptr() as *mut c_void, // pDstBuffer: *mut libc::c_void, + p_dst_buffer_alloc_start, // pDstBufferAllocStart: *const libc::c_void, + n_dst_buffer_alloc_size, + ) + }; + + if rv != 1 { + return Err(_last_cpl_err(rv.try_into().unwrap())); // this is probably incorrect! + } + + Ok(()) + } + + /// Read a 'Buffer' from this band. T implements 'GdalType' + /// + /// # Arguments + /// * array_start_index - Values representing the starting index to read in each dimension (in [0, aoDims[i].GetSize()-1] range). Array of GetDimensionCount() values. Must not be nullptr, unless for a zero-dimensional array. + /// * count - Values representing the number of values to extract in each dimension. Array of GetDimensionCount() values. Must not be nullptr, unless for a zero-dimensional array. + pub fn read_as( + &self, + array_start_index: Vec, + count: Vec, + + ) -> Result> { + + let pixels: usize = count.iter().product(); + let mut data: Vec = Vec::with_capacity(pixels); + + // Safety: the read_into_slice line below writes + // exactly pixel elements into the slice, before we + // read from this slice. This paradigm is suggested + // in the rust std docs + // (https://doc.rust-lang.org/std/vec/struct.Vec.html#examples-18) + unsafe { + data.set_len(pixels); + }; + self.read_into_slice( &mut data, array_start_index, count)?; + + Ok(data) + } + + #[cfg(feature = "ndarray")] + /// Read a 'Array2' from this band. 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 + /// # Docs + /// The Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis). + pub fn read_as_array( + &self, + array_start_index: Vec, + count: Vec, + array_size: Vec, + ) -> Result> { + let data = self.read_as::(array_start_index, count)?; + // Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis) + + let dim : IxDyn = IxDyn(&array_size); + Ok(ArrayD::from_shape_vec( + dim, + data, + )?) + } + +} + +/// Represent a mdarray in a dataset +/// +/// This object carries the lifetime of the dataset that +/// contains it. This is necessary to prevent the dataset +/// from being dropped before the group. +#[derive(Debug)] +pub struct Group<'a> { + c_group: GDALGroupH, + dataset: &'a Dataset, +} + + + +impl<'a> Group<'a> { + /// Create a Group from a wrapped C pointer + /// + /// # Safety + /// This method operates on a raw C pointer + pub unsafe fn from_c_group(dataset: &'a Dataset, c_group: GDALGroupH) -> Self { + Group { c_group, dataset } + } + pub fn name(&self) -> String { + _string(unsafe { GDALGroupGetName(self.c_group) }) + } + + pub fn array_names(&self, options: CslStringList) -> Vec { + let options = options.as_ptr(); + let c_array_names = unsafe { GDALGroupGetMDArrayNames(self.c_group, options) }; + _string_array(c_array_names) + } + + pub fn md_array(&self, name: String, options: CslStringList) -> MDArray{ + let name = CString::new(name).unwrap(); + let c_mdarray = unsafe {GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options.as_ptr() )}; + + MDArray::from_c_mdarray(&self, c_mdarray) + } + // pub unsafe fn array(&self, array_name: String, options: CslStringList) -> MDArray{ + // let name = CString::new(array_name).unwrap(); + // let options = options.as_ptr(); + // let array_h = GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options); + // MDArray::from_c_mdarray(&self, array_h) + // } +} From 2499b05af6acee84b01212ac611d90a6d4678941 Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Thu, 18 Nov 2021 15:31:35 +1000 Subject: [PATCH 06/21] Add test data for mdarrays --- fixtures/byte_no_cf.nc | Bin 0 -> 1420 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 fixtures/byte_no_cf.nc diff --git a/fixtures/byte_no_cf.nc b/fixtures/byte_no_cf.nc new file mode 100644 index 0000000000000000000000000000000000000000..b9bf53e922ce6fcae5a7848c09172916b43785b2 GIT binary patch literal 1420 zcmZ`&J8#=a5SH^O68oCNRaX(L86Qc>q*yW*Bg=>+L#Be9gn;0!Rtm8c<#6e|w%u}z zE4E~-lBG(QY4dk-#R0ilGLB(DM}pkhnQvylnVmW9y*Ow!&r?_)z<2Pm+5e{GPj_-7 zwOWTDDBLgWPpdpxtmCVzBwO8DTdkuX_CtCV7fGB(IbJF&_&j)bF&UqqUe5KA-?Lrq z4{aI_wa=RjYnCs5lESQW3 zy}5px#yNp7UB}T4r|oua(>d-Kw%0lK+^*wwZL8gdzu`~Av+-mQ{yEo!i_5-l=&osY z^*3+!SRC7NmE~eG7zV(kpJSYTNYGo|F!#W_o@pugbTkO(`U0;24#;EEayut&+p|wB z&+D|?dt5!sw)b=B{Nyk`4^G3ucr@3;JkIEQoKqYPagO3*lk33JpZLQd3?_3ug~}J# z3mk2h(I!t;Nd`RbArQ`r94<#yb&T>t0LQd| z2GETYQ$2eSnYzFl0@kKDYh2_|8*5)F%%+O|>j5-Ma2q}FFGIMaT3L5Q9dIw(UzNh{i8pB?{rMpHZ{l8Os(ynSZ&yt+B4YKPO8TrwOSAVf%Wn8 zI9ph?x|xwitb6@2$WPKuwu-WNjqfnO_Iv(IO*v|#>-Aqa*O~xNqG?8xRfZRL*hfWv zty~Y^$7!;N;Cfeeq=%qWJif#CG8m)ckuaN}yPe%5HHp#PDE{Ak0{-A{$mw73 zZS8iYEX#7oCFMjt6p`xN_ph7?Ci!+N1!n}_awjUr5oPBTF;ViZ5a^~7VoUi9(HTMn zaYQ)Z%95!l0#VRWPG^h?B4&h=8Df-D#w3-9OU?;H0OCkc$q;8!R1z^b3Xz+NP)d*> zm0+NOAEh%=DS7~;oO9(sB+vm?q{avYM>4|UAj%YH0KxH$1Dq-52t-PxI-Uups6nm_ z?GX7=Ng=q3EVx>D#pq2{NrfcfUjlK?zY<<@0H_=!+cGE@FQG<2P8|g=p%M~@zyPbW hroclvC{B>Bq+6&3Q`{j1R02nEg^(O*t9sB<{s;QkxMTnT literal 0 HcmV?d00001 From 0d5f475d1eb0338698bc8aae720ceb47bf9dde85 Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Thu, 18 Nov 2021 15:32:11 +1000 Subject: [PATCH 07/21] add mdarray example --- examples/mdarray.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 examples/mdarray.rs diff --git a/examples/mdarray.rs b/examples/mdarray.rs new file mode 100644 index 00000000..9dd05256 --- /dev/null +++ b/examples/mdarray.rs @@ -0,0 +1,31 @@ +use gdal::{Dataset, DatasetOptions, GdalOpenFlags, cpl}; +use ndarray::ArrayD; + +fn main() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let root_group_name = root_group.name(); + let array_name = "Band1".to_string(); + let options = cpl::CslStringList::new(); + let md_array = root_group.md_array(array_name, options); + let dimensions = md_array.get_dimensions().unwrap(); + let mut dimensions_size = Vec::new(); + for dimension in dimensions{ + dimensions_size.push(dimension.size()); + } + let count = dimensions_size.clone(); + + let array_start_index = vec![0,0]; + let data: ArrayD = md_array.read_as_array( + array_start_index, + count, + dimensions_size).unwrap(); + println!("data: {:?}", data); + +} \ No newline at end of file From 80da096de84c0461bba08a7c58476d11ac4112dc Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Thu, 18 Nov 2021 15:38:43 +1000 Subject: [PATCH 08/21] rustfmt --- examples/mdarray.rs | 19 +++---- src/raster/mdarray.rs | 128 ++++++++++++++++++------------------------ 2 files changed, 62 insertions(+), 85 deletions(-) diff --git a/examples/mdarray.rs b/examples/mdarray.rs index 9dd05256..a6084640 100644 --- a/examples/mdarray.rs +++ b/examples/mdarray.rs @@ -1,4 +1,4 @@ -use gdal::{Dataset, DatasetOptions, GdalOpenFlags, cpl}; +use gdal::{cpl, Dataset, DatasetOptions, GdalOpenFlags}; use ndarray::ArrayD; fn main() { @@ -10,22 +10,19 @@ fn main() { }; let dataset = Dataset::open_ex("fixtures/byte_no_cf.nc", dataset_options).unwrap(); let root_group = dataset.root_group().unwrap(); - let root_group_name = root_group.name(); let array_name = "Band1".to_string(); - let options = cpl::CslStringList::new(); + let options = cpl::CslStringList::new(); let md_array = root_group.md_array(array_name, options); let dimensions = md_array.get_dimensions().unwrap(); let mut dimensions_size = Vec::new(); - for dimension in dimensions{ + for dimension in dimensions { dimensions_size.push(dimension.size()); } let count = dimensions_size.clone(); - let array_start_index = vec![0,0]; - let data: ArrayD = md_array.read_as_array( - array_start_index, - count, - dimensions_size).unwrap(); + let array_start_index = vec![0, 0]; + let data: ArrayD = md_array + .read_as_array(array_start_index, count, dimensions_size) + .unwrap(); println!("data: {:?}", data); - -} \ No newline at end of file +} diff --git a/src/raster/mdarray.rs b/src/raster/mdarray.rs index e273fe3c..3b5be1f8 100644 --- a/src/raster/mdarray.rs +++ b/src/raster/mdarray.rs @@ -1,22 +1,25 @@ +use super::GdalType; +use crate::errors::*; use crate::utils::{_last_cpl_err, _string, _string_array}; use crate::{cpl::CslStringList, Dataset}; -use gdal_sys::{GDALDimensionGetName,GDALDimensionGetSize, CPLErr, GDALDimensionH, GDALDimensionHS, GDALGroupGetMDArrayNames, GDALGroupGetName, GDALGroupH, GDALGroupOpenMDArray, GDALMDArrayGetDataType, GDALMDArrayGetDimensionCount, GDALMDArrayGetDimensions, GDALMDArrayGetTotalElementsCount, GDALMDArrayHS}; +use gdal_sys::{ + GDALDimensionGetName, GDALDimensionGetSize, GDALDimensionHS, GDALGroupGetMDArrayNames, + GDALGroupGetName, GDALGroupH, GDALGroupOpenMDArray, GDALMDArrayGetDataType, + GDALMDArrayGetDimensionCount, GDALMDArrayGetDimensions, GDALMDArrayGetTotalElementsCount, + GDALMDArrayHS, +}; use libc::c_void; use std::convert::TryInto; use std::ffi::CString; -use crate::errors::*; -use super::GdalType; #[cfg(feature = "ndarray")] -use ndarray::Array2; use ndarray::ArrayD; use ndarray::IxDyn; use std::fmt::Debug; - #[cfg(test)] mod tests { - use crate::{Dataset, DatasetOptions, GdalOpenFlags, cpl}; + use crate::{cpl, Dataset, DatasetOptions, GdalOpenFlags}; #[test] fn test_root_group_name() { @@ -41,7 +44,6 @@ mod tests { }; let dataset = Dataset::open_ex("fixtures/byte_no_cf.nc", dataset_options).unwrap(); let root_group = dataset.root_group().unwrap(); - let root_group_name = root_group.name(); let options = cpl::CslStringList::new(); //Driver specific options determining how groups should be retrieved. Pass nullptr for default behavior. let array_names = root_group.array_names(options); assert_eq!(array_names, vec!["Band1".to_string()]) @@ -57,7 +59,6 @@ mod tests { }; let dataset = Dataset::open_ex("fixtures/byte_no_cf.nc", dataset_options).unwrap(); let root_group = dataset.root_group().unwrap(); - let root_group_name = root_group.name(); let array_name = "Band1".to_string(); let options = cpl::CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. let md_array = root_group.md_array(array_name, options); @@ -75,7 +76,6 @@ mod tests { }; let dataset = Dataset::open_ex("fixtures/byte_no_cf.nc", dataset_options).unwrap(); let root_group = dataset.root_group().unwrap(); - let root_group_name = root_group.name(); let array_name = "Band1".to_string(); let options = cpl::CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. let md_array = root_group.md_array(array_name, options); @@ -93,13 +93,12 @@ mod tests { }; let dataset = Dataset::open_ex("fixtures/byte_no_cf.nc", dataset_options).unwrap(); let root_group = dataset.root_group().unwrap(); - let root_group_name = root_group.name(); let array_name = "Band1".to_string(); let options = cpl::CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. let md_array = root_group.md_array(array_name, options); let dimensions = md_array.get_dimensions().unwrap(); let mut dimension_names = Vec::new(); - for dimension in dimensions{ + for dimension in dimensions { dimension_names.push(dimension.name()); } assert_eq!(dimension_names, vec!["y".to_string(), "x".to_string()]) @@ -114,21 +113,18 @@ mod tests { }; let dataset = Dataset::open_ex("fixtures/byte_no_cf.nc", dataset_options).unwrap(); let root_group = dataset.root_group().unwrap(); - let root_group_name = root_group.name(); let array_name = "Band1".to_string(); let options = cpl::CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. let md_array = root_group.md_array(array_name, options); let dimensions = md_array.get_dimensions().unwrap(); let mut dimensions_size = Vec::new(); - for dimension in dimensions{ + for dimension in dimensions { dimensions_size.push(dimension.size()); } - assert_eq!(dimensions_size, vec![20,20]) + assert_eq!(dimensions_size, vec![20, 20]) } - } - /// Represent an MDArray in a Group /// /// This object carries the lifetime of the Group that @@ -136,14 +132,14 @@ mod tests { /// from being dropped before the mdarray. #[derive(Debug)] pub struct MDArray<'a> { - c_mdarray: *mut GDALMDArrayHS,//H + c_mdarray: *mut GDALMDArrayHS, //H group: &'a Group<'a>, } - -pub struct Dimension<'a>{ +#[allow(dead_code)] +pub struct Dimension<'a> { c_dimension: *mut GDALDimensionHS, - md_array: &'a MDArray<'a> + md_array: &'a MDArray<'a>, } impl<'a> Dimension<'a> { @@ -152,16 +148,17 @@ impl<'a> Dimension<'a> { /// # Safety /// This method operates on a raw C pointer pub fn from_c_dimension(md_array: &'a MDArray<'a>, c_dimension: *mut GDALDimensionHS) -> Self { - Dimension { c_dimension: (c_dimension), md_array: (md_array) } - } + Dimension { + c_dimension: (c_dimension), + md_array: (md_array), + } + } pub fn size(self) -> usize { - unsafe {GDALDimensionGetSize(self.c_dimension) as usize } - + unsafe { GDALDimensionGetSize(self.c_dimension) as usize } } pub fn name(self) -> String { - _string( unsafe {GDALDimensionGetName(self.c_dimension) } ) - + _string(unsafe { GDALDimensionGetName(self.c_dimension) }) } } @@ -172,77 +169,67 @@ impl<'a> MDArray<'a> { /// This method operates on a raw C pointer pub fn from_c_mdarray(group: &'a Group, c_mdarray: *mut GDALMDArrayHS) -> Self { MDArray { c_mdarray, group } - } - - pub fn n_dimension(&self ) -> usize { - unsafe { GDALMDArrayGetDimensionCount(self.c_mdarray) } + + pub fn n_dimension(&self) -> usize { + unsafe { GDALMDArrayGetDimensionCount(self.c_mdarray) } } pub fn n_elements(&self) -> u64 { unsafe { GDALMDArrayGetTotalElementsCount(self.c_mdarray) } } - pub fn get_dimensions(&self) -> Result> - { - // break on ndims not is_null + pub fn get_dimensions(&self) -> Result> { + // break on ndims not is_null let n_dimension = self.n_dimension(); unsafe { let mut pn_count: usize = 0; - let pn_count_ptr: *mut usize = &mut pn_count; + let pn_count_ptr: *mut usize = &mut pn_count; let c_dimensions = GDALMDArrayGetDimensions(self.c_mdarray, pn_count_ptr); // if c_group.is_null() { // return Err(_last_null_pointer_err("GDALGetRasterBand")); // } let mut dimensions: Vec = Vec::new(); - let mut i = 0 ; - while i< n_dimension { + let mut i = 0; + while i < n_dimension { let ptr = c_dimensions.add(i); let next = ptr.read(); let value = Dimension::from_c_dimension(self, next); i += 1; dimensions.push(value); } - println!("len {:?}", dimensions.len()); - - let size = GDALDimensionGetSize(dimensions[0].c_dimension); - - Ok(dimensions) } } - pub fn read_into_slice( - &self, buffer: &mut [T], + &self, + buffer: &mut [T], array_start_index: Vec, - count: Vec - - + count: Vec, ) -> Result<()> { - // let array_start_index = [array_start_index.0, array_start_index.1]; // let count = [count.0, count.1]; let array_step: *const i64 = std::ptr::null(); let buffer_stride: *const i64 = std::ptr::null(); - let data_type = unsafe{GDALMDArrayGetDataType(self.c_mdarray)}; - let p_dst_buffer_alloc_start: *mut libc::c_void = std::ptr::null_mut(); + let data_type = unsafe { GDALMDArrayGetDataType(self.c_mdarray) }; + let p_dst_buffer_alloc_start: *mut libc::c_void = std::ptr::null_mut(); let n_dst_buffer_alloc_size = 0 as usize; - + let rv = unsafe { gdal_sys::GDALMDArrayRead( self.c_mdarray, array_start_index.as_ptr(), count.as_ptr(), - array_step, - buffer_stride, - data_type, - buffer.as_mut_ptr() as *mut c_void, // pDstBuffer: *mut libc::c_void, - p_dst_buffer_alloc_start, // pDstBufferAllocStart: *const libc::c_void, + array_step, + buffer_stride, + data_type, + buffer.as_mut_ptr() as *mut c_void, // pDstBuffer: *mut libc::c_void, + p_dst_buffer_alloc_start, // pDstBufferAllocStart: *const libc::c_void, n_dst_buffer_alloc_size, ) - }; - + }; + if rv != 1 { return Err(_last_cpl_err(rv.try_into().unwrap())); // this is probably incorrect! } @@ -254,14 +241,12 @@ impl<'a> MDArray<'a> { /// /// # Arguments /// * array_start_index - Values representing the starting index to read in each dimension (in [0, aoDims[i].GetSize()-1] range). Array of GetDimensionCount() values. Must not be nullptr, unless for a zero-dimensional array. - /// * count - Values representing the number of values to extract in each dimension. Array of GetDimensionCount() values. Must not be nullptr, unless for a zero-dimensional array. + /// * count - Values representing the number of values to extract in each dimension. Array of GetDimensionCount() values. Must not be nullptr, unless for a zero-dimensional array. pub fn read_as( &self, array_start_index: Vec, count: Vec, - ) -> Result> { - let pixels: usize = count.iter().product(); let mut data: Vec = Vec::with_capacity(pixels); @@ -273,7 +258,7 @@ impl<'a> MDArray<'a> { unsafe { data.set_len(pixels); }; - self.read_into_slice( &mut data, array_start_index, count)?; + self.read_into_slice(&mut data, array_start_index, count)?; Ok(data) } @@ -296,14 +281,10 @@ impl<'a> MDArray<'a> { ) -> Result> { let data = self.read_as::(array_start_index, count)?; // Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis) - - let dim : IxDyn = IxDyn(&array_size); - Ok(ArrayD::from_shape_vec( - dim, - data, - )?) - } + let dim: IxDyn = IxDyn(&array_size); + Ok(ArrayD::from_shape_vec(dim, data)?) + } } /// Represent a mdarray in a dataset @@ -317,8 +298,6 @@ pub struct Group<'a> { dataset: &'a Dataset, } - - impl<'a> Group<'a> { /// Create a Group from a wrapped C pointer /// @@ -332,15 +311,16 @@ impl<'a> Group<'a> { } pub fn array_names(&self, options: CslStringList) -> Vec { - let options = options.as_ptr(); + let options = options.as_ptr(); let c_array_names = unsafe { GDALGroupGetMDArrayNames(self.c_group, options) }; _string_array(c_array_names) } - pub fn md_array(&self, name: String, options: CslStringList) -> MDArray{ + pub fn md_array(&self, name: String, options: CslStringList) -> MDArray { let name = CString::new(name).unwrap(); - let c_mdarray = unsafe {GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options.as_ptr() )}; - + let c_mdarray = + unsafe { GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options.as_ptr()) }; + MDArray::from_c_mdarray(&self, c_mdarray) } // pub unsafe fn array(&self, array_name: String, options: CslStringList) -> MDArray{ From 5f081a3b3c630941bd64eea68b3ef3b4fad53f5b Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Thu, 18 Nov 2021 16:01:21 +1000 Subject: [PATCH 09/21] cargo fmt -- --emit files --- src/dataset.rs | 4 ++-- src/raster/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dataset.rs b/src/dataset.rs index d5609f42..a89cabe0 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -14,8 +14,8 @@ use crate::raster::RasterCreationOption; use crate::utils::{_last_cpl_err, _last_null_pointer_err, _path_to_c_string, _string}; use crate::vector::{sql, Geometry, OwnedLayer}; use crate::{ - gdal_major_object::MajorObject, raster::RasterBand, spatial_ref::SpatialRef, vector::Layer, - Driver, Metadata, raster::mdarray::Group + gdal_major_object::MajorObject, raster::mdarray::Group, raster::RasterBand, + spatial_ref::SpatialRef, vector::Layer, Driver, Metadata, }; use gdal_sys::OGRGeometryH; use gdal_sys::{ diff --git a/src/raster/mod.rs b/src/raster/mod.rs index 3443c1c3..5c8b916e 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -1,10 +1,10 @@ //! GDAL Raster Data +pub(crate) mod mdarray; mod rasterband; mod rasterize; mod types; mod warp; -pub(crate) mod mdarray; pub use rasterband::{Buffer, ByteBuffer, ColorInterpretation, RasterBand, ResampleAlg}; pub use rasterize::{rasterize, BurnSource, MergeAlgorithm, OptimizeMode, RasterizeOptions}; From cd727687ca1a267a629799de62591ab20b0ed0d9 Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Fri, 19 Nov 2021 08:50:54 +1000 Subject: [PATCH 10/21] only build mdarray if gdal >3.1 --- src/raster/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/raster/mod.rs b/src/raster/mod.rs index 5c8b916e..2a3ef752 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -1,6 +1,8 @@ //! GDAL Raster Data +#[cfg(all(major_ge_3, minor_ge_1))] pub(crate) mod mdarray; + mod rasterband; mod rasterize; mod types; From 85dbbadf34b501f18b123f2d91ea772908c15909 Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Fri, 19 Nov 2021 14:43:19 +1000 Subject: [PATCH 11/21] only build mdarray if gdal >3.1 --- src/dataset.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dataset.rs b/src/dataset.rs index a89cabe0..2f05fcb2 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -14,15 +14,19 @@ use crate::raster::RasterCreationOption; use crate::utils::{_last_cpl_err, _last_null_pointer_err, _path_to_c_string, _string}; use crate::vector::{sql, Geometry, OwnedLayer}; use crate::{ - gdal_major_object::MajorObject, raster::mdarray::Group, raster::RasterBand, - spatial_ref::SpatialRef, vector::Layer, Driver, Metadata, + gdal_major_object::MajorObject, raster::RasterBand, spatial_ref::SpatialRef, vector::Layer, + Driver, Metadata, }; + use gdal_sys::OGRGeometryH; use gdal_sys::{ self, CPLErr, GDALAccess, GDALDatasetH, GDALMajorObjectH, OGRErr, OGRLayerH, OGRwkbGeometryType, }; use libc::{c_double, c_int, c_uint}; +#[cfg(all(major_ge_3, minor_ge_1))] +use crate::raster::mdarray::Group; + use bitflags::bitflags; /// A 2-D affine transform mapping pixel coordiates to world @@ -414,6 +418,8 @@ impl Dataset { Ok(RasterBand::from_c_rasterband(self, c_band)) } } + + #[cfg(all(major_ge_3, minor_ge_1))] pub fn root_group(&self) -> Result { unsafe { let c_group = gdal_sys::GDALDatasetGetRootGroup(self.c_dataset()); From 5dc4d82ad95da5f7be110b2ee2a9638dadce783f Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Fri, 19 Nov 2021 14:48:04 +1000 Subject: [PATCH 12/21] only build mdarray if gdal >3.1 --- examples/mdarray.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/mdarray.rs b/examples/mdarray.rs index a6084640..69692477 100644 --- a/examples/mdarray.rs +++ b/examples/mdarray.rs @@ -1,6 +1,8 @@ +#[cfg(all(major_ge_3, minor_ge_1))] use gdal::{cpl, Dataset, DatasetOptions, GdalOpenFlags}; use ndarray::ArrayD; +#[cfg(all(major_ge_3, minor_ge_1))] fn main() { let dataset_options = DatasetOptions { open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER, From ddc01fc15beca9d1e937b1bba96bab5c8d20f1a0 Mon Sep 17 00:00:00 2001 From: Leonardo Hardtke Date: Fri, 19 Nov 2021 15:59:11 +1000 Subject: [PATCH 13/21] Temporary remove example, not sure how to fix clippy warning --- examples/mdarray.rs | 30 ------------------------------ src/raster/mdarray.rs | 7 +++---- 2 files changed, 3 insertions(+), 34 deletions(-) delete mode 100644 examples/mdarray.rs diff --git a/examples/mdarray.rs b/examples/mdarray.rs deleted file mode 100644 index 69692477..00000000 --- a/examples/mdarray.rs +++ /dev/null @@ -1,30 +0,0 @@ -#[cfg(all(major_ge_3, minor_ge_1))] -use gdal::{cpl, Dataset, DatasetOptions, GdalOpenFlags}; -use ndarray::ArrayD; - -#[cfg(all(major_ge_3, minor_ge_1))] -fn main() { - 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/byte_no_cf.nc", dataset_options).unwrap(); - let root_group = dataset.root_group().unwrap(); - let array_name = "Band1".to_string(); - let options = cpl::CslStringList::new(); - let md_array = root_group.md_array(array_name, options); - let dimensions = md_array.get_dimensions().unwrap(); - let mut dimensions_size = Vec::new(); - for dimension in dimensions { - dimensions_size.push(dimension.size()); - } - let count = dimensions_size.clone(); - - let array_start_index = vec![0, 0]; - let data: ArrayD = md_array - .read_as_array(array_start_index, count, dimensions_size) - .unwrap(); - println!("data: {:?}", data); -} diff --git a/src/raster/mdarray.rs b/src/raster/mdarray.rs index 3b5be1f8..5be42edc 100644 --- a/src/raster/mdarray.rs +++ b/src/raster/mdarray.rs @@ -13,8 +13,7 @@ use std::convert::TryInto; use std::ffi::CString; #[cfg(feature = "ndarray")] -use ndarray::ArrayD; -use ndarray::IxDyn; +use ndarray::{ArrayD, IxDyn}; use std::fmt::Debug; #[cfg(test)] @@ -214,7 +213,7 @@ impl<'a> MDArray<'a> { let buffer_stride: *const i64 = std::ptr::null(); let data_type = unsafe { GDALMDArrayGetDataType(self.c_mdarray) }; let p_dst_buffer_alloc_start: *mut libc::c_void = std::ptr::null_mut(); - let n_dst_buffer_alloc_size = 0 as usize; + let n_dst_buffer_alloc_size = 0; let rv = unsafe { gdal_sys::GDALMDArrayRead( @@ -321,7 +320,7 @@ impl<'a> Group<'a> { let c_mdarray = unsafe { GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options.as_ptr()) }; - MDArray::from_c_mdarray(&self, c_mdarray) + MDArray::from_c_mdarray(self, c_mdarray) } // pub unsafe fn array(&self, array_name: String, options: CslStringList) -> MDArray{ // let name = CString::new(array_name).unwrap(); From 7731c159f86b302fe3109b73d28be7122fd58cad Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Fri, 13 May 2022 10:10:07 +0200 Subject: [PATCH 14/21] gdalmd module fixes --- src/dataset.rs | 8 +++++++- src/raster/mod.rs | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/dataset.rs b/src/dataset.rs index 2f05fcb2..fc1c129e 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -25,7 +25,7 @@ use gdal_sys::{ use libc::{c_double, c_int, c_uint}; #[cfg(all(major_ge_3, minor_ge_1))] -use crate::raster::mdarray::Group; +use crate::raster::Group; use bitflags::bitflags; @@ -419,6 +419,12 @@ impl Dataset { } } + /// Opens the root group of a multi-dim GDAL raster + /// + /// # Note + /// You must have opened the dataset with the `GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER` + /// flag in order for it to work. + /// #[cfg(all(major_ge_3, minor_ge_1))] pub fn root_group(&self) -> Result { unsafe { diff --git a/src/raster/mod.rs b/src/raster/mod.rs index 2a3ef752..ff7a7ef3 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -1,13 +1,14 @@ //! GDAL Raster Data #[cfg(all(major_ge_3, minor_ge_1))] -pub(crate) mod mdarray; - +mod mdarray; mod rasterband; mod rasterize; mod types; mod warp; +#[cfg(all(major_ge_3, minor_ge_1))] +pub use mdarray::{Group, MDArray}; pub use rasterband::{Buffer, ByteBuffer, ColorInterpretation, RasterBand, ResampleAlg}; pub use rasterize::{rasterize, BurnSource, MergeAlgorithm, OptimizeMode, RasterizeOptions}; pub use types::{GDALDataType, GdalType}; From 758b5e78dfe3a210842df742b5ded8a4aa3c6a46 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Fri, 13 May 2022 15:50:51 +0200 Subject: [PATCH 15/21] more functionality and less memory leaks for mdarray --- fixtures/alldatatypes.nc | Bin 0 -> 34946 bytes fixtures/cf_nasa_4326.nc | Bin 0 -> 18815 bytes src/raster/mdarray.rs | 943 +++++++++++++++++++++++++++++++-------- 3 files changed, 761 insertions(+), 182 deletions(-) create mode 100644 fixtures/alldatatypes.nc create mode 100644 fixtures/cf_nasa_4326.nc diff --git a/fixtures/alldatatypes.nc b/fixtures/alldatatypes.nc new file mode 100644 index 0000000000000000000000000000000000000000..ab6048f4537ea6aa128b86a43df666f23a1cb121 GIT binary patch literal 34946 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 { - c_mdarray: *mut GDALMDArrayHS, //H - group: &'a Group<'a>, + c_mdarray: GDALMDArrayH, + _parent: GroupOrDimension<'a>, } -#[allow(dead_code)] -pub struct Dimension<'a> { - c_dimension: *mut GDALDimensionHS, - md_array: &'a MDArray<'a>, +#[derive(Debug)] +enum GroupOrDimension<'a> { + Group { _group: &'a Group<'a> }, + Dimension { _dimension: &'a Dimension<'a> }, } -impl<'a> Dimension<'a> { +impl Drop for MDArray<'_> { + fn drop(&mut self) { + unsafe { + GDALMDArrayRelease(self.c_mdarray); + } + } +} + +impl<'a> MDArray<'a> { /// Create a MDArray from a wrapped C pointer /// /// # Safety /// This method operates on a raw C pointer - pub fn from_c_dimension(md_array: &'a MDArray<'a>, c_dimension: *mut GDALDimensionHS) -> Self { - Dimension { - c_dimension: (c_dimension), - md_array: (md_array), + pub unsafe fn from_c_mdarray_and_group(_group: &'a Group, c_mdarray: GDALMDArrayH) -> Self { + Self { + c_mdarray, + _parent: GroupOrDimension::Group { _group }, } } - pub fn size(self) -> usize { - unsafe { GDALDimensionGetSize(self.c_dimension) as usize } - } - - pub fn name(self) -> String { - _string(unsafe { GDALDimensionGetName(self.c_dimension) }) - } -} -impl<'a> MDArray<'a> { /// Create a MDArray from a wrapped C pointer /// /// # Safety /// This method operates on a raw C pointer - pub fn from_c_mdarray(group: &'a Group, c_mdarray: *mut GDALMDArrayHS) -> Self { - MDArray { c_mdarray, group } + pub unsafe fn from_c_mdarray_and_dimension( + _dimension: &'a Dimension, + c_mdarray: GDALMDArrayH, + ) -> Self { + Self { + c_mdarray, + _parent: GroupOrDimension::Dimension { _dimension }, + } } - pub fn n_dimension(&self) -> usize { + pub fn num_dimensions(&self) -> usize { unsafe { GDALMDArrayGetDimensionCount(self.c_mdarray) } } - pub fn n_elements(&self) -> u64 { + pub fn num_elements(&self) -> u64 { unsafe { GDALMDArrayGetTotalElementsCount(self.c_mdarray) } } - pub fn get_dimensions(&self) -> Result> { - // break on ndims not is_null - let n_dimension = self.n_dimension(); + pub fn dimensions(&self) -> Result> { unsafe { - let mut pn_count: usize = 0; - let pn_count_ptr: *mut usize = &mut pn_count; - let c_dimensions = GDALMDArrayGetDimensions(self.c_mdarray, pn_count_ptr); - // if c_group.is_null() { - // return Err(_last_null_pointer_err("GDALGetRasterBand")); - // } - let mut dimensions: Vec = Vec::new(); - let mut i = 0; - while i < n_dimension { - let ptr = c_dimensions.add(i); - let next = ptr.read(); - let value = Dimension::from_c_dimension(self, next); - i += 1; - dimensions.push(value); + let mut num_dimensions: usize = 0; + let c_dimensions = + GDALMDArrayGetDimensions(self.c_mdarray, std::ptr::addr_of_mut!(num_dimensions)); + + let dimensions_ref = std::slice::from_raw_parts_mut(c_dimensions, num_dimensions); + + let mut dimensions: Vec = Vec::with_capacity(num_dimensions); + + for c_dimension in dimensions_ref { + let dimension = Dimension::from_c_dimension(self, *c_dimension); + dimensions.push(dimension); } + + // only free the array, not the dimensions themselves + VSIFree(c_dimensions as *mut c_void); + Ok(dimensions) } } + pub fn datatype(&self) -> ExtendedDataType { + unsafe { + let c_data_type = GDALMDArrayGetDataType(self.c_mdarray); + + ExtendedDataType::from_c_extended_data_type(c_data_type) + } + } + + /// Wrapper for `GDALMDArrayRead` + /// + /// # Params + /// * buffer - Mutable buffer to read into + /// * array_start_index - Values representing the starting index to read in each dimension (in [0, aoDims[i].GetSize()-1] range). + /// Array of GetDimensionCount() values. Must not be empty, unless for a zero-dimensional array. + /// * count - Values representing the number of values to extract in each dimension. Array of GetDimensionCount() values. + /// Must not be empty, unless for a zero-dimensional array. + /// pub fn read_into_slice( &self, buffer: &mut [T], array_start_index: Vec, count: Vec, ) -> Result<()> { - // let array_start_index = [array_start_index.0, array_start_index.1]; - // let count = [count.0, count.1]; + // If set to nullptr, [1, 1, … 1] will be used as a default to indicate consecutive elements. let array_step: *const i64 = std::ptr::null(); + // If set to nullptr, will be set so that pDstBuffer is written in a compact way, + // with elements of the last / fastest varying dimension being consecutive. let buffer_stride: *const i64 = std::ptr::null(); - let data_type = unsafe { GDALMDArrayGetDataType(self.c_mdarray) }; let p_dst_buffer_alloc_start: *mut libc::c_void = std::ptr::null_mut(); let n_dst_buffer_alloc_size = 0; let rv = unsafe { - gdal_sys::GDALMDArrayRead( + let data_type = GDALMDArrayGetDataType(self.c_mdarray); + + let rv = gdal_sys::GDALMDArrayRead( self.c_mdarray, array_start_index.as_ptr(), count.as_ptr(), array_step, buffer_stride, data_type, - buffer.as_mut_ptr() as *mut c_void, // pDstBuffer: *mut libc::c_void, - p_dst_buffer_alloc_start, // pDstBufferAllocStart: *const libc::c_void, + buffer.as_mut_ptr() as *mut c_void, + p_dst_buffer_alloc_start, n_dst_buffer_alloc_size, - ) + ); + + GDALExtendedDataTypeRelease(data_type); + + rv }; + // `rv` is boolean if rv != 1 { - return Err(_last_cpl_err(rv.try_into().unwrap())); // this is probably incorrect! + // OSGeo Python wrapper treats it as `CE_Failure` + return Err(_last_cpl_err(CPLErr::CE_Failure)); } Ok(()) @@ -239,8 +169,11 @@ impl<'a> MDArray<'a> { /// Read a 'Buffer' from this band. T implements 'GdalType' /// /// # Arguments - /// * array_start_index - Values representing the starting index to read in each dimension (in [0, aoDims[i].GetSize()-1] range). Array of GetDimensionCount() values. Must not be nullptr, unless for a zero-dimensional array. - /// * count - Values representing the number of values to extract in each dimension. Array of GetDimensionCount() values. Must not be nullptr, unless for a zero-dimensional array. + /// * array_start_index - Values representing the starting index to read in each dimension (in [0, aoDims[i].GetSize()-1] range). + /// Array of GetDimensionCount() values. Must not be empty, unless for a zero-dimensional array. + /// * count - Values representing the number of values to extract in each dimension. Array of GetDimensionCount() values. + /// Must not be empty, unless for a zero-dimensional array. + /// pub fn read_as( &self, array_start_index: Vec, @@ -284,6 +217,114 @@ impl<'a> MDArray<'a> { let dim: IxDyn = IxDyn(&array_size); Ok(ArrayD::from_shape_vec(dim, data)?) } + + /// Read `MDArray` as one-dimensional string array + pub fn read_as_string_array(&self) -> Result> { + let num_values = self.num_elements() as usize; + let mut string_pointers: Vec<*const c_char> = vec![std::ptr::null(); num_values]; + + let count: Vec = self + .dimensions()? + .into_iter() + .map(|dim| dim.size()) + .collect(); + let array_start_index: Vec = vec![0; count.len()]; + + // If set to nullptr, [1, 1, … 1] will be used as a default to indicate consecutive elements. + let array_step: *const i64 = std::ptr::null(); + // If set to nullptr, will be set so that pDstBuffer is written in a compact way, + // with elements of the last / fastest varying dimension being consecutive. + let buffer_stride: *const i64 = std::ptr::null(); + + let p_dst_buffer_alloc_start: *mut libc::c_void = std::ptr::null_mut(); + let n_dst_buffer_alloc_size = 0; + + unsafe { + let data_type = GDALMDArrayGetDataType(self.c_mdarray); + + let rv = gdal_sys::GDALMDArrayRead( + self.c_mdarray, + array_start_index.as_ptr(), + count.as_ptr(), + array_step, + buffer_stride, + data_type, + string_pointers.as_mut_ptr().cast::(), + p_dst_buffer_alloc_start, + n_dst_buffer_alloc_size, + ); + + GDALExtendedDataTypeRelease(data_type); + + // `rv` is boolean + if rv != 1 { + // OSGeo Python wrapper treats it as `CE_Failure` + return Err(_last_cpl_err(CPLErr::CE_Failure)); + } + + let strings = string_pointers + .into_iter() + .map(|string_ptr| { + let string = _string(string_ptr); + + VSIFree(string_ptr as *mut c_void); + + string + }) + .collect(); + + Ok(strings) + } + } + + pub fn spatial_reference(&self) -> Result { + unsafe { + let c_gdal_spatial_ref = GDALMDArrayGetSpatialRef(self.c_mdarray); + + let gdal_spatial_ref = SpatialRef::from_c_obj(c_gdal_spatial_ref); + + OSRDestroySpatialReference(c_gdal_spatial_ref); + + gdal_spatial_ref + } + } + + pub fn no_data_value_as_double(&self) -> Option { + let mut has_nodata = 0; + + let no_data_value = unsafe { + GDALMDArrayGetNoDataValueAsDouble(self.c_mdarray, std::ptr::addr_of_mut!(has_nodata)) + }; + + if has_nodata == 0 { + None + } else { + Some(no_data_value) + } + } + + pub fn unit(&self) -> String { + unsafe { + // should not be freed + let c_unit = GDALMDArrayGetUnit(self.c_mdarray); + + _string(c_unit) + } + } + + pub fn attribute(&self, name: &str) -> Result { + let name = CString::new(name)?; + + unsafe { + let c_attribute = GDALMDArrayGetAttribute(self.c_mdarray, name.as_ptr()); + + if c_attribute.is_null() { + return Err(_last_null_pointer_err("GDALGroupGetAttribute")); + } + + Ok(Attribute::from_c_attribute(c_attribute)) + } + } } /// Represent a mdarray in a dataset @@ -294,7 +335,15 @@ impl<'a> MDArray<'a> { #[derive(Debug)] pub struct Group<'a> { c_group: GDALGroupH, - dataset: &'a Dataset, + _dataset: &'a Dataset, +} + +impl Drop for Group<'_> { + fn drop(&mut self) { + unsafe { + GDALGroupRelease(self.c_group); + } + } } impl<'a> Group<'a> { @@ -302,30 +351,560 @@ impl<'a> Group<'a> { /// /// # Safety /// This method operates on a raw C pointer - pub unsafe fn from_c_group(dataset: &'a Dataset, c_group: GDALGroupH) -> Self { - Group { c_group, dataset } + pub unsafe fn from_c_group(_dataset: &'a Dataset, c_group: GDALGroupH) -> Self { + Group { c_group, _dataset } } + pub fn name(&self) -> String { _string(unsafe { GDALGroupGetName(self.c_group) }) } + pub fn group_names(&self, options: CslStringList) -> Vec { + unsafe { + let c_group_names = GDALGroupGetGroupNames(self.c_group, options.as_ptr()); + + let strings = _string_array(c_group_names); + + CSLDestroy(c_group_names); + + strings + } + } + pub fn array_names(&self, options: CslStringList) -> Vec { - let options = options.as_ptr(); - let c_array_names = unsafe { GDALGroupGetMDArrayNames(self.c_group, options) }; - _string_array(c_array_names) + unsafe { + let c_array_names = GDALGroupGetMDArrayNames(self.c_group, options.as_ptr()); + + let strings = _string_array(c_array_names); + + CSLDestroy(c_array_names); + + strings + } + } + + pub fn open_md_array(&self, name: &str, options: CslStringList) -> Result { + let name = CString::new(name)?; + + unsafe { + let c_mdarray = GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options.as_ptr()); + + if c_mdarray.is_null() { + return Err(_last_null_pointer_err("GDALGroupOpenMDArray")); + } + + Ok(MDArray::from_c_mdarray_and_group(self, c_mdarray)) + } + } + + pub fn open_group(&self, name: &str, options: CslStringList) -> Result { + let name = CString::new(name)?; + + unsafe { + let c_group = GDALGroupOpenGroup(self.c_group, name.as_ptr(), options.as_ptr()); + + if c_group.is_null() { + return Err(_last_null_pointer_err("GDALGroupOpenGroup")); + } + + Ok(Group::from_c_group(self._dataset, c_group)) + } + } + + pub fn attribute(&self, name: &str) -> Result { + let name = CString::new(name)?; + + unsafe { + let c_attribute = GDALGroupGetAttribute(self.c_group, name.as_ptr()); + + if c_attribute.is_null() { + return Err(_last_null_pointer_err("GDALGroupGetAttribute")); + } + + Ok(Attribute::from_c_attribute(c_attribute)) + } + } +} + +/// A `GDALDimension` with name and size +#[derive(Debug)] +pub struct Dimension<'a> { + c_dimension: *mut GDALDimensionHS, + _md_array: &'a MDArray<'a>, +} + +impl Drop for Dimension<'_> { + fn drop(&mut self) { + unsafe { + GDALDimensionRelease(self.c_dimension); + } + } +} + +impl<'a> Dimension<'a> { + /// Create a MDArray from a wrapped C pointer + /// + /// # Safety + /// This method operates on a raw C pointer + pub fn from_c_dimension(_md_array: &'a MDArray<'a>, c_dimension: *mut GDALDimensionHS) -> Self { + Self { + c_dimension, + _md_array, + } + } + pub fn size(&self) -> usize { + unsafe { GDALDimensionGetSize(self.c_dimension) as usize } + } + + pub fn name(&self) -> String { + _string(unsafe { GDALDimensionGetName(self.c_dimension) }) } - pub fn md_array(&self, name: String, options: CslStringList) -> MDArray { - let name = CString::new(name).unwrap(); - let c_mdarray = - unsafe { GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options.as_ptr()) }; + pub fn indexing_variable(&self) -> MDArray { + unsafe { + let c_md_array = GDALDimensionGetIndexingVariable(self.c_dimension); + + MDArray::from_c_mdarray_and_dimension(self, c_md_array) + } + } +} + +/// Wrapper for `GDALExtendedDataType` +#[derive(Debug)] +pub struct ExtendedDataType { + c_data_type: GDALExtendedDataTypeH, +} + +impl Drop for ExtendedDataType { + fn drop(&mut self) { + unsafe { + GDALExtendedDataTypeRelease(self.c_data_type); + } + } +} + +impl ExtendedDataType { + /// Create an `ExtendedDataTypeNumeric` from a wrapped C pointer + /// + /// # Safety + /// This method operates on a raw C pointer + pub fn from_c_extended_data_type(c_data_type: GDALExtendedDataTypeH) -> Self { + Self { c_data_type } + } + + /// The result is only valid if the data type is numeric + pub fn class(&self) -> GDALExtendedDataTypeClass::Type { + unsafe { GDALExtendedDataTypeGetClass(self.c_data_type) } + } + + /// The result is only valid if the data type is numeric + pub fn numeric_datatype(&self) -> GDALDataType::Type { + unsafe { GDALExtendedDataTypeGetNumericDataType(self.c_data_type) } + } +} + +// Wrapper for `GDALExtendedDataType` +#[derive(Debug)] +pub struct Attribute { + c_attribute: GDALAttributeH, +} + +impl Drop for Attribute { + fn drop(&mut self) { + unsafe { + GDALAttributeRelease(self.c_attribute); + } + } +} + +impl Attribute { + /// Create an `ExtendedDataTypeNumeric` from a wrapped C pointer + /// + /// # Safety + /// This method operates on a raw C pointer + pub fn from_c_attribute(c_attribute: GDALAttributeH) -> Self { + Self { c_attribute } + } + + /// Return the size of the dimensions of the attribute. + /// This will be an empty array for a scalar (single value) attribute. + pub fn dimension_sizes(&self) -> Vec { + unsafe { + let mut num_dimensions = 0; + + let c_dimension_sizes = GDALAttributeGetDimensionsSize( + self.c_attribute, + std::ptr::addr_of_mut!(num_dimensions), + ); + + let dimension_sizes = std::slice::from_raw_parts(c_dimension_sizes, num_dimensions) + .iter() + .map(|&size| size as usize) + .collect(); + + VSIFree(c_dimension_sizes as *mut c_void); + + dimension_sizes + } + } + + pub fn datatype(&self) -> ExtendedDataType { + unsafe { + let c_data_type = GDALAttributeGetDataType(self.c_attribute); + ExtendedDataType::from_c_extended_data_type(c_data_type) + } + } + + pub fn read_as_string(&self) -> String { + unsafe { + // SAFETY: should no be freed + let c_string = GDALAttributeReadAsString(self.c_attribute); + + _string(c_string) + } + } + + pub fn read_as_string_array(&self) -> Vec { + unsafe { + let c_string_array = GDALAttributeReadAsStringArray(self.c_attribute); + + let string_array = _string_array(c_string_array); + + CSLDestroy(c_string_array); + + string_array + } + } + + pub fn read_as_i64(&self) -> i32 { + unsafe { GDALAttributeReadAsInt(self.c_attribute) } + } + + pub fn read_as_i64_array(&self) -> Vec { + unsafe { + let mut array_len = 0; + let c_int_array = + GDALAttributeReadAsIntArray(self.c_attribute, std::ptr::addr_of_mut!(array_len)); + + let int_array = std::slice::from_raw_parts(c_int_array, array_len) + .iter() + .copied() + .collect(); + + VSIFree(c_int_array as *mut c_void); + + int_array + } + } + + pub fn read_as_f64(&self) -> f64 { + unsafe { GDALAttributeReadAsDouble(self.c_attribute) } + } + + pub fn read_as_f64_array(&self) -> Vec { + unsafe { + let mut array_len = 0; + let c_int_array = + GDALAttributeReadAsDoubleArray(self.c_attribute, std::ptr::addr_of_mut!(array_len)); + + let float_array = std::slice::from_raw_parts(c_int_array, array_len) + .iter() + .copied() + .collect(); + + VSIFree(c_int_array as *mut c_void); + + float_array + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::{Dataset, DatasetOptions, GdalOpenFlags}; + + #[test] + fn test_root_group_name() { + let options = DatasetOptions { + open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER, + allowed_drivers: None, + open_options: None, + sibling_files: None, + }; + let dataset = Dataset::open_ex("fixtures/byte_no_cf.nc", options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let root_group_name = root_group.name(); + assert_eq!(root_group_name, "/"); + } + + #[test] + fn test_array_names() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let options = CslStringList::new(); //Driver specific options determining how groups should be retrieved. Pass nullptr for default behavior. + let array_names = root_group.array_names(options); + assert_eq!(array_names, vec!["Band1".to_string()]) + } + + #[test] + fn test_n_dimension() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let array_name = "Band1".to_string(); + let options = CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. + let md_array = root_group.open_md_array(&array_name, options).unwrap(); + let n_dimension = md_array.num_dimensions(); + assert_eq!(2, n_dimension); + } + + #[test] + fn test_n_elements() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let array_name = "Band1".to_string(); + let options = CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. + let md_array = root_group.open_md_array(&array_name, options).unwrap(); + let n_elements = md_array.num_elements(); + assert_eq!(400, n_elements); + } + + #[test] + fn test_dimension_name() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let array_name = "Band1".to_string(); + let options = CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. + let md_array = root_group.open_md_array(&array_name, options).unwrap(); + let dimensions = md_array.dimensions().unwrap(); + let mut dimension_names = Vec::new(); + for dimension in dimensions { + dimension_names.push(dimension.name()); + } + assert_eq!(dimension_names, vec!["y".to_string(), "x".to_string()]) + } + #[test] + fn test_dimension_size() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + let root_group = dataset.root_group().unwrap(); + let array_name = "Band1".to_string(); + let options = CslStringList::new(); //Driver specific options determining how the array should be opened. Pass nullptr for default behavior. + let md_array = root_group.open_md_array(&array_name, options).unwrap(); + let dimensions = md_array.dimensions().unwrap(); + let mut dimensions_size = Vec::new(); + for dimension in dimensions { + dimensions_size.push(dimension.size()); + } + assert_eq!(dimensions_size, vec![20, 20]) + } + + #[test] + fn test_read_data() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + + let root_group = dataset.root_group().unwrap(); + let md_array = root_group + .open_md_array("Band1", CslStringList::new()) + .unwrap(); + + let values = md_array.read_as::(vec![0, 0], vec![20, 20]).unwrap(); + + assert_eq!(&values[..4], &[181, 181, 156, 148]); + } + + #[test] + fn test_read_string_array() { + 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/alldatatypes.nc", dataset_options).unwrap(); + + let root_group = dataset.root_group().unwrap(); + + let string_array = root_group + .open_md_array("string_var", CslStringList::new()) + .unwrap(); + + assert_eq!(string_array.read_as_string_array().unwrap(), ["abcd", "ef"]); + } + + #[test] + fn test_datatype() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + + let root_group = dataset.root_group().unwrap(); + let md_array = root_group + .open_md_array("Band1", CslStringList::new()) + .unwrap(); + + let datatype = md_array.datatype(); + + assert_eq!(datatype.class(), GDALExtendedDataTypeClass::GEDTC_NUMERIC); + assert_eq!(datatype.numeric_datatype(), GDALDataType::GDT_Byte); + } + + #[test] + fn test_spatial_ref() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + + let root_group = dataset.root_group().unwrap(); + let md_array = root_group + .open_md_array("Band1", CslStringList::new()) + .unwrap(); + + let spatial_ref = md_array.spatial_reference().unwrap(); + + assert_eq!(spatial_ref.name().unwrap(), "NAD27 / UTM zone 11N"); + + assert_eq!(spatial_ref.authority().unwrap(), "EPSG:26711"); + } + + #[test] + fn test_no_data_value() { + 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/byte_no_cf.nc", dataset_options).unwrap(); + + let root_group = dataset.root_group().unwrap(); + let md_array = root_group + .open_md_array("Band1", CslStringList::new()) + .unwrap(); + + assert_eq!(md_array.no_data_value_as_double(), Some(0.)); + } + + #[test] + fn test_attributes() { + 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 root_group = dataset.root_group().unwrap(); + + assert_eq!( + root_group.attribute("title").unwrap().read_as_string(), + "Simple CF file" + ); + + let group_science = root_group + .open_group("science", CslStringList::new()) + .unwrap(); + let group_grids = group_science + .open_group("grids", CslStringList::new()) + .unwrap(); + let group_data = group_grids + .open_group("data", CslStringList::new()) + .unwrap(); + + let md_array = group_data + .open_md_array("temp", CslStringList::new()) + .unwrap(); + + assert_eq!( + md_array + .attribute("standard_name") + .unwrap() + .read_as_string(), + "air_temperature" + ); + + assert_eq!( + md_array.attribute("_FillValue").unwrap().read_as_f64(), + -9999. + ); + } + + #[test] + fn test_unit() { + 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 root_group = dataset.root_group().unwrap(); + + assert_eq!( + root_group.attribute("title").unwrap().read_as_string(), + "Simple CF file" + ); + + let group_science = root_group + .open_group("science", CslStringList::new()) + .unwrap(); + let group_grids = group_science + .open_group("grids", CslStringList::new()) + .unwrap(); + let group_data = group_grids + .open_group("data", CslStringList::new()) + .unwrap(); + + let md_array = group_data + .open_md_array("temp", CslStringList::new()) + .unwrap(); - MDArray::from_c_mdarray(self, c_mdarray) + assert_eq!(md_array.unit(), "K"); } - // pub unsafe fn array(&self, array_name: String, options: CslStringList) -> MDArray{ - // let name = CString::new(array_name).unwrap(); - // let options = options.as_ptr(); - // let array_h = GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options); - // MDArray::from_c_mdarray(&self, array_h) - // } } From 6a48b8db56c19e2e405a753a852d68c5b37d7889 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Fri, 13 May 2022 16:17:09 +0200 Subject: [PATCH 16/21] clippy lints --- src/raster/mdarray.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/raster/mdarray.rs b/src/raster/mdarray.rs index c814f2f2..d9a809e1 100644 --- a/src/raster/mdarray.rs +++ b/src/raster/mdarray.rs @@ -188,9 +188,9 @@ impl<'a> MDArray<'a> { // in the rust std docs // (https://doc.rust-lang.org/std/vec/struct.Vec.html#examples-18) unsafe { + self.read_into_slice(&mut data, array_start_index, count)?; data.set_len(pixels); }; - self.read_into_slice(&mut data, array_start_index, count)?; Ok(data) } @@ -586,10 +586,7 @@ impl Attribute { let c_int_array = GDALAttributeReadAsIntArray(self.c_attribute, std::ptr::addr_of_mut!(array_len)); - let int_array = std::slice::from_raw_parts(c_int_array, array_len) - .iter() - .copied() - .collect(); + let int_array = std::slice::from_raw_parts(c_int_array, array_len).to_vec(); VSIFree(c_int_array as *mut c_void); @@ -607,10 +604,7 @@ impl Attribute { let c_int_array = GDALAttributeReadAsDoubleArray(self.c_attribute, std::ptr::addr_of_mut!(array_len)); - let float_array = std::slice::from_raw_parts(c_int_array, array_len) - .iter() - .copied() - .collect(); + let float_array = std::slice::from_raw_parts(c_int_array, array_len).to_vec(); VSIFree(c_int_array as *mut c_void); From 45c765f46ff02a7cd172eca3b362834413bcac88 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Fri, 13 May 2022 16:18:18 +0200 Subject: [PATCH 17/21] add mdarray to CHANGES.md --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d4bdd844..9d80ed42 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,10 @@ - +- Add support for MDArray API + + - + ## 0.12 - Bump Rust edition to 2021 From f7b0aa0cbae70fa634c9aeee616088bac3fa8830 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Fri, 13 May 2022 16:27:51 +0200 Subject: [PATCH 18/21] correct `method_name` in `root_group` --- src/dataset.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dataset.rs b/src/dataset.rs index fc1c129e..57d85fdf 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -430,7 +430,7 @@ impl Dataset { unsafe { let c_group = gdal_sys::GDALDatasetGetRootGroup(self.c_dataset()); if c_group.is_null() { - return Err(_last_null_pointer_err("GDALGetRasterBand")); + return Err(_last_null_pointer_err("GDALDatasetGetRootGroup")); } Ok(Group::from_c_group(self, c_group)) } From 947e7e99c30db09aa49f18d988a6d16c148881f2 Mon Sep 17 00:00:00 2001 From: Thomas ten Cate Date: Thu, 27 Jan 2022 16:18:05 +0100 Subject: [PATCH 19/21] Add functions to enumerate Drivers --- CHANGES.md | 6 ++++- examples/metadata.rs | 2 +- examples/read_write_ogr.rs | 2 +- examples/read_write_ogr_datetime.rs | 2 +- examples/write_ogr.rs | 4 ++-- src/dataset.rs | 6 ++--- src/driver.rs | 21 ++++++++++++++++- src/raster/tests.rs | 36 ++++++++++++++--------------- src/vector/vector_tests/mod.rs | 2 +- 9 files changed, 52 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1e4b23e9..f87b5663 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,10 @@ - https://github.com/georust/gdal/pull/267 +- **Breaking**: Rename `Driver::get` to `Driver::get_by_name`, add `Driver::get(usize)` and `Driver::count` + + - + - Add `programs::raster::build_vrt` - Add `GeoTransformEx` extension trait with `apply` and `invert` @@ -92,7 +96,7 @@ - ```rust -let driver = Driver::get("GTiff").unwrap(); +let driver = Driver::get_by_name("GTiff").unwrap(); let options = &[ RasterCreationOption { key: "COMPRESS", diff --git a/examples/metadata.rs b/examples/metadata.rs index 73759be5..80b79962 100644 --- a/examples/metadata.rs +++ b/examples/metadata.rs @@ -2,7 +2,7 @@ fn main() { use gdal::{Dataset, Metadata}; use std::path::Path; - let driver = gdal::Driver::get("mem").unwrap(); + let driver = gdal::Driver::get_by_name("mem").unwrap(); println!("driver description: {:?}", driver.description()); let path = Path::new("./fixtures/tinymarble.png"); diff --git a/examples/read_write_ogr.rs b/examples/read_write_ogr.rs index 7dc86b38..39bfa510 100644 --- a/examples/read_write_ogr.rs +++ b/examples/read_write_ogr.rs @@ -17,7 +17,7 @@ fn run() -> Result<()> { // Create a new dataset: let path = std::env::temp_dir().join("abcde.shp"); let _ = fs::remove_file(&path); - let drv = Driver::get("ESRI Shapefile")?; + let drv = Driver::get_by_name("ESRI Shapefile")?; let mut ds = drv.create_vector_only(path.to_str().unwrap())?; let lyr = ds.create_layer(Default::default())?; diff --git a/examples/read_write_ogr_datetime.rs b/examples/read_write_ogr_datetime.rs index 62656594..6470d1c7 100644 --- a/examples/read_write_ogr_datetime.rs +++ b/examples/read_write_ogr_datetime.rs @@ -15,7 +15,7 @@ fn run() -> gdal::errors::Result<()> { // Create a new dataset: let path = std::env::temp_dir().join("later.geojson"); let _ = std::fs::remove_file(&path); - let drv = Driver::get("GeoJSON")?; + let drv = Driver::get_by_name("GeoJSON")?; let mut ds = drv.create_vector_only(path.to_str().unwrap())?; let lyr = ds.create_layer(Default::default())?; diff --git a/examples/write_ogr.rs b/examples/write_ogr.rs index 60c15f73..b71cdc83 100644 --- a/examples/write_ogr.rs +++ b/examples/write_ogr.rs @@ -7,7 +7,7 @@ use std::fs; fn example_1() -> Result<()> { let path = std::env::temp_dir().join("output1.geojson"); let _ = fs::remove_file(&path); - let drv = Driver::get("GeoJSON")?; + let drv = Driver::get_by_name("GeoJSON")?; let mut ds = drv.create_vector_only(path.to_str().unwrap())?; let lyr = ds.create_layer(Default::default())?; @@ -52,7 +52,7 @@ fn example_1() -> Result<()> { fn example_2() -> Result<()> { let path = std::env::temp_dir().join("output2.geojson"); let _ = fs::remove_file(&path); - let driver = Driver::get("GeoJSON")?; + let driver = Driver::get_by_name("GeoJSON")?; let mut ds = driver.create_vector_only(path.to_str().unwrap())?; let mut layer = ds.create_layer(Default::default())?; diff --git a/src/dataset.rs b/src/dataset.rs index 57d85fdf..dbfe7e34 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -552,7 +552,7 @@ impl Dataset { /// /// ``` /// # use gdal::Driver; - /// # let driver = Driver::get("GPKG").unwrap(); + /// # let driver = Driver::get_by_name("GPKG").unwrap(); /// # let mut dataset = driver.create_vector_only("/vsimem/example.gpkg").unwrap(); /// let blank_layer = dataset.create_layer(Default::default()).unwrap(); /// ``` @@ -562,7 +562,7 @@ impl Dataset { /// ``` /// # use gdal::{Driver, LayerOptions}; /// # use gdal::spatial_ref::SpatialRef; - /// # let driver = Driver::get("GPKG").unwrap(); + /// # let driver = Driver::get_by_name("GPKG").unwrap(); /// # let mut dataset = driver.create_vector_only("/vsimem/example.gpkg").unwrap(); /// let roads = dataset.create_layer(LayerOptions { /// name: "roads", @@ -720,7 +720,7 @@ impl Dataset { /// } /// # /// # fn main() -> gdal::errors::Result<()> { - /// # let driver = gdal::Driver::get("SQLite")?; + /// # let driver = gdal::Driver::get_by_name("SQLite")?; /// # let mut dataset = driver.create_vector_only(":memory:")?; /// # create_point_grid(&mut dataset)?; /// # assert_eq!(dataset.layer(0)?.features().count(), 10000); diff --git a/src/driver.rs b/src/driver.rs index d37de998..f8949a1f 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -30,7 +30,8 @@ pub struct Driver { } impl Driver { - pub fn get(name: &str) -> Result { + /// Returns the driver with the given short name. + pub fn get_by_name(name: &str) -> Result { _register_drivers(); let c_name = CString::new(name)?; let c_driver = unsafe { gdal_sys::GDALGetDriverByName(c_name.as_ptr()) }; @@ -40,6 +41,24 @@ impl Driver { Ok(Driver { c_driver }) } + /// Returns the driver with the given index, which must be less than the value returned by + /// `Driver::count()`. + pub fn get(index: usize) -> Result { + _register_drivers(); + let c_driver = unsafe { gdal_sys::GDALGetDriver(index.try_into().unwrap()) }; + if c_driver.is_null() { + return Err(_last_null_pointer_err("GDALGetDriver")); + } + Ok(Driver { c_driver }) + } + + /// Returns the number of registered drivers. + pub fn count() -> usize { + _register_drivers(); + let count = unsafe { gdal_sys::GDALGetDriverCount() }; + count.try_into().unwrap() + } + /// Creates a new Driver object by wrapping a C pointer /// /// # Safety diff --git a/src/raster/tests.rs b/src/raster/tests.rs index 65da3653..b40fadb9 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -121,7 +121,7 @@ fn test_read_raster_with_average_resample() { #[test] fn test_write_raster() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = driver.create("", 20, 10, 1).unwrap(); // create a 2x1 raster @@ -153,7 +153,7 @@ fn test_rename_remove_raster() { let mem_file_path_a = Path::new("/vsimem/030bd1d1-8955-4604-8e37-177dade13863"); let mem_file_path_b = Path::new("/vsimem/c7bfce32-2474-48fa-a907-2af95f83c824"); - let driver = Driver::get("GTiff").unwrap(); + let driver = Driver::get_by_name("GTiff").unwrap(); dataset.create_copy(&driver, &mem_file_path_a, &[]).unwrap(); @@ -179,7 +179,7 @@ fn test_get_dataset_driver() { #[test] fn test_get_description() { - let driver = Driver::get("mem").unwrap(); + let driver = Driver::get_by_name("mem").unwrap(); assert_eq!(driver.description().unwrap(), "MEM".to_string()); } @@ -230,7 +230,7 @@ fn test_get_metadata_item() { #[test] fn test_set_metadata_item() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let mut dataset = driver.create("", 1, 1, 1).unwrap(); let key = "Test_Key"; @@ -245,7 +245,7 @@ fn test_set_metadata_item() { #[test] fn test_set_description() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = driver.create("", 1, 1, 1).unwrap(); let mut band = dataset.rasterband(1).unwrap(); @@ -258,7 +258,7 @@ fn test_set_description() { #[test] fn test_create() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = driver.create("", 10, 20, 3).unwrap(); assert_eq!(dataset.raster_size(), (10, 20)); assert_eq!(dataset.raster_count(), 3); @@ -267,7 +267,7 @@ fn test_create() { #[test] fn test_create_with_band_type() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = driver .create_with_band_type::("", 10, 20, 3) .unwrap(); @@ -280,7 +280,7 @@ fn test_create_with_band_type() { #[test] fn test_create_with_band_type_with_options() { - let driver = Driver::get("GTiff").unwrap(); + let driver = Driver::get_by_name("GTiff").unwrap(); let options = [ RasterCreationOption { key: "TILED", @@ -327,7 +327,7 @@ fn test_create_with_band_type_with_options() { #[test] fn test_create_copy() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = Dataset::open(fixture!("tinymarble.png")).unwrap(); let copy = dataset.create_copy(&driver, "", &[]).unwrap(); assert_eq!(copy.raster_size(), (100, 50)); @@ -347,7 +347,7 @@ fn test_create_copy_with_options() { let copy = dataset .create_copy( - &Driver::get("GTiff").unwrap(), + &Driver::get_by_name("GTiff").unwrap(), mem_file_path, &[ RasterCreationOption { @@ -376,7 +376,7 @@ fn test_create_copy_with_options() { #[test] #[allow(clippy::float_cmp)] fn test_geo_transform() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let mut dataset = driver.create("", 20, 10, 1).unwrap(); let transform = [0., 1., 0., 0., 0., 1.]; assert!(dataset.set_geo_transform(&transform).is_ok()); @@ -385,10 +385,10 @@ fn test_geo_transform() { #[test] fn test_get_driver_by_name() { - let missing_driver = Driver::get("wtf"); + let missing_driver = Driver::get_by_name("wtf"); assert!(missing_driver.is_err()); - let ok_driver = Driver::get("GTiff"); + let ok_driver = Driver::get_by_name("GTiff"); assert!(ok_driver.is_ok()); let driver = ok_driver.unwrap(); assert_eq!(driver.short_name(), "GTiff"); @@ -486,7 +486,7 @@ fn test_read_block_data() { #[test] fn test_get_band_type() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = driver.create("", 20, 10, 1).unwrap(); let rb = dataset.rasterband(1).unwrap(); assert_eq!(rb.band_type(), GDALDataType::GDT_Byte); @@ -494,7 +494,7 @@ fn test_get_band_type() { #[test] fn test_get_rasterband() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = driver.create("", 20, 10, 1).unwrap(); let rasterband = dataset.rasterband(1); assert!(rasterband.is_ok()); @@ -518,7 +518,7 @@ fn test_get_no_data_value() { #[test] #[allow(clippy::float_cmp)] fn test_set_no_data_value() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = driver.create("", 20, 10, 1).unwrap(); let mut rasterband = dataset.rasterband(1).unwrap(); assert_eq!(rasterband.no_data_value(), None); @@ -648,7 +648,7 @@ fn test_get_rasterband_color_interp() { #[test] fn test_set_rasterband_color_interp() { - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let dataset = driver.create("", 1, 1, 1).unwrap(); let mut rasterband = dataset.rasterband(1).unwrap(); rasterband @@ -678,7 +678,7 @@ fn test_rasterize() { let rows = 5; let cols = 5; - let driver = Driver::get("MEM").unwrap(); + let driver = Driver::get_by_name("MEM").unwrap(); let mut dataset = driver.create("", rows, cols, 1).unwrap(); let bands = [1]; diff --git a/src/vector/vector_tests/mod.rs b/src/vector/vector_tests/mod.rs index dce01dda..fb3d933a 100644 --- a/src/vector/vector_tests/mod.rs +++ b/src/vector/vector_tests/mod.rs @@ -672,7 +672,7 @@ mod tests { use std::fs; { - let driver = Driver::get("GeoJSON").unwrap(); + let driver = Driver::get_by_name("GeoJSON").unwrap(); let mut ds = driver .create_vector_only(&fixture!("output.geojson")) .unwrap(); From 79464ab02961e21ad8779825b8a24796f8e5e03b Mon Sep 17 00:00:00 2001 From: "Brendan C. Ward" Date: Sat, 21 May 2022 07:16:58 -0700 Subject: [PATCH 20/21] use match for error, try without casts to c_double --- src/spatial_ref/srs.rs | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/spatial_ref/srs.rs b/src/spatial_ref/srs.rs index 7eca66db..b7836d4f 100644 --- a/src/spatial_ref/srs.rs +++ b/src/spatial_ref/srs.rs @@ -1,6 +1,6 @@ use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string}; use gdal_sys::{self, CPLErr, OGRCoordinateTransformationH, OGRErr, OGRSpatialReferenceH}; -use libc::{c_char, c_double, c_int}; +use libc::{c_char, c_int}; use std::ffi::{CStr, CString}; use std::ptr::{self, null_mut}; use std::str::FromStr; @@ -55,37 +55,34 @@ impl CoordTransform { let ret_val = unsafe { gdal_sys::OCTTransformBounds( self.inner, - bounds[0] as c_double, - bounds[1] as c_double, - bounds[2] as c_double, - bounds[3] as c_double, - &mut out_xmin as *mut c_double, - &mut out_ymin as *mut c_double, - &mut out_xmax as *mut c_double, - &mut out_ymax as *mut c_double, + bounds[0], + bounds[1], + bounds[2], + bounds[3], + &mut out_xmin, + &mut out_ymin, + &mut out_xmax, + &mut out_ymax, densify_pts as c_int, ) == 1 }; - if ret_val { - Ok([out_xmin, out_ymin, out_xmax, out_ymax]) - } else { - let err = _last_cpl_err(CPLErr::CE_Failure); - let msg = if let GdalError::CplError { msg, .. } = err { - if msg.trim().is_empty() { - None - } else { - Some(msg) - } - } else { - return Err(err); + if !ret_val { + let msg = match _last_cpl_err(CPLErr::CE_Failure) { + GdalError::CplError { msg, .. } => match msg.is_empty() { + false => Some(msg), + _ => None + }, + err => return Err(err) }; - Err(GdalError::InvalidCoordinateRange { + return Err(GdalError::InvalidCoordinateRange { from: self.from.clone(), to: self.to.clone(), msg, }) } + + Ok([out_xmin, out_ymin, out_xmax, out_ymax]) } /// Transform coordinates in place. From 3ed047442608e29da355e5c4bbfd1c223de35edc Mon Sep 17 00:00:00 2001 From: "Brendan C. Ward" Date: Mon, 23 May 2022 09:17:22 -0700 Subject: [PATCH 21/21] Fix formatting issues --- src/spatial_ref/srs.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/spatial_ref/srs.rs b/src/spatial_ref/srs.rs index b7836d4f..c21f92c9 100644 --- a/src/spatial_ref/srs.rs +++ b/src/spatial_ref/srs.rs @@ -71,15 +71,15 @@ impl CoordTransform { let msg = match _last_cpl_err(CPLErr::CE_Failure) { GdalError::CplError { msg, .. } => match msg.is_empty() { false => Some(msg), - _ => None + _ => None, }, - err => return Err(err) + err => return Err(err), }; return Err(GdalError::InvalidCoordinateRange { from: self.from.clone(), to: self.to.clone(), msg, - }) + }); } Ok([out_xmin, out_ymin, out_xmax, out_ymax])