gdal/raster/
mdarray.rs

1use std::{
2    ffi::{c_char, c_void, CString},
3    fmt::{Debug, Display},
4};
5
6use gdal_sys::{
7    CPLErr, CSLDestroy, GDALAttributeGetDataType, GDALAttributeGetDimensionsSize, GDALAttributeH,
8    GDALAttributeReadAsDouble, GDALAttributeReadAsDoubleArray, GDALAttributeReadAsInt,
9    GDALAttributeReadAsIntArray, GDALAttributeReadAsString, GDALAttributeReadAsStringArray,
10    GDALAttributeRelease, GDALDataType, GDALDatasetH, GDALDimensionGetIndexingVariable,
11    GDALDimensionGetName, GDALDimensionGetSize, GDALDimensionHS, GDALDimensionRelease,
12    GDALExtendedDataTypeClass, GDALExtendedDataTypeCreate, GDALExtendedDataTypeGetClass,
13    GDALExtendedDataTypeGetName, GDALExtendedDataTypeGetNumericDataType, GDALExtendedDataTypeH,
14    GDALExtendedDataTypeRelease, GDALGroupGetAttribute, GDALGroupGetDimensions,
15    GDALGroupGetGroupNames, GDALGroupGetMDArrayNames, GDALGroupGetName, GDALGroupH,
16    GDALGroupOpenGroup, GDALGroupOpenMDArray, GDALGroupRelease, GDALMDArrayGetAttribute,
17    GDALMDArrayGetDataType, GDALMDArrayGetDimensionCount, GDALMDArrayGetDimensions,
18    GDALMDArrayGetNoDataValueAsDouble, GDALMDArrayGetSpatialRef, GDALMDArrayGetTotalElementsCount,
19    GDALMDArrayGetUnit, GDALMDArrayH, GDALMDArrayRelease, OSRDestroySpatialReference, VSIFree,
20};
21
22#[cfg(feature = "ndarray")]
23use ndarray::{ArrayD, IxDyn};
24
25use super::GdalType;
26use crate::errors::*;
27use crate::spatial_ref::SpatialRef;
28use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string, _string_array};
29use crate::{cpl::CslStringList, Dataset};
30
31/// Represent an MDArray in a Group
32///
33/// This object carries the lifetime of the Group that
34/// contains it. This is necessary to prevent the Group
35/// from being dropped before the mdarray.
36#[derive(Debug)]
37pub struct MDArray<'a> {
38    c_mdarray: GDALMDArrayH,
39    c_dataset: GDALDatasetH,
40    _parent: GroupOrDimension<'a>,
41}
42
43#[derive(Debug)]
44pub enum GroupOrDimension<'a> {
45    Group { _group: &'a Group<'a> },
46    Dimension { _dimension: &'a Dimension<'a> },
47}
48
49#[derive(Debug)]
50pub enum GroupOrArray<'a> {
51    Group { _group: &'a Group<'a> },
52    MDArray { _md_array: &'a MDArray<'a> },
53}
54
55impl Drop for MDArray<'_> {
56    fn drop(&mut self) {
57        unsafe {
58            GDALMDArrayRelease(self.c_mdarray);
59        }
60    }
61}
62
63impl<'a> MDArray<'a> {
64    /// Create a MDArray from a wrapped C pointer
65    ///
66    /// # Safety
67    /// This method operates on a raw C pointer
68    pub unsafe fn from_c_mdarray_and_group(_group: &'a Group, c_mdarray: GDALMDArrayH) -> Self {
69        Self {
70            c_mdarray,
71            c_dataset: _group._dataset.c_dataset(),
72            _parent: GroupOrDimension::Group { _group },
73        }
74    }
75
76    /// Create a MDArray from a wrapped C pointer
77    ///
78    /// # Safety
79    /// This method operates on a raw C pointer
80    pub unsafe fn from_c_mdarray_and_dimension(
81        _dimension: &'a Dimension,
82        c_mdarray: GDALMDArrayH,
83    ) -> Self {
84        Self {
85            c_mdarray,
86            c_dataset: match _dimension._parent {
87                GroupOrArray::Group { _group } => _group._dataset.c_dataset(),
88                GroupOrArray::MDArray { _md_array } => _md_array.c_dataset,
89            },
90            _parent: GroupOrDimension::Dimension { _dimension },
91        }
92    }
93
94    pub fn num_dimensions(&self) -> usize {
95        unsafe { GDALMDArrayGetDimensionCount(self.c_mdarray) }
96    }
97
98    pub fn num_elements(&self) -> u64 {
99        unsafe { GDALMDArrayGetTotalElementsCount(self.c_mdarray) }
100    }
101
102    pub fn dimensions(&self) -> Result<Vec<Dimension>> {
103        let mut num_dimensions: usize = 0;
104
105        let c_dimensions = unsafe { GDALMDArrayGetDimensions(self.c_mdarray, &mut num_dimensions) };
106
107        // If `num_dimensions` is `0`, we can safely return an empty vector.
108        // `GDALMDArrayGetDimensions` does not state that errors can occur.
109        if num_dimensions == 0 {
110            return Ok(Vec::new());
111        }
112        if c_dimensions.is_null() {
113            return Err(_last_null_pointer_err("GDALMDArrayGetDimensions"));
114        }
115
116        let dimensions_ref =
117            unsafe { std::slice::from_raw_parts_mut(c_dimensions, num_dimensions) };
118
119        let mut dimensions: Vec<Dimension> = Vec::with_capacity(num_dimensions);
120
121        for c_dimension in dimensions_ref {
122            let dimension = unsafe {
123                Dimension::from_c_dimension(GroupOrArray::MDArray { _md_array: self }, *c_dimension)
124            };
125            dimensions.push(dimension);
126        }
127
128        // only free the array, not the dimensions themselves
129        unsafe {
130            VSIFree(c_dimensions as *mut c_void);
131        }
132
133        Ok(dimensions)
134    }
135
136    pub fn datatype(&self) -> ExtendedDataType {
137        unsafe {
138            let c_data_type = GDALMDArrayGetDataType(self.c_mdarray);
139
140            ExtendedDataType::from_c_extended_data_type(c_data_type)
141        }
142    }
143
144    unsafe fn read<T: Copy + GdalType>(
145        &self,
146        buffer: *mut T,
147        array_start_index: Vec<u64>,
148        count: Vec<usize>,
149    ) -> Result<()> {
150        // If set to nullptr, [1, 1, … 1] will be used as a default to indicate consecutive elements.
151        let array_step: *const i64 = std::ptr::null();
152        // If set to nullptr, will be set so that pDstBuffer is written in a compact way,
153        // with elements of the last / fastest varying dimension being consecutive.
154        let buffer_stride: *const i64 = std::ptr::null();
155        let p_dst_buffer_alloc_start: *mut c_void = std::ptr::null_mut();
156        let n_dst_buffer_alloc_size = 0;
157
158        let rv = unsafe {
159            if !self.datatype().class().is_numeric() {
160                return Err(GdalError::UnsupportedMdDataType {
161                    data_type: self.datatype().class(),
162                    method_name: "GDALMDArrayRead",
163                });
164            }
165
166            let data_type = GDALExtendedDataTypeCreate(T::gdal_ordinal());
167
168            let rv = gdal_sys::GDALMDArrayRead(
169                self.c_mdarray,
170                array_start_index.as_ptr(),
171                count.as_ptr(),
172                array_step,
173                buffer_stride,
174                data_type,
175                buffer as *mut c_void,
176                p_dst_buffer_alloc_start,
177                n_dst_buffer_alloc_size,
178            );
179
180            GDALExtendedDataTypeRelease(data_type);
181
182            rv
183        };
184
185        // `rv` is boolean
186        if rv != 1 {
187            // OSGeo Python wrapper treats it as `CE_Failure`
188            return Err(_last_cpl_err(CPLErr::CE_Failure));
189        }
190
191        Ok(())
192    }
193
194    /// Wrapper for `GDALMDArrayRead`
195    ///
196    /// # Params
197    /// * buffer - Mutable buffer to read into
198    /// * array_start_index - Values representing the starting index to read in each dimension (in `[0, aoDims[i].GetSize()-1]` range).
199    ///   Array of `GetDimensionCount()` values. Must not be empty, unless for a zero-dimensional array.
200    /// * count - Values representing the number of values to extract in each dimension. Array of `GetDimensionCount()` values.
201    ///   Must not be empty, unless for a zero-dimensional array.
202    ///
203    pub fn read_into_slice<T: Copy + GdalType>(
204        &self,
205        buffer: &mut [T],
206        array_start_index: Vec<u64>,
207        count: Vec<usize>,
208    ) -> Result<()> {
209        let pixels: usize = count.iter().product();
210        if buffer.len() < pixels {
211            return Err(GdalError::BadArgument(format!(
212                "buffer length is {}, must be at least {}",
213                buffer.len(),
214                pixels
215            )));
216        }
217
218        // SAFETY: we checked the buffer length above.
219        unsafe { self.read(buffer.as_mut_ptr(), array_start_index, count) }
220    }
221
222    /// Read a [`Vec<T>`] from this band, where `T` implements [`GdalType`].
223    ///
224    /// # Arguments
225    /// * `array_start_index` - Values representing the starting index to read in each dimension (in `[0, aoDims[i].GetSize()-1]` range).
226    ///   Array of `GetDimensionCount()` values. Must not be empty, unless for a zero-dimensional array.
227    /// * `count` - Values representing the number of values to extract in each dimension. Array of `GetDimensionCount()` values.
228    ///   Must not be empty, unless for a zero-dimensional array.
229    ///
230    pub fn read_as<T: Copy + GdalType>(
231        &self,
232        array_start_index: Vec<u64>,
233        count: Vec<usize>,
234    ) -> Result<Vec<T>> {
235        let pixels: usize = count.iter().product();
236        let mut data: Vec<T> = Vec::with_capacity(pixels);
237
238        // SAFETY: the `read` line below writes
239        // exactly `pixels` elements into the slice, before we
240        // read from this slice. This paradigm is suggested
241        // in the standard library docs.
242        // (https://doc.rust-lang.org/std/vec/struct.Vec.html#method.set_len)
243        unsafe {
244            self.read(data.as_mut_ptr(), array_start_index, count)?;
245            data.set_len(pixels);
246        };
247
248        Ok(data)
249    }
250
251    #[cfg(feature = "ndarray")]
252    /// Read an [`ArrayD<T>`] from this band. T implements [`GdalType`].
253    ///
254    /// # Arguments
255    /// * `window` - the window position from top left
256    /// * `window_size` - the window size (GDAL will interpolate data if window_size != array_size)
257    /// * `array_size` - the desired size of the 'Array'
258    /// * `e_resample_alg` - the resample algorithm used for the interpolation
259    ///
260    /// # Notes
261    /// The Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis).
262    pub fn read_as_array<T: Copy + GdalType + Debug>(
263        &self,
264        array_start_index: Vec<u64>,
265        count: Vec<usize>,
266        array_size: Vec<usize>,
267    ) -> Result<ArrayD<T>> {
268        let data = self.read_as::<T>(array_start_index, count)?;
269        // Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis)
270
271        let dim: IxDyn = IxDyn(&array_size);
272        Ok(ArrayD::from_shape_vec(dim, data)?)
273    }
274
275    /// Read `MDArray` as one-dimensional string array
276    pub fn read_as_string_array(&self) -> Result<Vec<String>> {
277        let data_type = self.datatype();
278        if !data_type.class().is_string() {
279            // We have to check that the data type is string.
280            // Only then, GDAL returns an array of string pointers.
281            // Otherwise, we will dereference these string pointers and get a segfault.
282
283            return Err(GdalError::UnsupportedMdDataType {
284                data_type: data_type.class(),
285                method_name: "GDALMDArrayRead (string)",
286            });
287        }
288
289        let num_values = self.num_elements() as usize;
290        let mut string_pointers: Vec<*const c_char> = vec![std::ptr::null(); num_values];
291
292        let count: Vec<usize> = self
293            .dimensions()?
294            .into_iter()
295            .map(|dim| dim.size())
296            .collect();
297        let array_start_index: Vec<u64> = vec![0; count.len()];
298
299        // If set to nullptr, [1, 1, … 1] will be used as a default to indicate consecutive elements.
300        let array_step: *const i64 = std::ptr::null();
301        // If set to nullptr, will be set so that pDstBuffer is written in a compact way,
302        // with elements of the last / fastest varying dimension being consecutive.
303        let buffer_stride: *const i64 = std::ptr::null();
304
305        let p_dst_buffer_alloc_start: *mut c_void = std::ptr::null_mut();
306        let n_dst_buffer_alloc_size = 0;
307
308        unsafe {
309            let data_type = GDALMDArrayGetDataType(self.c_mdarray);
310
311            let rv = gdal_sys::GDALMDArrayRead(
312                self.c_mdarray,
313                array_start_index.as_ptr(),
314                count.as_ptr(),
315                array_step,
316                buffer_stride,
317                data_type,
318                string_pointers.as_mut_ptr().cast::<c_void>(),
319                p_dst_buffer_alloc_start,
320                n_dst_buffer_alloc_size,
321            );
322
323            GDALExtendedDataTypeRelease(data_type);
324
325            // `rv` is boolean
326            if rv != 1 {
327                // OSGeo Python wrapper treats it as `CE_Failure`
328                return Err(_last_cpl_err(CPLErr::CE_Failure));
329            }
330
331            let strings = string_pointers
332                .into_iter()
333                .filter_map(|ptr| {
334                    let s = _string(ptr);
335                    // GDAL allows `VSIFree(nullptr)`
336                    VSIFree(ptr as *mut c_void);
337                    s
338                })
339                .collect();
340
341            Ok(strings)
342        }
343    }
344
345    pub fn spatial_reference(&self) -> Result<SpatialRef> {
346        unsafe {
347            let c_gdal_spatial_ref = GDALMDArrayGetSpatialRef(self.c_mdarray);
348
349            let gdal_spatial_ref = SpatialRef::from_c_obj(c_gdal_spatial_ref);
350
351            OSRDestroySpatialReference(c_gdal_spatial_ref);
352
353            gdal_spatial_ref
354        }
355    }
356
357    pub fn no_data_value_as_double(&self) -> Option<f64> {
358        let mut has_nodata = 0;
359
360        let no_data_value =
361            unsafe { GDALMDArrayGetNoDataValueAsDouble(self.c_mdarray, &mut has_nodata) };
362
363        if has_nodata == 0 {
364            None
365        } else {
366            Some(no_data_value)
367        }
368    }
369
370    pub fn unit(&self) -> String {
371        unsafe {
372            // should not be freed
373            let c_ptr = GDALMDArrayGetUnit(self.c_mdarray);
374            _string(c_ptr).unwrap_or_default()
375        }
376    }
377
378    pub fn attribute(&self, name: &str) -> Result<Attribute> {
379        let name = CString::new(name)?;
380
381        unsafe {
382            let c_attribute = GDALMDArrayGetAttribute(self.c_mdarray, name.as_ptr());
383
384            if c_attribute.is_null() {
385                return Err(_last_null_pointer_err("GDALGroupGetAttribute"));
386            }
387
388            Ok(Attribute::from_c_attribute(c_attribute))
389        }
390    }
391
392    /// Fetch statistics.
393    ///
394    /// Returns the minimum, maximum, mean and standard deviation of all pixel values in this array.
395    ///
396    /// If `force` is `false` results will only be returned if it can be done quickly (i.e. without scanning the data).
397    /// If `force` is `false` and results cannot be returned efficiently, the method will return `None`.
398    ///
399    /// When cached statistics are not available, and `force` is `true`, ComputeStatistics() is called.
400    ///
401    /// Note that file formats using PAM (Persistent Auxiliary Metadata) services will generally cache statistics in the .aux.xml file allowing fast fetch after the first request.
402    ///
403    /// This methods is a wrapper for [`GDALMDArrayGetStatistics`](https://gdal.org/api/gdalmdarray_cpp.html#_CPPv4N11GDALMDArray13GetStatisticsEbbPdPdPdPdP7GUInt6416GDALProgressFuncPv).
404    ///
405    /// TODO: add option to pass progress callback (`pfnProgress`)
406    ///
407    pub fn get_statistics(
408        &self,
409        force: bool,
410        is_approx_ok: bool,
411    ) -> Result<Option<MdStatisticsAll>> {
412        use std::ffi::c_int;
413
414        let mut statistics = MdStatisticsAll {
415            min: 0.,
416            max: 0.,
417            mean: 0.,
418            std_dev: 0.,
419            valid_count: 0,
420        };
421
422        let rv = unsafe {
423            gdal_sys::GDALMDArrayGetStatistics(
424                self.c_mdarray,
425                self.c_dataset,
426                c_int::from(is_approx_ok),
427                c_int::from(force),
428                &mut statistics.min,
429                &mut statistics.max,
430                &mut statistics.mean,
431                &mut statistics.std_dev,
432                &mut statistics.valid_count,
433                None,
434                std::ptr::null_mut(),
435            )
436        };
437
438        match CplErrType::from(rv) {
439            CplErrType::None => Ok(Some(statistics)),
440            CplErrType::Warning => Ok(None),
441            _ => Err(_last_cpl_err(rv)),
442        }
443    }
444}
445
446#[derive(Debug, PartialEq)]
447pub struct MdStatisticsAll {
448    pub min: f64,
449    pub max: f64,
450    pub mean: f64,
451    pub std_dev: f64,
452    pub valid_count: u64,
453}
454
455/// Represent a mdarray in a dataset
456///
457/// This object carries the lifetime of the dataset that
458/// contains it. This is necessary to prevent the dataset
459/// from being dropped before the group.
460#[derive(Debug)]
461pub struct Group<'a> {
462    c_group: GDALGroupH,
463    _dataset: &'a Dataset,
464}
465
466impl Drop for Group<'_> {
467    fn drop(&mut self) {
468        unsafe {
469            GDALGroupRelease(self.c_group);
470        }
471    }
472}
473
474impl<'a> Group<'a> {
475    /// Create a Group from a wrapped C pointer
476    ///
477    /// # Safety
478    /// This method operates on a raw C pointer
479    pub unsafe fn from_c_group(_dataset: &'a Dataset, c_group: GDALGroupH) -> Self {
480        Group { c_group, _dataset }
481    }
482
483    pub fn name(&self) -> String {
484        let c_ptr = unsafe { GDALGroupGetName(self.c_group) };
485        _string(c_ptr).unwrap_or_default()
486    }
487
488    pub fn group_names(&self, options: CslStringList) -> Vec<String> {
489        unsafe {
490            let c_group_names = GDALGroupGetGroupNames(self.c_group, options.as_ptr());
491
492            let strings = _string_array(c_group_names);
493
494            CSLDestroy(c_group_names);
495
496            strings
497        }
498    }
499
500    pub fn array_names(&self, options: CslStringList) -> Vec<String> {
501        unsafe {
502            let c_array_names = GDALGroupGetMDArrayNames(self.c_group, options.as_ptr());
503
504            let strings = _string_array(c_array_names);
505
506            CSLDestroy(c_array_names);
507
508            strings
509        }
510    }
511
512    pub fn open_md_array(&self, name: &str, options: CslStringList) -> Result<MDArray> {
513        let name = CString::new(name)?;
514
515        unsafe {
516            let c_mdarray = GDALGroupOpenMDArray(self.c_group, name.as_ptr(), options.as_ptr());
517
518            if c_mdarray.is_null() {
519                return Err(_last_null_pointer_err("GDALGroupOpenMDArray"));
520            }
521
522            Ok(MDArray::from_c_mdarray_and_group(self, c_mdarray))
523        }
524    }
525
526    pub fn open_group(&'_ self, name: &str, options: CslStringList) -> Result<Group<'a>> {
527        let name = CString::new(name)?;
528
529        unsafe {
530            let c_group = GDALGroupOpenGroup(self.c_group, name.as_ptr(), options.as_ptr());
531
532            if c_group.is_null() {
533                return Err(_last_null_pointer_err("GDALGroupOpenGroup"));
534            }
535
536            Ok(Group::from_c_group(self._dataset, c_group))
537        }
538    }
539
540    pub fn attribute(&self, name: &str) -> Result<Attribute> {
541        let name = CString::new(name)?;
542
543        unsafe {
544            let c_attribute = GDALGroupGetAttribute(self.c_group, name.as_ptr());
545
546            if c_attribute.is_null() {
547                return Err(_last_null_pointer_err("GDALGroupGetAttribute"));
548            }
549
550            Ok(Attribute::from_c_attribute(c_attribute))
551        }
552    }
553
554    pub fn dimensions(&self, options: CslStringList) -> Result<Vec<Dimension>> {
555        unsafe {
556            let mut num_dimensions: usize = 0;
557            let c_dimensions =
558                GDALGroupGetDimensions(self.c_group, &mut num_dimensions, options.as_ptr());
559
560            // If `num_dimensions` is `0`, we can safely return an empty vector.
561            // `GDALGroupGetDimensions` does not state that errors can occur.
562            if num_dimensions == 0 {
563                return Ok(Vec::new());
564            }
565            if c_dimensions.is_null() {
566                return Err(_last_null_pointer_err("GDALGroupGetDimensions"));
567            }
568
569            let dimensions_ref = std::slice::from_raw_parts_mut(c_dimensions, num_dimensions);
570
571            let mut dimensions: Vec<Dimension> = Vec::with_capacity(num_dimensions);
572
573            for c_dimension in dimensions_ref {
574                let dimension =
575                    Dimension::from_c_dimension(GroupOrArray::Group { _group: self }, *c_dimension);
576                dimensions.push(dimension);
577            }
578
579            // only free the array, not the dimensions themselves
580            VSIFree(c_dimensions as *mut c_void);
581
582            Ok(dimensions)
583        }
584    }
585}
586
587/// A `GDALDimension` with name and size
588#[derive(Debug)]
589pub struct Dimension<'a> {
590    c_dimension: *mut GDALDimensionHS,
591    _parent: GroupOrArray<'a>,
592}
593
594impl Drop for Dimension<'_> {
595    fn drop(&mut self) {
596        unsafe {
597            GDALDimensionRelease(self.c_dimension);
598        }
599    }
600}
601
602impl<'a> Dimension<'a> {
603    /// Create a MDArray from a wrapped C pointer
604    ///
605    /// # Safety
606    /// This method operates on a raw C pointer
607    pub unsafe fn from_c_dimension(
608        _parent: GroupOrArray<'a>,
609        c_dimension: *mut GDALDimensionHS,
610    ) -> Self {
611        Self {
612            c_dimension,
613            _parent,
614        }
615    }
616    pub fn size(&self) -> usize {
617        unsafe { GDALDimensionGetSize(self.c_dimension) as usize }
618    }
619
620    pub fn name(&self) -> String {
621        let c_ptr = unsafe { GDALDimensionGetName(self.c_dimension) };
622        _string(c_ptr).unwrap_or_default()
623    }
624
625    pub fn indexing_variable(&self) -> MDArray {
626        unsafe {
627            let c_md_array = GDALDimensionGetIndexingVariable(self.c_dimension);
628
629            MDArray::from_c_mdarray_and_dimension(self, c_md_array)
630        }
631    }
632}
633
634/// Wrapper for `GDALExtendedDataType`
635#[derive(Debug)]
636pub struct ExtendedDataType {
637    c_data_type: GDALExtendedDataTypeH,
638}
639
640impl Drop for ExtendedDataType {
641    fn drop(&mut self) {
642        unsafe {
643            GDALExtendedDataTypeRelease(self.c_data_type);
644        }
645    }
646}
647
648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
649pub enum ExtendedDataTypeClass {
650    Compound = GDALExtendedDataTypeClass::GEDTC_COMPOUND as isize,
651    Numeric = GDALExtendedDataTypeClass::GEDTC_NUMERIC as isize,
652    String = GDALExtendedDataTypeClass::GEDTC_STRING as isize,
653}
654
655impl ExtendedDataTypeClass {
656    pub fn is_string(&self) -> bool {
657        matches!(self, ExtendedDataTypeClass::String)
658    }
659
660    pub fn is_numeric(&self) -> bool {
661        matches!(self, ExtendedDataTypeClass::Numeric)
662    }
663
664    pub fn is_compound(&self) -> bool {
665        matches!(self, ExtendedDataTypeClass::Compound)
666    }
667}
668
669impl From<GDALExtendedDataTypeClass::Type> for ExtendedDataTypeClass {
670    fn from(class: GDALExtendedDataTypeClass::Type) -> Self {
671        match class {
672            GDALExtendedDataTypeClass::GEDTC_COMPOUND => ExtendedDataTypeClass::Compound,
673            GDALExtendedDataTypeClass::GEDTC_NUMERIC => ExtendedDataTypeClass::Numeric,
674            GDALExtendedDataTypeClass::GEDTC_STRING => ExtendedDataTypeClass::String,
675            _ => panic!("Unknown ExtendedDataTypeClass {class}"),
676        }
677    }
678}
679
680impl Display for ExtendedDataTypeClass {
681    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
682        match self {
683            ExtendedDataTypeClass::Compound => write!(f, "Compound"),
684            ExtendedDataTypeClass::Numeric => write!(f, "Numeric"),
685            ExtendedDataTypeClass::String => write!(f, "String"),
686        }
687    }
688}
689
690impl ExtendedDataType {
691    /// Create an `ExtendedDataTypeNumeric` from a wrapped C pointer
692    ///
693    /// # Safety
694    /// This method operates on a raw C pointer
695    pub fn from_c_extended_data_type(c_data_type: GDALExtendedDataTypeH) -> Self {
696        Self { c_data_type }
697    }
698
699    /// The result is only valid if the data type is numeric
700    pub fn class(&self) -> ExtendedDataTypeClass {
701        unsafe { GDALExtendedDataTypeGetClass(self.c_data_type) }.into()
702    }
703
704    /// The result is only valid if the data type is numeric
705    pub fn numeric_datatype(&self) -> GDALDataType::Type {
706        unsafe { GDALExtendedDataTypeGetNumericDataType(self.c_data_type) }
707    }
708
709    pub fn name(&self) -> String {
710        let c_ptr = unsafe { GDALExtendedDataTypeGetName(self.c_data_type) };
711        _string(c_ptr).unwrap_or_default()
712    }
713}
714
715// Wrapper for `GDALAttribute`
716#[derive(Debug)]
717pub struct Attribute {
718    c_attribute: GDALAttributeH,
719}
720
721impl Drop for Attribute {
722    fn drop(&mut self) {
723        unsafe {
724            GDALAttributeRelease(self.c_attribute);
725        }
726    }
727}
728
729impl Attribute {
730    /// Create an `ExtendedDataTypeNumeric` from a wrapped C pointer
731    ///
732    /// # Safety
733    /// This method operates on a raw C pointer
734    pub fn from_c_attribute(c_attribute: GDALAttributeH) -> Self {
735        Self { c_attribute }
736    }
737
738    /// Return the size of the dimensions of the attribute.
739    /// This will be an empty array for a scalar (single value) attribute.
740    pub fn dimension_sizes(&self) -> Vec<usize> {
741        unsafe {
742            let mut num_dimensions = 0;
743
744            let c_dimension_sizes =
745                GDALAttributeGetDimensionsSize(self.c_attribute, &mut num_dimensions);
746
747            let dimension_sizes = std::slice::from_raw_parts(c_dimension_sizes, num_dimensions)
748                .iter()
749                .map(|&size| size as usize)
750                .collect();
751
752            VSIFree(c_dimension_sizes as *mut c_void);
753
754            dimension_sizes
755        }
756    }
757
758    pub fn datatype(&self) -> ExtendedDataType {
759        unsafe {
760            let c_data_type = GDALAttributeGetDataType(self.c_attribute);
761            ExtendedDataType::from_c_extended_data_type(c_data_type)
762        }
763    }
764
765    pub fn read_as_string(&self) -> String {
766        // should not be freed
767        let c_ptr = unsafe { GDALAttributeReadAsString(self.c_attribute) };
768        _string(c_ptr).unwrap_or_default()
769    }
770
771    pub fn read_as_string_array(&self) -> Vec<String> {
772        unsafe {
773            let c_string_array = GDALAttributeReadAsStringArray(self.c_attribute);
774
775            let string_array = _string_array(c_string_array);
776
777            CSLDestroy(c_string_array);
778
779            string_array
780        }
781    }
782
783    pub fn read_as_i64(&self) -> i32 {
784        unsafe { GDALAttributeReadAsInt(self.c_attribute) }
785    }
786
787    pub fn read_as_i64_array(&self) -> Vec<i32> {
788        unsafe {
789            let mut array_len = 0;
790            let c_int_array = GDALAttributeReadAsIntArray(self.c_attribute, &mut array_len);
791
792            let int_array = std::slice::from_raw_parts(c_int_array, array_len).to_vec();
793
794            VSIFree(c_int_array as *mut c_void);
795
796            int_array
797        }
798    }
799
800    pub fn read_as_f64(&self) -> f64 {
801        unsafe { GDALAttributeReadAsDouble(self.c_attribute) }
802    }
803
804    pub fn read_as_f64_array(&self) -> Vec<f64> {
805        unsafe {
806            let mut array_len = 0;
807            let c_int_array = GDALAttributeReadAsDoubleArray(self.c_attribute, &mut array_len);
808
809            let float_array = std::slice::from_raw_parts(c_int_array, array_len).to_vec();
810
811            VSIFree(c_int_array as *mut c_void);
812
813            float_array
814        }
815    }
816}
817
818/// [Dataset] methods supporting multi-dimensional array operations.
819impl Dataset {
820    /// Opens the root group of a multi-dim GDAL raster
821    ///
822    /// # Note
823    /// You must have opened the dataset with the `GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER`
824    /// flag in order for it to work.
825    ///
826    pub fn root_group(&self) -> Result<Group> {
827        unsafe {
828            let c_group = gdal_sys::GDALDatasetGetRootGroup(self.c_dataset());
829            if c_group.is_null() {
830                return Err(_last_null_pointer_err("GDALDatasetGetRootGroup"));
831            }
832            Ok(Group::from_c_group(self, c_group))
833        }
834    }
835}
836
837#[cfg(test)]
838mod tests {
839
840    use super::*;
841
842    use crate::options::DatasetOptions;
843    use crate::{test_utils::TempFixture, Dataset, GdalOpenFlags};
844
845    #[test]
846    #[cfg_attr(feature = "gdal-src", ignore)]
847    fn test_root_group_name() {
848        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
849
850        let options = DatasetOptions {
851            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
852            allowed_drivers: None,
853            open_options: None,
854            sibling_files: None,
855        };
856        let dataset = Dataset::open_ex(fixture, options).unwrap();
857        let root_group = dataset.root_group().unwrap();
858        let root_group_name = root_group.name();
859        assert_eq!(root_group_name, "/");
860    }
861
862    #[test]
863    #[cfg_attr(feature = "gdal-src", ignore)]
864    fn test_array_names() {
865        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
866
867        let dataset_options = DatasetOptions {
868            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
869            allowed_drivers: None,
870            open_options: None,
871            sibling_files: None,
872        };
873        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
874        let root_group = dataset.root_group().unwrap();
875        let options = CslStringList::new();
876        let array_names = root_group.array_names(options);
877        assert_eq!(
878            array_names,
879            vec!["X".to_string(), "Y".to_string(), "byte_no_cf".to_string()]
880        )
881    }
882
883    #[test]
884    #[cfg_attr(feature = "gdal-src", ignore)]
885    fn test_n_dimension() {
886        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
887
888        let dataset_options = DatasetOptions {
889            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
890            allowed_drivers: None,
891            open_options: None,
892            sibling_files: None,
893        };
894        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
895        let root_group = dataset.root_group().unwrap();
896        let array_name = "byte_no_cf".to_string();
897        let options = CslStringList::new();
898        let md_array = root_group.open_md_array(&array_name, options).unwrap();
899        let n_dimension = md_array.num_dimensions();
900        assert_eq!(2, n_dimension);
901    }
902
903    #[test]
904    #[cfg_attr(feature = "gdal-src", ignore)]
905    fn test_n_elements() {
906        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
907
908        let dataset_options = DatasetOptions {
909            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
910            allowed_drivers: None,
911            open_options: None,
912            sibling_files: None,
913        };
914        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
915        let root_group = dataset.root_group().unwrap();
916        let array_name = "byte_no_cf".to_string();
917        let options = CslStringList::new();
918        let md_array = root_group.open_md_array(&array_name, options).unwrap();
919        let n_elements = md_array.num_elements();
920        assert_eq!(400, n_elements);
921    }
922
923    #[test]
924    #[cfg_attr(feature = "gdal-src", ignore)]
925    fn test_dimension_name() {
926        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
927
928        let dataset_options = DatasetOptions {
929            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
930            allowed_drivers: None,
931            open_options: None,
932            sibling_files: None,
933        };
934        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
935        let root_group = dataset.root_group().unwrap();
936
937        // group dimensions
938        let group_dimensions = root_group.dimensions(CslStringList::new()).unwrap();
939        let group_dimensions_names: Vec<String> = group_dimensions
940            .into_iter()
941            .map(|dimensions| dimensions.name())
942            .collect();
943        assert_eq!(group_dimensions_names, vec!["X", "Y"]);
944
945        // array dimensions
946
947        let array_name = "byte_no_cf".to_string();
948        let options = CslStringList::new();
949        let md_array = root_group.open_md_array(&array_name, options).unwrap();
950        let dimensions = md_array.dimensions().unwrap();
951        let mut dimension_names = Vec::new();
952        for dimension in dimensions {
953            dimension_names.push(dimension.name());
954        }
955        assert_eq!(dimension_names, vec!["Y".to_string(), "X".to_string()])
956    }
957
958    #[test]
959    #[cfg_attr(feature = "gdal-src", ignore)]
960    fn test_dimension_size() {
961        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
962
963        let dataset_options = DatasetOptions {
964            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
965            allowed_drivers: None,
966            open_options: None,
967            sibling_files: None,
968        };
969        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
970        let root_group = dataset.root_group().unwrap();
971        let array_name = "byte_no_cf".to_string();
972        let options = CslStringList::new();
973        let md_array = root_group.open_md_array(&array_name, options).unwrap();
974        let dimensions = md_array.dimensions().unwrap();
975        let mut dimensions_size = Vec::new();
976        for dimension in dimensions {
977            dimensions_size.push(dimension.size());
978        }
979        assert_eq!(dimensions_size, vec![20, 20])
980    }
981
982    #[test]
983    #[cfg_attr(feature = "gdal-src", ignore)]
984    fn test_read_data() {
985        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
986
987        let dataset_options = DatasetOptions {
988            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
989            allowed_drivers: None,
990            open_options: None,
991            sibling_files: None,
992        };
993        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
994
995        let root_group = dataset.root_group().unwrap();
996        let md_array = root_group
997            .open_md_array("byte_no_cf", CslStringList::new())
998            .unwrap();
999
1000        let values = md_array.read_as::<u8>(vec![0, 0], vec![20, 20]).unwrap();
1001
1002        assert_eq!(&values[..4], &[181, 181, 156, 148]);
1003
1004        let values = md_array.read_as::<u16>(vec![0, 0], vec![20, 20]).unwrap();
1005        assert_eq!(&values[..4], &[181, 181, 156, 148]);
1006
1007        let mut buffer: Vec<u8> = vec![0; 20 * 20];
1008        md_array
1009            .read_into_slice(&mut buffer, vec![0, 0], vec![20, 20])
1010            .unwrap();
1011
1012        let mut buffer: Vec<u8> = vec![0; 20 * 20 - 1];
1013        md_array
1014            .read_into_slice(&mut buffer, vec![0, 0], vec![20, 20])
1015            .expect_err("read_into_slice() with insufficient capacity should panic");
1016    }
1017
1018    #[test]
1019    #[cfg_attr(feature = "gdal-src", ignore)]
1020    fn test_read_string_array() {
1021        // Beware https://github.com/georust/gdal/issues/299 if you want to reuse this
1022        // This can't be Zarr because it doesn't support string arrays
1023        let fixture = TempFixture::fixture("alldatatypes.nc");
1024
1025        let dataset_options = DatasetOptions {
1026            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
1027            allowed_drivers: None,
1028            open_options: None,
1029            sibling_files: None,
1030        };
1031        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
1032
1033        let root_group = dataset.root_group().unwrap();
1034
1035        let string_array = root_group
1036            .open_md_array("string_var", CslStringList::new())
1037            .unwrap();
1038
1039        assert_eq!(string_array.read_as_string_array().unwrap(), ["abcd", "ef"]);
1040
1041        let non_string_array = root_group
1042            .open_md_array("uint_var", CslStringList::new())
1043            .unwrap();
1044
1045        // check that we don't get a `SIGSEV` here
1046        assert!(non_string_array.read_as_string_array().is_err());
1047
1048        assert!(string_array.read_as::<u8>(vec![0, 0], vec![1, 2]).is_err());
1049    }
1050
1051    #[test]
1052    #[cfg_attr(feature = "gdal-src", ignore)]
1053    fn test_datatype() {
1054        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
1055
1056        let dataset_options = DatasetOptions {
1057            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
1058            allowed_drivers: None,
1059            open_options: None,
1060            sibling_files: None,
1061        };
1062        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
1063
1064        let root_group = dataset.root_group().unwrap();
1065
1066        let md_array = root_group
1067            .open_md_array("byte_no_cf", CslStringList::new())
1068            .unwrap();
1069
1070        let datatype = md_array.datatype();
1071
1072        assert_eq!(datatype.class(), ExtendedDataTypeClass::Numeric);
1073        assert_eq!(datatype.numeric_datatype(), GDALDataType::GDT_Byte);
1074        assert_eq!(datatype.name(), "");
1075    }
1076
1077    #[test]
1078    #[cfg_attr(feature = "gdal-src", ignore)]
1079    fn test_spatial_ref() {
1080        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
1081
1082        let dataset_options = DatasetOptions {
1083            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
1084            allowed_drivers: None,
1085            open_options: None,
1086            sibling_files: None,
1087        };
1088        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
1089
1090        let root_group = dataset.root_group().unwrap();
1091        let md_array = root_group
1092            .open_md_array("byte_no_cf", CslStringList::new())
1093            .unwrap();
1094
1095        let spatial_ref = md_array.spatial_reference().unwrap();
1096
1097        assert_eq!(spatial_ref.name().unwrap(), "NAD27 / UTM zone 11N");
1098
1099        assert_eq!(spatial_ref.authority().unwrap(), "EPSG:26711");
1100    }
1101
1102    #[test]
1103    #[cfg_attr(feature = "gdal-src", ignore)]
1104    fn test_no_data_value() {
1105        let fixture = "/vsizip/fixtures/byte_no_cf.zarr.zip";
1106
1107        let dataset_options = DatasetOptions {
1108            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
1109            allowed_drivers: None,
1110            open_options: None,
1111            sibling_files: None,
1112        };
1113        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
1114
1115        let root_group = dataset.root_group().unwrap();
1116        let md_array = root_group
1117            .open_md_array("byte_no_cf", CslStringList::new())
1118            .unwrap();
1119
1120        assert_eq!(md_array.no_data_value_as_double(), Some(0.));
1121    }
1122
1123    #[test]
1124    #[cfg_attr(feature = "gdal-src", ignore)]
1125    fn test_attributes() {
1126        let fixture = "/vsizip/fixtures/cf_nasa_4326.zarr.zip";
1127
1128        let dataset_options = DatasetOptions {
1129            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
1130            allowed_drivers: None,
1131            open_options: None,
1132            sibling_files: None,
1133        };
1134        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
1135
1136        let root_group = dataset.root_group().unwrap();
1137
1138        assert_eq!(
1139            root_group.attribute("title").unwrap().read_as_string(),
1140            "Simple CF file"
1141        );
1142
1143        let group_science = root_group
1144            .open_group("science", CslStringList::new())
1145            .unwrap();
1146
1147        assert!(group_science
1148            .dimensions(Default::default())
1149            .unwrap()
1150            .is_empty());
1151
1152        let group_grids = group_science
1153            .open_group("grids", CslStringList::new())
1154            .unwrap();
1155        let group_data = group_grids
1156            .open_group("data", CslStringList::new())
1157            .unwrap();
1158
1159        let md_array = group_data
1160            .open_md_array("temp", CslStringList::new())
1161            .unwrap();
1162
1163        assert_eq!(
1164            md_array
1165                .attribute("standard_name")
1166                .unwrap()
1167                .read_as_string(),
1168            "air_temperature"
1169        );
1170
1171        assert_eq!(md_array.no_data_value_as_double().unwrap(), -9999.);
1172    }
1173
1174    #[test]
1175    #[cfg_attr(feature = "gdal-src", ignore)]
1176    fn test_unit() {
1177        let fixture = "/vsizip/fixtures/cf_nasa_4326.zarr.zip";
1178
1179        let dataset_options = DatasetOptions {
1180            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
1181            allowed_drivers: None,
1182            open_options: None,
1183            sibling_files: None,
1184        };
1185        let dataset = Dataset::open_ex(fixture, dataset_options).unwrap();
1186
1187        let root_group = dataset.root_group().unwrap();
1188
1189        assert_eq!(
1190            root_group.attribute("title").unwrap().read_as_string(),
1191            "Simple CF file"
1192        );
1193
1194        let group_science = root_group
1195            .open_group("science", CslStringList::new())
1196            .unwrap();
1197        let group_grids = group_science
1198            .open_group("grids", CslStringList::new())
1199            .unwrap();
1200
1201        drop(group_science); // check that `Group`s do not borrow each other
1202
1203        let group_data = group_grids
1204            .open_group("data", CslStringList::new())
1205            .unwrap();
1206
1207        let md_array = group_data
1208            .open_md_array("temp", CslStringList::new())
1209            .unwrap();
1210
1211        assert_eq!(md_array.unit(), "K");
1212    }
1213
1214    #[test]
1215    #[cfg_attr(feature = "gdal-src", ignore)]
1216    fn test_stats() {
1217        // make a copy to avoid writing the statistics into the original file
1218        let fixture = TempFixture::fixture("byte_no_cf.zarr.zip");
1219
1220        let dataset_options = DatasetOptions {
1221            open_flags: GdalOpenFlags::GDAL_OF_MULTIDIM_RASTER,
1222            allowed_drivers: None,
1223            open_options: None,
1224            sibling_files: None,
1225        };
1226        let dataset = Dataset::open_ex(
1227            format!("/vsizip/{}", fixture.path().display()),
1228            dataset_options,
1229        )
1230        .unwrap();
1231        let root_group = dataset.root_group().unwrap();
1232        let array_name = "byte_no_cf".to_string();
1233        let options = CslStringList::new();
1234        let md_array = root_group.open_md_array(&array_name, options).unwrap();
1235
1236        assert!(md_array.get_statistics(false, true).unwrap().is_none());
1237
1238        assert_eq!(
1239            md_array.get_statistics(true, true).unwrap().unwrap(),
1240            MdStatisticsAll {
1241                min: 74.0,
1242                max: 255.0,
1243                mean: 126.76500000000001,
1244                std_dev: 22.928470838675654,
1245                valid_count: 400,
1246            }
1247        );
1248    }
1249}