oxigdal_netcdf/lib.rs
1//! OxiGDAL NetCDF Driver - Pure Rust NetCDF-3 with Optional NetCDF-4 Support
2//!
3//! This crate provides NetCDF file format support for OxiGDAL, following the
4//! COOLJAPAN Pure Rust policy.
5//!
6//! # Pure Rust Policy Compliance
7//!
8//! **IMPORTANT**: This driver provides the structure and API for Pure Rust NetCDF support,
9//! but the actual netcdf3 integration is currently incomplete due to breaking API changes
10//! in netcdf3 v0.6.0. The driver demonstrates:
11//!
12//! - Complete Pure Rust data structures for NetCDF metadata (dimensions, variables, attributes)
13//! - CF conventions support
14//! - Feature-gated architecture for Pure Rust vs. C-binding implementations
15//!
16//! **Status**: The reader/writer implementations need to be updated to use the new
17//! `Dataset`/`FileReader`/`FileWriter` API from netcdf3 v0.6.0 (breaking change from v0.1.0).
18//!
19//! For NetCDF-4 (HDF5-based) support, you can enable the `netcdf4` feature,
20//! which requires system libraries (libnetcdf, libhdf5) and is **NOT Pure Rust**.
21//!
22//! ## Feature Flags
23//!
24//! - `netcdf3` (default): Pure Rust NetCDF-3 support via netcdf3 crate
25//! - `netcdf4`: NetCDF-4/HDF5 support via C bindings (requires system libraries)
26//! - `cf_conventions`: CF (Climate and Forecast) conventions support
27//! - `async`: Async I/O support
28//! - `compression`: Compression support (NetCDF-4 only)
29//!
30//! # NetCDF Format Support
31//!
32//! ## NetCDF-3 (Pure Rust, Default)
33//!
34//! Fully supported data types:
35//! - `i8`, `i16`, `i32` - Signed integers
36//! - `f32`, `f64` - Floating point numbers
37//! - `char` - Character data
38//!
39//! Features:
40//! - Fixed and unlimited dimensions
41//! - Multi-dimensional arrays
42//! - Variable and global attributes
43//! - Coordinate variables
44//!
45//! ## NetCDF-4 (C Bindings, Feature-Gated)
46//!
47//! Additional data types (requires `netcdf4` feature):
48//! - `u8`, `u16`, `u32`, `u64` - Unsigned integers
49//! - `i64`, `u64` - 64-bit integers
50//! - `string` - Variable-length strings
51//!
52//! Additional features (requires `netcdf4` feature):
53//! - HDF5-based compression
54//! - Groups and nested groups
55//! - User-defined types
56//! - Multiple unlimited dimensions
57//!
58//! # Example - Reading NetCDF-3 File (Pure Rust)
59//!
60//! ```ignore
61//! use oxigdal_netcdf::NetCdfReader;
62//!
63//! // Open a NetCDF-3 file
64//! let reader = NetCdfReader::open("data.nc")?;
65//!
66//! // Get metadata
67//! println!("{}", reader.metadata().summary());
68//!
69//! // List dimensions
70//! for dim in reader.dimensions().iter() {
71//! println!("Dimension: {} (size: {})", dim.name(), dim.len());
72//! }
73//!
74//! // List variables
75//! for var in reader.variables().iter() {
76//! println!("Variable: {} (type: {})", var.name(), var.data_type().name());
77//! }
78//!
79//! // Read variable data
80//! let temperature = reader.read_f32("temperature")?;
81//! println!("Temperature data: {:?}", temperature);
82//! ```
83//!
84//! # Example - Writing NetCDF-3 File (Pure Rust)
85//!
86//! ```ignore
87//! use oxigdal_netcdf::{NetCdfWriter, NetCdfVersion};
88//! use oxigdal_netcdf::dimension::Dimension;
89//! use oxigdal_netcdf::variable::{Variable, DataType};
90//! use oxigdal_netcdf::attribute::{Attribute, AttributeValue};
91//!
92//! // Create a new NetCDF-3 file
93//! let mut writer = NetCdfWriter::create("output.nc", NetCdfVersion::Classic)?;
94//!
95//! // Add dimensions
96//! writer.add_dimension(Dimension::new_unlimited("time", 0)?)?;
97//! writer.add_dimension(Dimension::new("lat", 180)?)?;
98//! writer.add_dimension(Dimension::new("lon", 360)?)?;
99//!
100//! // Add coordinate variables
101//! writer.add_variable(Variable::new_coordinate("time", DataType::F64)?)?;
102//! writer.add_variable(Variable::new_coordinate("lat", DataType::F32)?)?;
103//! writer.add_variable(Variable::new_coordinate("lon", DataType::F32)?)?;
104//!
105//! // Add data variable
106//! let temp_var = Variable::new(
107//! "temperature",
108//! DataType::F32,
109//! vec!["time".to_string(), "lat".to_string(), "lon".to_string()],
110//! )?;
111//! writer.add_variable(temp_var)?;
112//!
113//! // Add variable attributes
114//! writer.add_variable_attribute(
115//! "temperature",
116//! Attribute::new("units", AttributeValue::text("celsius"))?,
117//! )?;
118//! writer.add_variable_attribute(
119//! "temperature",
120//! Attribute::new("long_name", AttributeValue::text("Air Temperature"))?,
121//! )?;
122//!
123//! // Add global attributes
124//! writer.add_global_attribute(
125//! Attribute::new("Conventions", AttributeValue::text("CF-1.8"))?,
126//! )?;
127//! writer.add_global_attribute(
128//! Attribute::new("title", AttributeValue::text("Temperature Data"))?,
129//! )?;
130//!
131//! // End define mode
132//! writer.end_define_mode()?;
133//!
134//! // Write data
135//! let time_data = vec![0.0, 1.0, 2.0];
136//! writer.write_f64("time", &time_data)?;
137//!
138//! let lat_data: Vec<f32> = (0..180).map(|i| -90.0 + i as f32).collect();
139//! writer.write_f32("lat", &lat_data)?;
140//!
141//! let lon_data: Vec<f32> = (0..360).map(|i| -180.0 + i as f32).collect();
142//! writer.write_f32("lon", &lon_data)?;
143//!
144//! // Write temperature data
145//! let temp_data = vec![20.0f32; 3 * 180 * 360];
146//! writer.write_f32("temperature", &temp_data)?;
147//!
148//! // Close file
149//! writer.close()?;
150//! ```
151//!
152//! # CF Conventions Support
153//!
154//! The driver recognizes and parses CF (Climate and Forecast) conventions metadata:
155//!
156//! ```ignore
157//! use oxigdal_netcdf::NetCdfReader;
158//!
159//! let reader = NetCdfReader::open("cf_data.nc")?;
160//!
161//! if let Some(cf) = reader.cf_metadata() {
162//! if cf.is_cf_compliant() {
163//! println!("CF Conventions: {}", cf.conventions.as_deref().unwrap_or(""));
164//! println!("Title: {}", cf.title.as_deref().unwrap_or(""));
165//! println!("Institution: {}", cf.institution.as_deref().unwrap_or(""));
166//! }
167//! }
168//! ```
169//!
170//! # Pure Rust Limitations
171//!
172//! When using the default Pure Rust mode (NetCDF-3 only):
173//!
174//! - No NetCDF-4/HDF5 format support
175//! - No compression support
176//! - No groups or user-defined types
177//! - Only one unlimited dimension allowed
178//! - Limited to NetCDF-3 data types
179//!
180//! To use NetCDF-4 features, enable the `netcdf4` feature (requires C dependencies):
181//!
182//! ```toml
183//! [dependencies]
184//! oxigdal-netcdf = { version = "0.1", features = ["netcdf4"] }
185//! ```
186//!
187//! **Note**: Enabling `netcdf4` violates the COOLJAPAN Pure Rust policy and requires
188//! system libraries (libnetcdf ≥ 4.0, libhdf5 ≥ 1.8).
189//!
190//! # Performance Considerations
191//!
192//! - Pure Rust NetCDF-3 reader/writer has comparable performance to C libraries
193//! - For large datasets, consider using chunked reading/writing
194//! - Unlimited dimensions may have performance implications
195//! - CF metadata parsing is done on-demand
196//!
197//! # References
198//!
199//! - [NetCDF User Guide](https://www.unidata.ucar.edu/software/netcdf/docs/)
200//! - [CF Conventions](http://cfconventions.org/)
201//! - [netcdf3 crate](https://crates.io/crates/netcdf3)
202
203#![cfg_attr(not(feature = "std"), no_std)]
204#![warn(clippy::all)]
205// Pedantic disabled to reduce noise - default clippy::all is sufficient
206// #![warn(clippy::pedantic)]
207#![deny(clippy::unwrap_used)]
208#![allow(clippy::module_name_repetitions)]
209#![allow(clippy::similar_names)]
210// Allow unexpected cfg for optional netcdf4 feature
211#![allow(unexpected_cfgs)]
212// Allow unused imports during development
213#![allow(unused_imports)]
214// Allow missing docs during API development
215#![allow(missing_docs)]
216// Allow dead code for future netcdf3/netcdf4 integration
217#![allow(dead_code)]
218// Allow manual div_ceil for dimension calculations
219#![allow(clippy::manual_div_ceil)]
220// Allow expect() for internal netcdf state invariants
221#![allow(clippy::expect_used)]
222// Allow collapsible match for netcdf error handling
223#![allow(clippy::collapsible_match)]
224// Allow struct field pub visibility in internal modules
225#![allow(clippy::redundant_field_names)]
226
227#[cfg(feature = "std")]
228extern crate std;
229
230pub mod attribute;
231#[cfg(feature = "cf_conventions")]
232pub mod cf_conventions;
233pub mod dimension;
234pub mod error;
235pub mod metadata;
236#[cfg(feature = "netcdf3")]
237pub(crate) mod nc3_compat;
238pub mod netcdf4;
239pub mod reader;
240pub mod variable;
241pub mod writer;
242
243// Re-export commonly used types
244pub use attribute::{Attribute, AttributeValue, Attributes};
245pub use dimension::{Dimension, DimensionSize, Dimensions};
246pub use error::{NetCdfError, Result};
247pub use metadata::{CfMetadata, NetCdfMetadata, NetCdfVersion};
248pub use netcdf4::{
249 ChunkInfo, CompressionFilter, Hdf5ByteOrder, Hdf5DatatypeClass, Hdf5MessageType,
250 Hdf5Superblock, Hdf5SuperblockVersion, Nc4Group, Nc4Reader, Nc4VariableInfo, Nc4Writer,
251};
252pub use reader::NetCdfReader;
253pub use variable::{DataType, Variable, Variables};
254pub use writer::NetCdfWriter;
255
256/// Crate version
257pub const VERSION: &str = env!("CARGO_PKG_VERSION");
258
259/// Crate name
260pub const NAME: &str = env!("CARGO_PKG_NAME");
261
262/// Pure Rust compliance status
263///
264/// Returns true if running in Pure Rust mode (no C dependencies).
265/// Returns false if netcdf4 feature is enabled (requires C libraries).
266#[must_use]
267pub const fn is_pure_rust() -> bool {
268 !cfg!(feature = "netcdf4")
269}
270
271/// Check if NetCDF-3 support is available.
272#[must_use]
273pub const fn has_netcdf3() -> bool {
274 cfg!(feature = "netcdf3")
275}
276
277/// Check if NetCDF-4 support is available.
278#[must_use]
279pub const fn has_netcdf4() -> bool {
280 cfg!(feature = "netcdf4")
281}
282
283/// Get supported format versions.
284#[must_use]
285#[allow(unused_mut)]
286pub fn supported_versions() -> Vec<NetCdfVersion> {
287 let mut versions = Vec::new();
288
289 #[cfg(feature = "netcdf3")]
290 {
291 versions.push(NetCdfVersion::Classic);
292 versions.push(NetCdfVersion::Offset64Bit);
293 }
294
295 #[cfg(feature = "netcdf4")]
296 {
297 versions.push(NetCdfVersion::NetCdf4);
298 versions.push(NetCdfVersion::NetCdf4Classic);
299 }
300
301 versions
302}
303
304/// Get driver information.
305#[must_use]
306pub fn info() -> String {
307 let pure_rust = if is_pure_rust() {
308 "Pure Rust"
309 } else {
310 "C Bindings"
311 };
312
313 let versions: Vec<&str> = supported_versions()
314 .iter()
315 .map(|v| v.format_name())
316 .collect();
317
318 format!(
319 "{} {} - {} - Supports: {}",
320 NAME,
321 VERSION,
322 pure_rust,
323 versions.join(", ")
324 )
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn test_version() {
333 assert!(!VERSION.is_empty());
334 assert_eq!(NAME, "oxigdal-netcdf");
335 }
336
337 #[test]
338 fn test_pure_rust_status() {
339 #[cfg(feature = "netcdf4")]
340 assert!(!is_pure_rust());
341
342 #[cfg(not(feature = "netcdf4"))]
343 assert!(is_pure_rust());
344 }
345
346 #[test]
347 fn test_feature_detection() {
348 #[cfg(feature = "netcdf3")]
349 assert!(has_netcdf3());
350
351 #[cfg(feature = "netcdf4")]
352 assert!(has_netcdf4());
353 }
354
355 #[test]
356 fn test_supported_versions() {
357 let versions = supported_versions();
358
359 // When no features enabled, versions list is empty
360 #[cfg(all(not(feature = "netcdf3"), not(feature = "netcdf4")))]
361 assert!(versions.is_empty());
362
363 #[cfg(any(feature = "netcdf3", feature = "netcdf4"))]
364 assert!(!versions.is_empty());
365
366 #[cfg(feature = "netcdf3")]
367 assert!(versions.contains(&NetCdfVersion::Classic));
368 }
369
370 #[test]
371 fn test_info() {
372 let info = info();
373 assert!(info.contains(NAME));
374 assert!(info.contains(VERSION));
375 }
376}