netcdf_reader/nc4/
dimensions.rs1use std::collections::HashMap;
14
15use hdf5_reader::group::Group;
16
17use crate::error::{Error, Result};
18use crate::types::NcDimension;
19
20fn leaf_name(name: &str) -> &str {
21 name.rsplit('/').next().unwrap_or(name)
22}
23
24pub(crate) fn is_dimension_without_variable_name(name: &str) -> bool {
25 name.starts_with("This is a netCDF dimension but not a netCDF variable")
26}
27
28pub fn extract_dimensions(
37 group: &Group,
38 metadata_mode: crate::NcMetadataMode,
39) -> Result<(Vec<NcDimension>, HashMap<u64, NcDimension>)> {
40 let datasets = group.datasets()?;
41 extract_dimensions_from_datasets(&datasets, metadata_mode)
42}
43
44pub fn extract_dimensions_from_datasets(
45 datasets: &[hdf5_reader::Dataset],
46 metadata_mode: crate::NcMetadataMode,
47) -> Result<(Vec<NcDimension>, HashMap<u64, NcDimension>)> {
48 let mut dims: Vec<(Option<i64>, NcDimension, u64)> = Vec::new();
49
50 for ds in datasets {
51 if let Some((dimid, dim, address)) = extract_dimension_entry(ds, metadata_mode)? {
52 dims.push((dimid, dim, address));
53 }
54 }
55
56 dims.sort_by_key(|(id, _, _)| id.unwrap_or(i64::MAX));
58
59 let addr_map: HashMap<u64, NcDimension> =
60 dims.iter().map(|(_, d, addr)| (*addr, d.clone())).collect();
61
62 let dim_list: Vec<NcDimension> = dims.into_iter().map(|(_, d, _)| d).collect();
63
64 Ok((dim_list, addr_map))
65}
66
67fn extract_dimension_entry(
68 ds: &hdf5_reader::Dataset,
69 metadata_mode: crate::NcMetadataMode,
70) -> Result<Option<(Option<i64>, NcDimension, u64)>> {
71 let strict = metadata_mode == crate::NcMetadataMode::Strict;
72
73 let is_dim_scale = match ds.attribute("CLASS") {
74 Ok(attr) => match attr.read_string() {
75 Ok(value) => value == "DIMENSION_SCALE",
76 Err(err) if strict => {
77 return Err(Error::InvalidData(format!(
78 "dataset '{}' has unreadable CLASS attribute: {err}",
79 ds.name()
80 )))
81 }
82 Err(_) => false,
83 },
84 Err(_) => false,
85 };
86
87 if !is_dim_scale {
88 return Ok(None);
89 }
90
91 let name = match ds.attribute("NAME") {
92 Ok(attr) => match attr.read_string() {
93 Ok(value) => {
94 if is_dimension_without_variable_name(&value) {
95 leaf_name(ds.name()).to_string()
96 } else {
97 value
98 }
99 }
100 Err(err) if strict => {
101 return Err(Error::InvalidData(format!(
102 "dimension scale '{}' has unreadable NAME attribute: {err}",
103 ds.name()
104 )))
105 }
106 Err(_) => leaf_name(ds.name()).to_string(),
107 },
108 Err(_) => leaf_name(ds.name()).to_string(),
109 };
110
111 let shape = ds.shape();
112 let size = if shape.is_empty() { 0 } else { shape[0] };
113 let is_unlimited = ds
114 .max_dims()
115 .is_some_and(|md| !md.is_empty() && md[0] == u64::MAX);
116 let dimid = match ds.attribute("_Netcdf4Dimid") {
117 Ok(attr) => match attr.read_scalar::<i32>() {
118 Ok(id) => Some(id as i64),
119 Err(err) if strict => {
120 return Err(Error::InvalidData(format!(
121 "dimension scale '{}' has unreadable _Netcdf4Dimid attribute: {err}",
122 ds.name()
123 )))
124 }
125 Err(_) => None,
126 },
127 Err(_) => None,
128 };
129
130 Ok(Some((
131 dimid,
132 NcDimension {
133 name,
134 size,
135 is_unlimited,
136 },
137 ds.address(),
138 )))
139}