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#[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 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 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 == 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 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 let array_step: *const i64 = std::ptr::null();
152 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 if rv != 1 {
187 return Err(_last_cpl_err(CPLErr::CE_Failure));
189 }
190
191 Ok(())
192 }
193
194 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 unsafe { self.read(buffer.as_mut_ptr(), array_start_index, count) }
220 }
221
222 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 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 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 let dim: IxDyn = IxDyn(&array_size);
272 Ok(ArrayD::from_shape_vec(dim, data)?)
273 }
274
275 pub fn read_as_string_array(&self) -> Result<Vec<String>> {
277 let data_type = self.datatype();
278 if !data_type.class().is_string() {
279 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 let array_step: *const i64 = std::ptr::null();
301 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 if rv != 1 {
327 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 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 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 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#[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 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 == 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 VSIFree(c_dimensions as *mut c_void);
581
582 Ok(dimensions)
583 }
584 }
585}
586
587#[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 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#[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 pub fn from_c_extended_data_type(c_data_type: GDALExtendedDataTypeH) -> Self {
696 Self { c_data_type }
697 }
698
699 pub fn class(&self) -> ExtendedDataTypeClass {
701 unsafe { GDALExtendedDataTypeGetClass(self.c_data_type) }.into()
702 }
703
704 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#[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 pub fn from_c_attribute(c_attribute: GDALAttributeH) -> Self {
735 Self { c_attribute }
736 }
737
738 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 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
818impl Dataset {
820 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 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 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 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 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); 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 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}