1use std::path::Path;
7
8use crate::attribute::{Attribute, AttributeValue, Attributes};
9use crate::dimension::{Dimension, Dimensions};
10use crate::error::{NetCdfError, Result};
11use crate::metadata::{CfMetadata, NetCdfMetadata, NetCdfVersion};
12use crate::variable::{DataType, Variable, Variables};
13
14#[cfg(feature = "netcdf3")]
15use std::cell::RefCell;
16
17pub struct NetCdfReader {
21 metadata: NetCdfMetadata,
22 #[cfg(feature = "netcdf3")]
23 file_nc3: Option<RefCell<netcdf3::FileReader>>,
24 #[cfg(feature = "netcdf4")]
25 file_nc4: Option<netcdf::File>,
26}
27
28impl std::fmt::Debug for NetCdfReader {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("NetCdfReader")
31 .field("metadata", &self.metadata)
32 .finish_non_exhaustive()
33 }
34}
35
36impl NetCdfReader {
37 #[allow(unused_variables)]
49 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
50 let path = path.as_ref();
51
52 #[cfg(feature = "netcdf3")]
54 {
55 if let Ok(file) = netcdf3::FileReader::open(path) {
56 return Self::from_netcdf3(file);
57 }
58 }
59
60 #[cfg(feature = "netcdf4")]
62 {
63 if let Ok(file) = netcdf::open(path) {
64 return Self::from_netcdf4(file);
65 }
66 }
67
68 #[cfg(not(feature = "netcdf3"))]
70 #[cfg(not(feature = "netcdf4"))]
71 {
72 Err(NetCdfError::FeatureNotEnabled {
73 feature: "netcdf3 or netcdf4".to_string(),
74 message: "Enable 'netcdf3' or 'netcdf4' feature to read NetCDF files".to_string(),
75 })
76 }
77
78 #[cfg(feature = "netcdf3")]
79 #[cfg(not(feature = "netcdf4"))]
80 {
81 Err(NetCdfError::InvalidFormat(
82 "File is not a valid NetCDF-3 file".to_string(),
83 ))
84 }
85
86 #[cfg(feature = "netcdf4")]
87 #[cfg(not(feature = "netcdf3"))]
88 {
89 Err(NetCdfError::InvalidFormat(
90 "File is not a valid NetCDF-4 file".to_string(),
91 ))
92 }
93
94 #[cfg(feature = "netcdf3")]
95 #[cfg(feature = "netcdf4")]
96 {
97 Err(NetCdfError::InvalidFormat(
98 "File is not a valid NetCDF file".to_string(),
99 ))
100 }
101 }
102
103 #[cfg(feature = "netcdf3")]
109 pub fn from_netcdf3(file: netcdf3::FileReader) -> Result<Self> {
110 let metadata = Self::read_metadata_nc3(&file)?;
111 Ok(Self {
112 metadata,
113 file_nc3: Some(RefCell::new(file)),
114 #[cfg(feature = "netcdf4")]
115 file_nc4: None,
116 })
117 }
118
119 #[cfg(feature = "netcdf4")]
125 pub fn from_netcdf4(file: netcdf::File) -> Result<Self> {
126 let metadata = Self::read_metadata_nc4(&file)?;
127 Ok(Self {
128 metadata,
129 file_nc3: None,
130 file_nc4: Some(file),
131 })
132 }
133
134 #[must_use]
136 pub const fn metadata(&self) -> &NetCdfMetadata {
137 &self.metadata
138 }
139
140 #[must_use]
142 pub fn version(&self) -> NetCdfVersion {
143 self.metadata.version()
144 }
145
146 #[must_use]
148 pub fn dimensions(&self) -> &Dimensions {
149 self.metadata.dimensions()
150 }
151
152 #[must_use]
154 pub fn variables(&self) -> &Variables {
155 self.metadata.variables()
156 }
157
158 #[must_use]
160 pub fn global_attributes(&self) -> &Attributes {
161 self.metadata.global_attributes()
162 }
163
164 #[must_use]
166 pub fn cf_metadata(&self) -> Option<&CfMetadata> {
167 self.metadata.cf_metadata()
168 }
169
170 #[cfg(feature = "netcdf3")]
172 fn read_metadata_nc3(file: &netcdf3::FileReader) -> Result<NetCdfMetadata> {
173 use crate::nc3_compat;
174
175 let mut metadata = NetCdfMetadata::new_classic();
176 let dataset = file.data_set();
177
178 let dimensions = nc3_compat::read_dimensions(dataset)?;
180 for dimension in dimensions {
181 metadata.dimensions_mut().add(dimension)?;
182 }
183
184 for attr_name in dataset.get_global_attr_names() {
186 if let Some(attr) = nc3_compat::read_global_attribute(dataset, &attr_name)? {
187 metadata.global_attributes_mut().add(attr)?;
188 }
189 }
190
191 for var_name in dataset.get_var_names() {
193 let var = nc3_compat::read_variable(dataset, &var_name)?;
194 metadata.variables_mut().add(var)?;
195 }
196
197 metadata.parse_cf_metadata();
199
200 Ok(metadata)
201 }
202
203 #[cfg(feature = "netcdf3")]
205 fn convert_datatype_nc3(nc3_type: netcdf3::DataType) -> Result<DataType> {
206 use netcdf3::DataType as Nc3Type;
207
208 match nc3_type {
209 Nc3Type::I8 => Ok(DataType::I8),
210 Nc3Type::I16 => Ok(DataType::I16),
211 Nc3Type::I32 => Ok(DataType::I32),
212 Nc3Type::F32 => Ok(DataType::F32),
213 Nc3Type::F64 => Ok(DataType::F64),
214 Nc3Type::U8 => Ok(DataType::Char), }
216 }
217
218 #[cfg(feature = "netcdf4")]
220 fn read_metadata_nc4(_file: &netcdf::File) -> Result<NetCdfMetadata> {
221 Err(NetCdfError::NetCdf4NotAvailable)
223 }
224
225 #[allow(unused_variables)]
231 pub fn read_f32(&self, var_name: &str) -> Result<Vec<f32>> {
232 #[cfg(feature = "netcdf3")]
233 if let Some(ref file_cell) = self.file_nc3 {
234 return Self::read_f32_nc3(&mut file_cell.borrow_mut(), var_name);
235 }
236
237 #[cfg(feature = "netcdf4")]
238 if let Some(ref _file) = self.file_nc4 {
239 return Err(NetCdfError::NetCdf4NotAvailable);
240 }
241
242 Err(NetCdfError::FeatureNotEnabled {
243 feature: "netcdf3 or netcdf4".to_string(),
244 message: "No reader available".to_string(),
245 })
246 }
247
248 #[allow(unused_variables)]
254 pub fn read_f64(&self, var_name: &str) -> Result<Vec<f64>> {
255 #[cfg(feature = "netcdf3")]
256 if let Some(ref file_cell) = self.file_nc3 {
257 return Self::read_f64_nc3(&mut file_cell.borrow_mut(), var_name);
258 }
259
260 #[cfg(feature = "netcdf4")]
261 if let Some(ref _file) = self.file_nc4 {
262 return Err(NetCdfError::NetCdf4NotAvailable);
263 }
264
265 Err(NetCdfError::FeatureNotEnabled {
266 feature: "netcdf3 or netcdf4".to_string(),
267 message: "No reader available".to_string(),
268 })
269 }
270
271 #[allow(unused_variables)]
277 pub fn read_i32(&self, var_name: &str) -> Result<Vec<i32>> {
278 #[cfg(feature = "netcdf3")]
279 if let Some(ref file_cell) = self.file_nc3 {
280 return Self::read_i32_nc3(&mut file_cell.borrow_mut(), var_name);
281 }
282
283 #[cfg(feature = "netcdf4")]
284 if let Some(ref _file) = self.file_nc4 {
285 return Err(NetCdfError::NetCdf4NotAvailable);
286 }
287
288 Err(NetCdfError::FeatureNotEnabled {
289 feature: "netcdf3 or netcdf4".to_string(),
290 message: "No reader available".to_string(),
291 })
292 }
293
294 #[cfg(feature = "netcdf3")]
296 fn read_f32_nc3(file: &mut netcdf3::FileReader, var_name: &str) -> Result<Vec<f32>> {
297 let dataset = file.data_set();
298 let var_info = dataset
299 .get_var(var_name)
300 .ok_or_else(|| NetCdfError::VariableNotFound {
301 name: var_name.to_string(),
302 })?;
303
304 use netcdf3::DataType as Nc3Type;
305 let data_type = var_info.data_type();
306
307 if data_type != Nc3Type::F32 {
309 return Err(NetCdfError::DataTypeMismatch {
310 expected: "F32".to_string(),
311 found: format!("{:?}", data_type),
312 });
313 }
314
315 let data = file.read_var_f32(var_name)?;
317 Ok(data)
318 }
319
320 #[cfg(feature = "netcdf3")]
322 fn read_f64_nc3(file: &mut netcdf3::FileReader, var_name: &str) -> Result<Vec<f64>> {
323 let dataset = file.data_set();
324 let var_info = dataset
325 .get_var(var_name)
326 .ok_or_else(|| NetCdfError::VariableNotFound {
327 name: var_name.to_string(),
328 })?;
329
330 use netcdf3::DataType as Nc3Type;
331 let data_type = var_info.data_type();
332
333 if data_type != Nc3Type::F64 {
335 return Err(NetCdfError::DataTypeMismatch {
336 expected: "F64".to_string(),
337 found: format!("{:?}", data_type),
338 });
339 }
340
341 let data = file.read_var_f64(var_name)?;
343 Ok(data)
344 }
345
346 #[cfg(feature = "netcdf3")]
348 fn read_i32_nc3(file: &mut netcdf3::FileReader, var_name: &str) -> Result<Vec<i32>> {
349 let dataset = file.data_set();
350 let var_info = dataset
351 .get_var(var_name)
352 .ok_or_else(|| NetCdfError::VariableNotFound {
353 name: var_name.to_string(),
354 })?;
355
356 use netcdf3::DataType as Nc3Type;
357 let data_type = var_info.data_type();
358
359 if data_type != Nc3Type::I32 {
361 return Err(NetCdfError::DataTypeMismatch {
362 expected: "I32".to_string(),
363 found: format!("{:?}", data_type),
364 });
365 }
366
367 let data = file.read_var_i32(var_name)?;
369 Ok(data)
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_data_type_conversion() {
379 #[cfg(feature = "netcdf3")]
380 {
381 use netcdf3::DataType as Nc3Type;
382 assert_eq!(
383 NetCdfReader::convert_datatype_nc3(Nc3Type::F32)
384 .expect("Failed to convert NetCDF datatype in test"),
385 DataType::F32
386 );
387 assert_eq!(
388 NetCdfReader::convert_datatype_nc3(Nc3Type::F64)
389 .expect("Failed to convert NetCDF datatype in test"),
390 DataType::F64
391 );
392 assert_eq!(
393 NetCdfReader::convert_datatype_nc3(Nc3Type::I32)
394 .expect("Failed to convert NetCDF datatype in test"),
395 DataType::I32
396 );
397 }
398 }
399}