├── codecov.yml ├── .gitignore ├── netcdf ├── tests │ ├── testdata │ │ ├── simple_nc4.nc │ │ ├── pres_temp_4D.nc │ │ ├── sfc_pres_temp.nc │ │ ├── patmosx_v05r03-preliminary_NOAA-19_asc_d20130630_c20140325.nc │ │ └── simple_xy.nc │ ├── common │ │ └── mod.rs │ ├── file.rs │ ├── group.rs │ ├── attributes.rs │ └── types.rs ├── build.rs ├── Cargo.toml ├── src │ ├── rc.rs │ ├── error.rs │ ├── lib.rs │ ├── dimension.rs │ ├── group.rs │ ├── file.rs │ └── types.rs └── examples │ └── ncdump.rs ├── .gitmodules ├── netcdf-src ├── README.md ├── src │ └── lib.rs ├── Cargo.toml └── build.rs ├── netcdf-sys ├── testdata │ └── simple_xy.nc ├── README.md ├── LICENSE-MIT ├── src │ ├── mmap.rs │ ├── filter.rs │ ├── lib.rs │ ├── consts.rs │ └── dispatch.rs ├── Cargo.toml └── build.rs ├── Cargo.toml ├── .github └── workflows │ ├── codecov.yml │ ├── release.yml │ └── ci.yml ├── LICENSE-MIT ├── THIRD_PARTY ├── README.md └── LICENSE-APACHE /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "tests" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | testout 3 | Cargo.lock 4 | .*.swp 5 | -------------------------------------------------------------------------------- /netcdf/tests/testdata/simple_nc4.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brews/netcdf/master/netcdf/tests/testdata/simple_nc4.nc -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "netcdf-src/source"] 2 | path = netcdf-src/source 3 | url = https://github.com/mulimoen/netcdf-c 4 | -------------------------------------------------------------------------------- /netcdf/tests/testdata/pres_temp_4D.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brews/netcdf/master/netcdf/tests/testdata/pres_temp_4D.nc -------------------------------------------------------------------------------- /netcdf/tests/testdata/sfc_pres_temp.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brews/netcdf/master/netcdf/tests/testdata/sfc_pres_temp.nc -------------------------------------------------------------------------------- /netcdf-src/README.md: -------------------------------------------------------------------------------- 1 | # netcdf-src 2 | 3 | Dummy crate for building `netCDF` from source 4 | 5 | The current pinned version is 4.9.2 6 | -------------------------------------------------------------------------------- /netcdf/tests/testdata/patmosx_v05r03-preliminary_NOAA-19_asc_d20130630_c20140325.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brews/netcdf/master/netcdf/tests/testdata/patmosx_v05r03-preliminary_NOAA-19_asc_d20130630_c20140325.nc -------------------------------------------------------------------------------- /netcdf-src/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Dummy crate for building `netCDF` from source 2 | //! 3 | //! The current pinned version is 4.9.2 4 | 5 | #[cfg(feature = "dap")] 6 | extern crate link_cplusplus; 7 | 8 | extern crate hdf5_sys; 9 | extern crate libz_sys; 10 | -------------------------------------------------------------------------------- /netcdf/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | /// Get location of the test files 2 | pub(crate) fn test_location() -> std::path::PathBuf { 3 | use std::path::Path; 4 | 5 | let mnf_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); 6 | Path::new(&mnf_dir).join("tests").join("testdata") 7 | } 8 | -------------------------------------------------------------------------------- /netcdf/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | if std::env::var("DEP_NETCDF_HAS_MMAP").is_ok() { 3 | println!("cargo:rustc-cfg=feature=\"has-mmap\""); 4 | } 5 | for (env, _value) in std::env::vars() { 6 | if let Some(version) = env.strip_prefix("DEP_NETCDF_VERSION_") { 7 | println!("cargo:rustc-cfg=feature={version}"); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /netcdf-sys/testdata/simple_xy.nc: -------------------------------------------------------------------------------- 1 | CDF 2 | xy data ` 3 |  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFG -------------------------------------------------------------------------------- /netcdf/tests/testdata/simple_xy.nc: -------------------------------------------------------------------------------- 1 | CDF 2 | xy data ` 3 |  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFG -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "netcdf", 4 | "netcdf-sys", 5 | "netcdf-src", 6 | ] 7 | default-members = ["netcdf", "netcdf-sys"] 8 | resolver = "2" 9 | 10 | [workspace.dependencies] 11 | netcdf = { path = "netcdf", version = "0.9.2" } 12 | netcdf-sys = { path = "netcdf-sys", version = "0.6.2" } 13 | netcdf-src = { path = "netcdf-src", version = "0.3.6" } 14 | hdf5-sys = { version = "0.8.0" } 15 | -------------------------------------------------------------------------------- /netcdf-sys/README.md: -------------------------------------------------------------------------------- 1 | # netcdf-sys 2 | 3 | Rust bindings to `netcdf-c` to locate and link the system libraries neccessary to use `netcdf`. 4 | This library can also build `hdf5` and `netcdf` from source, to allow a fully static linking experience. This is enabled with the `static` feature. 5 | 6 | ## Detection of netCDF 7 | 8 | The detection of `netCDF` has this priority order: 9 | * `static` feature will choose the built static library 10 | * `NETCDF_DIR` environment variable 11 | * `nc-config` 12 | * Default linker-available `netcdf` 13 | 14 | If an include file is not found, some features might not be available, despite being included in the library. 15 | -------------------------------------------------------------------------------- /netcdf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netcdf" 3 | version = "0.9.3" 4 | authors = [ 5 | "Michael Hiley ", 6 | "Magnus Ulimoen " 7 | ] 8 | license = "MIT OR Apache-2.0" 9 | description = "High-level NetCDF bindings for Rust" 10 | repository = "https://github.com/georust/netcdf" 11 | keywords = ["netcdf", "hdf", "hdf5", "libnetcdf", "netcdf4"] 12 | edition = "2021" 13 | readme = "../README.md" 14 | categories = ["science", "filesystem"] 15 | exclude = ["examples/**", "tests/**"] 16 | build = "build.rs" 17 | 18 | [features] 19 | default = ["ndarray"] 20 | static = ["netcdf-sys/static"] 21 | 22 | [dependencies] 23 | ndarray = { version = "0.15", optional = true } 24 | netcdf-sys = { workspace = true } 25 | bitflags = "2.4.2" 26 | libc = "0.2.155" 27 | 28 | [dev-dependencies] 29 | clap = { version = "4.5.1", features = ["derive"] } 30 | tempfile = "3.1.0" 31 | 32 | [package.metadata.docs.rs] 33 | features = ["static"] 34 | rustdoc-args = ["--cfg", "docsrs"] 35 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | on: [push] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.ref }} 6 | cancel-in-progress: true 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | tarpaulin: 13 | name: tarpaulin 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | 19 | - name: Install netcdf 20 | run: sudo apt-get install libnetcdf-dev 21 | 22 | - name: Install rust 23 | uses: dtolnay/rust-toolchain@stable 24 | 25 | - name: Install tarpaulin 26 | uses: baptiste0928/cargo-install@30f432979e99f3ea66a8fa2eede53c07063995d8 # v2.1.0 27 | with: 28 | crate: cargo-tarpaulin 29 | version: "0.27.3" 30 | 31 | - name: Tarpaulin 32 | run: cargo tarpaulin --verbose --out Xml --ignore-tests 33 | 34 | - name: Upload to codecov 35 | uses: codecov/codecov-action@v4.1.0 36 | with: 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /netcdf-src/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netcdf-src" 3 | version = "0.3.7" 4 | authors = ["Magnus Ulimoen "] 5 | edition = "2021" 6 | description = "Build scripts for building `netCDF` from source" 7 | build = "build.rs" 8 | repository = "https://github.com/georust/netcdf" 9 | license-file = "source/COPYRIGHT" 10 | links = "netcdfsrc" 11 | categories = ["filesystem"] 12 | keywords = ["netcdf"] 13 | readme = "README.md" 14 | exclude = [ 15 | "source/unit_test/**", 16 | "source/NUG/**", 17 | "source/dap4_test/**", 18 | "source/examples/**", 19 | "source/nc_test/**", 20 | "source/h5_test/**", 21 | "source/nc_perf/**", 22 | "source/ncdump/**", 23 | "source/hdf4_test/**", 24 | "source/ncgen/**", 25 | "source/ncgen3/**", 26 | "source/nctest/**", 27 | "source/ncdap_test/**", 28 | ] 29 | 30 | [features] 31 | dap = ["dep:link-cplusplus"] 32 | 33 | [dependencies] 34 | hdf5-sys = { workspace = true, features = ["hl", "deprecated", "zlib"] } 35 | libz-sys = { version = "1.0.25" } 36 | link-cplusplus = { version = "1.0.9", optional = true } 37 | 38 | [build-dependencies] 39 | cmake = "0.1.44" 40 | -------------------------------------------------------------------------------- /netcdf-sys/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Michael Hiley 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /netcdf-sys/src/mmap.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "has-mmap")] 2 | 3 | use std::os::raw::{c_char, c_int, c_void}; 4 | 5 | #[repr(C)] 6 | #[derive(Copy, Clone)] 7 | #[cfg(all(feature = "has-mmap", feature = "1.6.2"))] 8 | pub struct NC_memio { 9 | size: usize, 10 | memory: *mut c_void, 11 | flags: c_int, 12 | } 13 | 14 | extern "C" { 15 | #[cfg(feature = "has-mmap")] 16 | pub fn nc_open_mem( 17 | path: *const c_char, 18 | mode: c_int, 19 | size: usize, 20 | memory: *mut c_void, 21 | ncidp: *mut c_int, 22 | ) -> c_int; 23 | 24 | #[cfg(all(feature = "has-mmap", feature = "1.6.2"))] 25 | pub fn nc_create_mem( 26 | path: *const c_char, 27 | mode: c_int, 28 | initialsize: usize, 29 | ncidp: *mut c_int, 30 | ) -> c_int; 31 | #[cfg(all(feature = "has-mmap", feature = "1.6.2"))] 32 | pub fn nc_open_memio( 33 | path: *const c_char, 34 | mode: c_int, 35 | info: *mut NC_memio, 36 | ncidp: *mut c_int, 37 | ) -> c_int; 38 | 39 | #[cfg(all(feature = "has-mmap", feature = "1.6.2"))] 40 | pub fn nc_close_memio(ncid: c_int, info: *mut NC_memio) -> c_int; 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: 5 | push: 6 | tags: 7 | - "netcdf-v*" 8 | - "netcdf-sys-v*" 9 | - "netcdf-src-v*" 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | release: 16 | name: release 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: Install netCDF 24 | run: sudo apt-get update && sudo apt-get install libnetcdf-dev 25 | - name: Install rust 26 | uses: dtolnay/rust-toolchain@stable 27 | with: 28 | toolchain: stable 29 | - name: Publish netcdf-src 30 | if: "${{ startsWith(github.ref_name, 'netcdf-src-v') }}" 31 | run: cargo publish --package netcdf-src --token "${{ secrets.CRATES_IO_TOKEN }}" 32 | - name: Publish netcdf-sys 33 | if: "${{ startsWith(github.ref_name, 'netcdf-sys-v') }}" 34 | run: cargo publish --package netcdf-sys --token "${{ secrets.CRATES_IO_TOKEN }}" 35 | - name: Publish netcdf 36 | if: "${{ startsWith(github.ref_name, 'netcdf-v') }}" 37 | run: cargo publish --package netcdf --token "${{ secrets.CRATES_IO_TOKEN }}" 38 | -------------------------------------------------------------------------------- /netcdf/src/rc.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, CStr, CString}; 2 | use std::ops::Deref; 3 | use std::ptr::NonNull; 4 | 5 | pub fn set(key: &str, value: &str) -> crate::error::Result<()> { 6 | let key = CString::new(key)?; 7 | let value = CString::new(value)?; 8 | crate::error::checked(crate::with_lock(|| unsafe { 9 | netcdf_sys::nc_rc_set(key.as_ptr(), value.as_ptr()) 10 | })) 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct OwnedString { 15 | inner: NonNull, 16 | } 17 | 18 | impl Deref for OwnedString { 19 | type Target = CStr; 20 | fn deref(&self) -> &Self::Target { 21 | unsafe { CStr::from_ptr(self.inner.as_ptr()) } 22 | } 23 | } 24 | 25 | impl Drop for OwnedString { 26 | fn drop(&mut self) { 27 | unsafe { 28 | libc::free(self.inner.as_ptr().cast()); 29 | } 30 | } 31 | } 32 | 33 | pub fn get(key: &str) -> Option { 34 | let key = if let Ok(key) = CString::new(key) { 35 | key 36 | } else { 37 | return None; 38 | }; 39 | let _lock = netcdf_sys::libnetcdf_lock.lock().unwrap(); 40 | let value = unsafe { netcdf_sys::nc_rc_get(key.as_ptr()) }; 41 | NonNull::new(value).map(|inner| OwnedString { inner }) 42 | } 43 | -------------------------------------------------------------------------------- /netcdf-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netcdf-sys" 3 | version = "0.6.2" 4 | authors = [ 5 | "Michael Hiley ", 6 | "Magnus Ulimoen " 7 | ] 8 | license = "MIT" 9 | description = "FFI bindings to NetCDF" 10 | repository = "https://github.com/georust/netcdf" 11 | keywords = ["netcdf", "hdf", "hdf5", "cdm", "ffi"] 12 | edition = "2021" 13 | links = "netcdf" 14 | build = "build.rs" 15 | readme = "README.md" 16 | categories = ["external-ffi-bindings", "filesystem", "science"] 17 | exclude = [ 18 | "testdata/**", 19 | ] 20 | rust-version = "1.70" 21 | 22 | [dependencies] 23 | libz-sys = { version = "1.0.25" } 24 | curl-sys = { version = "0.4.51", optional = true } 25 | hdf5-sys = { workspace = true } 26 | netcdf-src = { workspace = true, optional = true } 27 | 28 | [dev-dependencies] 29 | 30 | [features] 31 | default = [] 32 | memio = [] 33 | static = ["libz-sys/static", "hdf5-sys/static", "hdf5-sys/hl", "hdf5-sys/deprecated", "hdf5-sys/zlib", "dep:netcdf-src", "curl-sys?/static-curl", "curl-sys?/static-ssl"] 34 | dap = ["dep:curl-sys", "netcdf-src?/dap"] 35 | 36 | [build-dependencies] 37 | semver = "1.0.9" 38 | 39 | [package.metadata.docs.rs] 40 | features = ["static"] 41 | rustdoc-args = ["--cfg", "docsrs"] 42 | -------------------------------------------------------------------------------- /THIRD_PARTY: -------------------------------------------------------------------------------- 1 | netcdf contains code from Unidata's NetCDF, under the following license: 2 | 3 | NetCDF License (http://www.unidata.ucar.edu/software/netcdf/copyright.html) 4 | --------------- 5 | Copyright 1993-2014 University Corporation for Atmospheric Research/Unidata 6 | 7 | Portions of this software were developed by the Unidata Program at the University Corporation for Atmospheric Research. 8 | 9 | Access and use of this software shall impose the following obligations and understandings on the user. The user is granted the right, without any fee or cost, to use, copy, modify, alter, enhance and distribute this software, and any derivative works thereof, and its supporting documentation for any purpose whatsoever, provided that this entire notice appears in all copies of the software, derivative works and supporting documentation. Further, UCAR requests that the user credit UCAR/Unidata in any publications that result from the use of this software or in any product that includes this software, although this is not an obligation. The names UCAR and/or Unidata, however, may not be used in any advertising or publicity to endorse or promote any products or commercial entity unless specific written permission is obtained from UCAR/Unidata. The user also understands that UCAR/Unidata is not obligated to provide the user with any support, consulting, training or assistance of any kind with regard to the use, operation and performance of this software nor to provide the user with any updates, revisions, new versions or "bug fixes." 10 | 11 | THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. 12 | -------------------------------------------------------------------------------- /netcdf/tests/file.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn create_classic_model() { 3 | let d = tempfile::tempdir().unwrap(); 4 | let path = d.path().join("create_classic.nc"); 5 | 6 | let mut file = netcdf::create_with(path, netcdf::Options::CLASSIC).unwrap(); 7 | // Classic mode does not support groups 8 | file.add_group("grp").unwrap_err(); 9 | } 10 | 11 | #[test] 12 | fn create_with_options() { 13 | let d = tempfile::tempdir().unwrap(); 14 | let path0 = d.path().join("create0.nc"); 15 | let path1 = d.path().join("create1.nc"); 16 | 17 | let mut file = netcdf::create_with(path0, netcdf::Options::_64BIT_DATA).unwrap(); 18 | file.add_group("grp").unwrap_err(); 19 | let mut file = netcdf::create_with(path1, netcdf::Options::_64BIT_OFFSET).unwrap(); 20 | file.add_group("grp").unwrap_err(); 21 | } 22 | 23 | #[test] 24 | fn noclobber() { 25 | let d = tempfile::tempdir().unwrap(); 26 | let path = d.path().join("cdf5.nc"); 27 | { 28 | let mut file = netcdf::create_with(&path, netcdf::Options::_64BIT_DATA).unwrap(); 29 | file.add_dimension("t", 1).unwrap(); 30 | } 31 | let _file = netcdf::create_with(&path, netcdf::Options::NOCLOBBER).unwrap_err(); 32 | } 33 | 34 | #[test] 35 | fn appending_with() { 36 | let d = tempfile::tempdir().unwrap(); 37 | let path = d.path().join("cdf5.nc"); 38 | { 39 | let mut file = netcdf::create_with(&path, netcdf::Options::NETCDF4).unwrap(); 40 | file.add_dimension("t", 1).unwrap(); 41 | } 42 | let _file = 43 | netcdf::append_with(&path, netcdf::Options::NETCDF4 | netcdf::Options::DISKLESS).unwrap(); 44 | } 45 | 46 | #[test] 47 | fn fetch_from_path() { 48 | let d = tempfile::tempdir().unwrap(); 49 | let path = d.path().join("cdf5.nc"); 50 | { 51 | let mut file = netcdf::create(path.clone()).unwrap(); 52 | let mut group = file.add_group("grp").unwrap(); 53 | let mut subgroup = group.add_group("subgrp").unwrap(); 54 | subgroup.add_dimension("dim", 1).unwrap(); 55 | subgroup.add_variable::("var", &["dim"]).unwrap(); 56 | subgroup.add_attribute("attr", "test").unwrap(); 57 | } 58 | let file = netcdf::open(path).unwrap(); 59 | assert_eq!( 60 | file.group("grp/subgrp") 61 | .unwrap() 62 | .unwrap() 63 | .variable("var") 64 | .unwrap() 65 | .name(), 66 | file.variable("grp/subgrp/var").unwrap().name(), 67 | ); 68 | match file.attribute("grp/subgrp/attr").unwrap().value().unwrap() { 69 | netcdf::AttributeValue::Str(string) => assert_eq!(string, "test"), 70 | _ => panic!(), 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /netcdf-sys/src/filter.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::{c_char, c_int, c_uint, c_void}; 2 | 3 | extern "C" { 4 | pub fn nc_inq_var_filter_ids( 5 | ncid: c_int, 6 | varid: c_int, 7 | nfilters: *mut usize, 8 | filterids: *mut c_uint, 9 | ) -> c_int; 10 | 11 | pub fn nc_inq_var_filter_info( 12 | ncid: c_int, 13 | varid: c_int, 14 | id: c_uint, 15 | nparams: *mut usize, 16 | params: *mut c_uint, 17 | ) -> c_int; 18 | 19 | pub fn nc_inq_filter_avail(ncid: c_int, id: c_uint) -> c_int; 20 | 21 | } 22 | 23 | #[cfg(feature = "4.9.0")] 24 | extern "C" { 25 | pub fn nc_def_var_bzip2(ncid: c_int, varid: c_int, level: c_int) -> c_int; 26 | pub fn nc_inq_var_bzip2( 27 | ncid: c_int, 28 | varid: c_int, 29 | hasfilterp: *mut c_int, 30 | levelp: *mut c_int, 31 | ) -> c_int; 32 | 33 | pub fn nc_def_var_zstandard(ncid: c_int, varid: c_int, level: c_int) -> c_int; 34 | pub fn nc_inq_var_zstandard( 35 | ncid: c_int, 36 | varid: c_int, 37 | hasfilterp: *mut c_int, 38 | levelp: *mut c_int, 39 | ) -> c_int; 40 | 41 | pub fn nc_def_var_blosc( 42 | ncid: c_int, 43 | varid: c_int, 44 | subcompressor: c_uint, 45 | level: c_uint, 46 | blocksize: c_uint, 47 | addshuffle: c_uint, 48 | ) -> c_int; 49 | pub fn nc_inq_var_blosc( 50 | ncid: c_int, 51 | varid: c_int, 52 | hasfilterp: *mut c_int, 53 | subpcompressorp: *mut c_uint, 54 | levelp: *mut c_uint, 55 | blocsizep: *mut c_uint, 56 | addshufflep: *mut c_uint, 57 | ) -> c_int; 58 | } 59 | 60 | pub const NCZ_CODEC_CLASS_VER: c_int = 1; 61 | pub const NCZ_CODEC_HDF5: c_int = 1; 62 | pub const NCZ_FILTER_DECODE: usize = 0x00000001; 63 | 64 | pub type NCZ_get_codec_info_proto = unsafe extern "C" fn() -> *const c_void; 65 | 66 | pub type NCZ_codec_info_defaults_proto = unsafe extern "C" fn() -> *const c_void; 67 | 68 | #[repr(C)] 69 | #[derive(Copy, Clone)] 70 | pub struct NCZ_codec_t { 71 | pub version: c_int, 72 | pub sort: c_int, 73 | 74 | pub codecid: *const c_char, 75 | pub hdf5id: c_uint, 76 | pub NCZ_codec_initialize: Option c_void>, 77 | pub NCZ_codec_finalize: Option c_void>, 78 | 79 | pub NCZ_codec_to_hdf5: Option< 80 | unsafe extern "C" fn( 81 | codec: *const c_void, 82 | nparamsp: *mut usize, 83 | paramsp: *mut *mut c_uint, 84 | ) -> c_int, 85 | >, 86 | pub NCZ_hdf5_to_codec: Option< 87 | unsafe extern "C" fn( 88 | nparams: usize, 89 | params: *const c_uint, 90 | codecp: *mut *mut c_char, 91 | ) -> c_int, 92 | >, 93 | pub NCZ_modify_parameters: Option< 94 | unsafe extern "C" fn( 95 | ncid: c_int, 96 | varid: c_int, 97 | vnparamsp: *mut usize, 98 | vparamsp: *mut *mut c_uint, 99 | wnparamsp: *mut usize, 100 | wparamsp: *mut *mut c_uint, 101 | ) -> c_int, 102 | >, 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netcdf 2 | 3 | [![Docs](https://docs.rs/netcdf/badge.svg)](https://docs.rs/netcdf) 4 | ![Build Status](https://github.com/georust/netcdf/workflows/CI/badge.svg) 5 | [![Crates.io](https://img.shields.io/crates/d/netcdf.svg)](https://crates.io/crates/netcdf) 6 | [![](http://meritbadge.herokuapp.com/netcdf)](https://crates.io/crates/netcdf) 7 | [![codecov](https://codecov.io/gh/georust/netcdf/branch/master/graph/badge.svg)](https://codecov.io/gh/georust/netcdf) 8 | ![Crates.io](https://img.shields.io/crates/l/netcdf) 9 | 10 | 11 | Medium-level [netCDF](http://www.unidata.ucar.edu/software/netcdf/) bindings for Rust, allowing easy reading and writing of array-like structures to a file. 12 | netCDF can read and write `hdf5` files, which is a commonly used file format in scientific computing. 13 | 14 | ## Status 15 | 16 | Supported: 17 | 18 | * Variables 19 | * Normal Dimensions 20 | * Attributes 21 | * Subgroups 22 | * Open/Append/Create modes 23 | * Reading from memory 24 | * Unlimited dimensions 25 | * String variables 26 | * User defined types (variable length, enum, compound, opaque) 27 | 28 | Not (yet) supported: 29 | 30 | * Nested user defined types 31 | * Writing using memory-mapped file 32 | 33 | All variable data is read into a contiguous buffer, or into an [ndarray](https://github.com/rust-ndarray/rust-ndarray) if the `ndarray` feature is activated. 34 | 35 | ## Building 36 | 37 | This crate depends on `libnetcdf`, but a static build from source is also supported, which can be enabled using the `static` feature. 38 | 39 | The crate is built on several platforms using github actions, and is currently known to build form from source on all major platforms (linux, macos, windows (gnu+msvc)), and through the package installers `conda` and `apt`. 40 | 41 | If during compilation there is an error in building the `hdf5` crate, consider using the `static` feature which will include a compatible version of both `netcdf` and `hdf5`. This is likely to be an issue [upstream](https://github.com/aldanor/hdf5-rust/issues/262). 42 | 43 | ### Building without `libnetcdf` or building statically 44 | 1. `git clone https://github.com/georust/netcdf` 45 | 2. `git submodule update --init --recursive` 46 | 3. `cargo build --features static` 47 | 48 | 49 | ## Documentation 50 | 51 | Some examples of usage can be found in the [tests/lib.rs](netcdf/tests/lib.rs) file. The documentation can also be generated using `cargo doc`. 52 | 53 | 54 | ## Thread safety 55 | 56 | The `netcdf` crate is thread-safe, although the `netcdf-c` library is not itself threadsafe. To render a safe interface, a global mutex is used to serialize access to the underlying library. Consider using a non threadsafe version of `hdf5` to avoid double locking (performance consideration). 57 | 58 | Use of `netcdf-sys` is not thread-safe. Users of this library must take care that calls do not interfere with simultaneous use of e.g. `netcdf`. Using the `hdf5-sys` library could also pose a problem, as this library is used throughout `netCDF-c` and internal state may be disrupted. 59 | 60 | ## License 61 | 62 | Licensed under either of 63 | 64 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 65 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 66 | 67 | at your option. 68 | 69 | ### Contribution 70 | 71 | Unless you explicitly state otherwise, any contribution intentionally submitted 72 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 73 | additional terms or conditions. 74 | -------------------------------------------------------------------------------- /netcdf/tests/group.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn dimensions() { 3 | let d = tempfile::tempdir().unwrap(); 4 | let path = d.path().join("dimensions.rs"); 5 | 6 | let mut file = netcdf::create(path).unwrap(); 7 | 8 | let mut group = file.add_group("group").unwrap(); 9 | 10 | group.add_dimension("a", 5).unwrap(); 11 | group.add_dimension("b", 6).unwrap(); 12 | group.add_unlimited_dimension("c").unwrap(); 13 | 14 | let dim = group.dimension("a").unwrap(); 15 | assert_eq!(dim.len(), 5); 16 | let dim = group.dimension("b").unwrap(); 17 | assert_eq!(dim.len(), 6); 18 | let dim = group.dimension("c").unwrap(); 19 | assert_eq!(dim.len(), 0); 20 | assert!(group.dimension("d").is_none()); 21 | } 22 | 23 | #[test] 24 | fn groups() { 25 | let d = tempfile::tempdir().unwrap(); 26 | let path = d.path().join("groups.rs"); 27 | let mut file = netcdf::create(path).unwrap(); 28 | let mut group = file.add_group("group").unwrap(); 29 | group.add_group("g").unwrap(); 30 | group.add_group("e").unwrap(); 31 | group.add_group("f").unwrap(); 32 | 33 | assert_eq!(group.groups().count(), 3); 34 | assert!(group.group("w").is_none()); 35 | assert!(group.group_mut("w").is_none()); 36 | assert!(group.group_mut("e").is_some()); 37 | assert!(group.group("f").is_some()); 38 | } 39 | 40 | #[test] 41 | fn find_variable() { 42 | let d = tempfile::tempdir().unwrap(); 43 | let path = d.path().join("groups.rs"); 44 | let mut file = netcdf::create(path).unwrap(); 45 | let mut group = file.add_group("group").unwrap(); 46 | 47 | group.add_variable::("v", &[]).unwrap(); 48 | group.add_variable::("w", &[]).unwrap(); 49 | group.add_dimension("d", 3).unwrap(); 50 | group.add_variable::("z", &["d"]).unwrap(); 51 | 52 | assert_eq!(group.variables_mut().count(), 3); 53 | assert_eq!(group.variables().count(), 3); 54 | 55 | let v = group.variable("v").unwrap(); 56 | assert_eq!(v.dimensions().iter().count(), 0); 57 | assert_eq!(v.len(), 1); 58 | let z = group.variable_mut("z").unwrap(); 59 | assert_eq!(z.dimensions()[0].len(), 3); 60 | assert!(z.vartype().as_basic().unwrap().is_u8()); 61 | assert_eq!(z.name(), "z"); 62 | 63 | assert!(group.variable("vvvvv").is_none()); 64 | 65 | for mut var in group.variables_mut() { 66 | if !var.dimensions().is_empty() { 67 | var.set_compression(3, false).unwrap(); 68 | } 69 | if var.name() == "z" { 70 | var.set_chunking(&[1]).unwrap(); 71 | } else { 72 | var.set_chunking(&[]).unwrap(); 73 | } 74 | } 75 | } 76 | 77 | #[test] 78 | fn add_and_get_from_path() { 79 | let d = tempfile::tempdir().unwrap(); 80 | let path = d.path().join("cdf5.nc"); 81 | { 82 | let mut file = netcdf::create(path.clone()).unwrap(); 83 | file.add_group("a/b").unwrap(); 84 | file.add_dimension("a/b/dim", 1).unwrap(); 85 | assert!(file.add_dimension("a/c/dim", 1).is_err()); 86 | file.add_variable::("a/b/var", &["dim"]).unwrap(); 87 | assert!(file.add_variable::("a/c/var", &["dim"]).is_err()); 88 | file.add_attribute("a/b/attr", "test").unwrap(); 89 | assert!(file.add_attribute("a/c/test", "test").is_err()); 90 | } 91 | let file = netcdf::open(path).unwrap(); 92 | let root = file.root().unwrap(); 93 | assert_eq!( 94 | root.group("a/b").unwrap().variable("var").unwrap().name(), 95 | root.variable("a/b/var").unwrap().name(), 96 | ); 97 | assert!(file.group("missing/subgrp").is_err()); 98 | match file.attribute("a/b/attr").unwrap().value().unwrap() { 99 | netcdf::AttributeValue::Str(string) => assert_eq!(string, "test"), 100 | _ => panic!(), 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /netcdf-src/build.rs: -------------------------------------------------------------------------------- 1 | macro_rules! feature { 2 | ($feature:expr) => { 3 | std::env::var(concat!("CARGO_FEATURE_", $feature)) 4 | }; 5 | } 6 | 7 | fn get_hdf5_version() -> String { 8 | let (major, minor, patch) = std::env::vars() 9 | .filter_map(|(key, value)| { 10 | key.strip_prefix("DEP_HDF5_VERSION_").map(|key| { 11 | assert_eq!(value, "1"); 12 | let mut version = key.split('_'); 13 | let major: usize = version.next().unwrap().parse().unwrap(); 14 | let minor: usize = version.next().unwrap().parse().unwrap(); 15 | let patch: usize = version.next().unwrap().parse().unwrap(); 16 | 17 | (major, minor, patch) 18 | }) 19 | }) 20 | .max() 21 | .expect("Crate hdf5 should have emitted a hdf5 version"); 22 | 23 | format!("{major}.{minor}.{patch}") 24 | } 25 | 26 | fn main() { 27 | println!("cargo:rerun-if-changed=build.rs"); 28 | 29 | let hdf5_incdir = std::env::var("DEP_HDF5_INCLUDE").unwrap(); 30 | let mut hdf5_lib = std::env::var("DEP_HDF5_LIBRARY").unwrap(); 31 | let mut hdf5_hl_lib = std::env::var("DEP_HDF5_HL_LIBRARY").unwrap(); 32 | 33 | #[cfg(unix)] 34 | { 35 | let hdf5_root = format!("{hdf5_incdir}/../"); 36 | let mut hdf5_libdir = format!("{hdf5_root}/lib/"); 37 | if !std::path::Path::new(&hdf5_libdir).exists() { 38 | hdf5_libdir = format!("{hdf5_root}/lib64/"); 39 | } 40 | hdf5_lib = format!("{hdf5_libdir}/{hdf5_lib}.a"); 41 | hdf5_hl_lib = format!("{hdf5_libdir}/{hdf5_hl_lib}.a"); 42 | } 43 | 44 | let hdf5_version = get_hdf5_version(); 45 | 46 | let mut netcdf_config = cmake::Config::new("source"); 47 | netcdf_config 48 | .define("BUILD_SHARED_LIBS", "OFF") 49 | .define("NC_FIND_SHARED_LIBS", "OFF") 50 | .define("BUILD_UTILITIES", "OFF") 51 | .define("ENABLE_EXAMPLES", "OFF") 52 | .define("ENABLE_DAP_REMOTE_TESTS", "OFF") 53 | .define("ENABLE_TESTS", "OFF") 54 | .define("ENABLE_EXTREME_NUMBERS", "OFF") 55 | .define("ENABLE_PARALLEL_TESTS", "OFF") 56 | .define("ENABLE_FILTER_TESTING", "OFF") 57 | .define("ENABLE_BASH_SCRIPT_TESTING", "OFF") 58 | .define("ENABLE_PLUGINS", "OFF") 59 | .define("PLUGIN_INSTALL_DIR", "OFF") 60 | // 61 | .define("HDF5_VERSION", &hdf5_version) 62 | .define("HDF5_C_LIBRARY", &hdf5_lib) 63 | .define("HDF5_HL_LIBRARY", &hdf5_hl_lib) 64 | .define("HDF5_INCLUDE_DIR", hdf5_incdir) 65 | // 66 | .define("ENABLE_LIBXML2", "OFF") // Use bundled xml2 67 | // 68 | .define("ENABLE_PARALLEL4", "OFF") // TODO: Enable mpi support 69 | // 70 | .define("ENABLE_NCZARR", "OFF") // TODO: requires a bunch of deps 71 | // 72 | .define("ENABLE_DAP", "OFF") // TODO: feature flag, requires curl 73 | .define("ENABLE_BYTERANGE", "OFF") // TODO: feature flag, requires curl 74 | .define("ENABLE_DAP_REMOTE_TESTS", "OFF") 75 | // 76 | .profile("RelWithDebInfo"); // TODO: detect opt-level 77 | 78 | let zlib_include_dir = std::env::var("DEP_Z_INCLUDE").unwrap(); 79 | netcdf_config.define("ZLIB_ROOT", format!("{zlib_include_dir}/..")); 80 | 81 | if feature!("DAP").is_ok() { 82 | netcdf_config.define("ENABLE_DAP", "ON"); 83 | netcdf_config.define("ENABLE_BYTERANGE", "ON"); 84 | } 85 | 86 | let netcdf = netcdf_config.build(); 87 | 88 | println!("cargo:lib=netcdf"); 89 | let search_path = format!("{}/lib", netcdf.display()); 90 | if std::path::Path::new(&search_path).exists() { 91 | println!("cargo:search={}", search_path); 92 | } else { 93 | let search_path = format!("{}/lib64", netcdf.display()); 94 | println!("cargo:search={}", search_path); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /netcdf-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)] 2 | #![allow(clippy::type_complexity)] 3 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 4 | 5 | extern crate hdf5_sys; 6 | 7 | #[cfg(feature = "dap")] 8 | extern crate curl_sys; 9 | 10 | #[cfg(feature = "static")] 11 | extern crate netcdf_src; 12 | 13 | mod consts; 14 | mod functions; 15 | pub use consts::*; 16 | pub use functions::*; 17 | 18 | #[cfg(feature = "4.8.0")] 19 | mod dispatch; 20 | #[cfg(feature = "4.8.0")] 21 | pub use dispatch::*; 22 | 23 | #[cfg(feature = "has-mmap")] 24 | mod mmap; 25 | #[cfg(feature = "has-mmap")] 26 | pub use mmap::*; 27 | 28 | #[cfg(feature = "4.8.0")] 29 | mod filter; 30 | #[cfg(feature = "4.8.0")] 31 | pub use filter::*; 32 | 33 | use std::sync::Mutex; 34 | 35 | /// Global netCDF lock for using all functions in the netCDF library 36 | /// 37 | /// Per the NetCDF FAQ: "THE C-BASED LIBRARIES ARE NOT THREAD-SAFE" 38 | pub static libnetcdf_lock: Mutex<()> = Mutex::new(()); 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use std::env; 44 | use std::ffi; 45 | use std::path; 46 | 47 | #[test] 48 | fn test_nc_open_close() { 49 | let mnf_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 50 | let test_data_path = path::Path::new(&mnf_dir) 51 | .join("testdata") 52 | .join("simple_xy.nc"); 53 | let f = ffi::CString::new(test_data_path.to_str().unwrap()).unwrap(); 54 | 55 | let mut ncid: nc_type = -999_999; 56 | unsafe { 57 | let _g = libnetcdf_lock.lock().unwrap(); 58 | let err = nc_open(f.as_ptr(), NC_NOWRITE, &mut ncid); 59 | assert_eq!(err, NC_NOERR); 60 | let err = nc_close(ncid); 61 | assert_eq!(err, NC_NOERR); 62 | } 63 | } 64 | 65 | #[test] 66 | fn test_inq_varid() { 67 | let mnf_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 68 | let test_data_path = path::Path::new(&mnf_dir) 69 | .join("testdata") 70 | .join("simple_xy.nc"); 71 | let f = ffi::CString::new(test_data_path.to_str().unwrap()).unwrap(); 72 | let varname = ffi::CString::new("data").unwrap(); 73 | 74 | let mut ncid: nc_type = -999_999; 75 | let mut varid: nc_type = -999_999; 76 | let mut nvars: nc_type = -999_999; 77 | unsafe { 78 | let _g = libnetcdf_lock.lock().unwrap(); 79 | let err = nc_open(f.as_ptr(), NC_NOWRITE, &mut ncid); 80 | assert_eq!(err, NC_NOERR); 81 | let err = nc_inq_nvars(ncid, &mut nvars); 82 | assert_eq!(err, NC_NOERR); 83 | assert_eq!(nvars, 1); 84 | let err = nc_inq_varid(ncid, varname.as_ptr(), &mut varid); 85 | assert_eq!(err, NC_NOERR); 86 | let err = nc_close(ncid); 87 | assert_eq!(err, NC_NOERR); 88 | } 89 | } 90 | 91 | #[test] 92 | fn test_get_var() { 93 | let mnf_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 94 | let test_data_path = path::Path::new(&mnf_dir) 95 | .join("testdata") 96 | .join("simple_xy.nc"); 97 | let f = ffi::CString::new(test_data_path.to_str().unwrap()).unwrap(); 98 | let varname = ffi::CString::new("data").unwrap(); 99 | 100 | let mut ncid: nc_type = -999_999; 101 | let mut varid: nc_type = -999_999; 102 | let mut buf: Vec = vec![0; 6 * 12]; 103 | unsafe { 104 | let _g = libnetcdf_lock.lock().unwrap(); 105 | let err = nc_open(f.as_ptr(), NC_NOWRITE, &mut ncid); 106 | assert_eq!(err, NC_NOERR); 107 | 108 | let err = nc_inq_varid(ncid, varname.as_ptr(), &mut varid); 109 | assert_eq!(err, NC_NOERR); 110 | 111 | let err = nc_get_var_int(ncid, varid, buf.as_mut_ptr()); 112 | assert_eq!(err, NC_NOERR); 113 | 114 | let err = nc_close(ncid); 115 | assert_eq!(err, NC_NOERR); 116 | } 117 | 118 | for (x, d) in buf.into_iter().enumerate() { 119 | assert_eq!(d, x as _); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | schedule: 12 | - cron: '0 0 15 * *' 13 | 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | env: 20 | CARGO_TERM_COLOR: always 21 | 22 | jobs: 23 | rustfmt: 24 | name: rustfmt 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: {submodules: true} 30 | - name: Install netCDF 31 | run: sudo apt-get update && sudo apt-get install libnetcdf-dev 32 | - name: Install rust 33 | uses: dtolnay/rust-toolchain@stable 34 | with: 35 | toolchain: stable 36 | components: rustfmt, clippy 37 | - name: Check formatting 38 | run: cargo fmt -- --check 39 | - name: Documentation 40 | run: cargo doc --workspace 41 | - name: Clippy 42 | run: cargo clippy --workspace -- -D warnings 43 | 44 | test_apt: 45 | name: test apt 46 | runs-on: ubuntu-latest 47 | strategy: 48 | matrix: 49 | build: 50 | - stable 51 | - beta 52 | - nightly 53 | include: 54 | - build: stable 55 | rust: stable 56 | - build: beta 57 | rust: beta 58 | - build: nightly 59 | rust: nightly 60 | steps: 61 | - name: Checkout repository 62 | uses: actions/checkout@v4 63 | with: {submodules: false} 64 | 65 | - name: Install netcdf 66 | run: sudo apt-get update && sudo apt-get install libnetcdf-dev 67 | 68 | - name: Install rust 69 | uses: dtolnay/rust-toolchain@stable 70 | with: 71 | toolchain: ${{ matrix.rust }} 72 | 73 | - name: Build 74 | run: cargo build --verbose --workspace --exclude netcdf-src 75 | 76 | - name: Test 77 | run: cargo test --verbose --workspace --exclude netcdf-src 78 | 79 | conda: 80 | name: conda 81 | runs-on: ${{matrix.os}}-latest 82 | strategy: 83 | fail-fast: false 84 | matrix: 85 | include: 86 | - {os: ubuntu, channel: conda-forge, rust: stable} 87 | - {os: windows, channel: conda-forge, rust: stable} 88 | - {os: macos, channel: conda-forge, rust: stable} 89 | defaults: 90 | run: 91 | shell: bash -l {0} 92 | steps: 93 | - name: Checkout repository 94 | uses: actions/checkout@v4 95 | with: {submodules: false} 96 | - name: Install Rust (${{matrix.rust}}) 97 | uses: dtolnay/rust-toolchain@stable 98 | with: {toolchain: '${{matrix.rust}}'} 99 | - name: Install conda 100 | uses: conda-incubator/setup-miniconda@v3 101 | with: {auto-update-conda: false, activate-environment: testenv} 102 | - name: Install netCDF 103 | run: conda install -y -c ${{matrix.channel}} libnetcdf=4.8.1 104 | - name: Build and test 105 | run: | 106 | export HDF5_DIR="$CONDA_PREFIX" 107 | export NETCDF_DIR="$CONDA_PREFIX" 108 | [ "${{runner.os}}" != "Windows" ] && export RUSTFLAGS="-C link-args=-Wl,-rpath,$CONDA_PREFIX/lib" 109 | cargo test -vv --workspace --exclude netcdf-src 110 | 111 | static_builds: 112 | name: static builds 113 | runs-on: ${{matrix.os}} 114 | strategy: 115 | fail-fast: false 116 | matrix: 117 | include: 118 | - {os: ubuntu-latest, rust: stable} 119 | - {os: windows-latest, rust: stable-msvc} 120 | - {os: windows-latest, rust: stable-gnu} 121 | - {os: macos-11, rust: stable} 122 | - {os: macos-latest, rust: stable} 123 | defaults: 124 | run: 125 | shell: bash -l {0} 126 | steps: 127 | - name: Checkout repository 128 | uses: actions/checkout@v4 129 | with: {submodules: true} 130 | - name: Install Rust (${{matrix.rust}}) 131 | uses: dtolnay/rust-toolchain@stable 132 | with: {toolchain: '${{matrix.rust}}'} 133 | - name: Build and test 134 | run: cargo test -vv --features static --workspace 135 | 136 | addr_san: 137 | name: Address sanitizer 138 | runs-on: ubuntu-latest 139 | steps: 140 | - name: Checkout repository 141 | uses: actions/checkout@v4 142 | with: {submodules: true} 143 | - name: Install Rust 144 | uses: dtolnay/rust-toolchain@stable 145 | with: {toolchain: nightly} 146 | - name: Run test with sanitizer 147 | env: 148 | RUSTFLAGS: "-Z sanitizer=address" 149 | RUSTDOCFLAGS: "-Z sanitizer=address" 150 | run: cargo test --features netcdf-sys/static --target x86_64-unknown-linux-gnu --workspace 151 | -------------------------------------------------------------------------------- /netcdf/examples/ncdump.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(Debug, Parser)] 4 | struct Opt { 5 | path: std::path::PathBuf, 6 | } 7 | 8 | fn main() { 9 | let opt = Opt::parse(); 10 | 11 | match run(&opt.path) { 12 | Err(e) => { 13 | println!("{}", e); 14 | std::process::exit(1); 15 | } 16 | Ok(()) => { 17 | std::process::exit(0); 18 | } 19 | } 20 | } 21 | 22 | fn run(path: &std::path::Path) -> Result<(), Box> { 23 | let file = netcdf::open(path)?; 24 | 25 | println!("{}", file.path()?.to_str().unwrap()); 26 | print_file(&file) 27 | } 28 | 29 | fn print_file(g: &netcdf::File) -> Result<(), Box> { 30 | let mut dims = g.dimensions().peekable(); 31 | if dims.peek().is_some() { 32 | println!("Dimensions:"); 33 | for d in dims { 34 | if d.is_unlimited() { 35 | println!("\t{} : Unlimited ({})", d.name(), d.len()); 36 | } else { 37 | println!("\t{} : ({})", d.name(), d.len()); 38 | } 39 | } 40 | } 41 | let mut types = g.types()?.peekable(); 42 | if types.peek().is_some() { 43 | println!("Types:"); 44 | for t in types { 45 | use netcdf::types::VariableType; 46 | print!("\t{}: ", t.name()); 47 | match t { 48 | VariableType::Basic(_) | VariableType::String => unreachable!(), 49 | VariableType::Opaque(o) => println!("Opaque({})", o.size()), 50 | VariableType::Enum(_) => println!("Enum"), 51 | VariableType::Vlen(v) => println!("Vlen({})", v.typ().name()), 52 | VariableType::Compound(c) => { 53 | print!("Compound({{"); 54 | for field in c.fields() { 55 | print!(" {}: {} ", field.name(), field.typ().name()); 56 | } 57 | println!("}})"); 58 | } 59 | } 60 | } 61 | } 62 | let mut variables = g.variables().peekable(); 63 | if variables.peek().is_some() { 64 | println!("Variables:"); 65 | for v in variables { 66 | print!("\t{}", v.name()); 67 | print!("("); 68 | for d in v.dimensions() { 69 | print!(" {} ", d.name()); 70 | } 71 | println!("): {}", v.vartype().name()); 72 | for a in v.attributes() { 73 | println!("\t\t{} = {:?}", a.name(), a.value()?); 74 | } 75 | } 76 | } 77 | let mut attributes = g.attributes().peekable(); 78 | if attributes.peek().is_some() { 79 | println!("Attributes:"); 80 | for a in g.attributes() { 81 | println!("\t\t{} = {:?}", a.name(), a.value()?); 82 | } 83 | } 84 | if let Some(g) = g.root() { 85 | for g in g.groups() { 86 | println!(); 87 | print_group(&g)?; 88 | } 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | fn print_group(g: &netcdf::Group) -> Result<(), Box> { 95 | println!("Group: {}", g.name()); 96 | 97 | let mut dims = g.dimensions().peekable(); 98 | if dims.peek().is_some() { 99 | println!("Dimensions:"); 100 | for d in dims { 101 | if d.is_unlimited() { 102 | println!("\t{} : Unlimited ({})", d.name(), d.len()); 103 | } else { 104 | println!("\t{} : ({})", d.name(), d.len()); 105 | } 106 | } 107 | } 108 | let mut types = g.types().peekable(); 109 | if types.peek().is_some() { 110 | println!("Types:"); 111 | for t in types { 112 | use netcdf::types::VariableType; 113 | print!("\t{}: ", t.name()); 114 | match t { 115 | VariableType::Basic(_) | VariableType::String => unreachable!(), 116 | VariableType::Opaque(o) => println!("Opaque({})", o.size()), 117 | VariableType::Enum(_) => println!("Enum"), 118 | VariableType::Vlen(v) => println!("Vlen({})", v.typ().name()), 119 | VariableType::Compound(c) => { 120 | print!("Compound({{"); 121 | for field in c.fields() { 122 | print!(" {}: {} ", field.name(), field.typ().name()); 123 | } 124 | println!("}})"); 125 | } 126 | } 127 | } 128 | } 129 | 130 | let mut variables = g.variables().peekable(); 131 | if variables.peek().is_some() { 132 | println!("Variables:"); 133 | for v in variables { 134 | print!("\t{}", v.name()); 135 | print!("("); 136 | for d in v.dimensions() { 137 | print!(" {} ", d.name()); 138 | } 139 | println!("): {}", v.vartype().name()); 140 | for a in v.attributes() { 141 | println!("\t\t{} = {:?}", a.name(), a.value()?); 142 | } 143 | } 144 | } 145 | let mut attributes = g.attributes().peekable(); 146 | if attributes.peek().is_some() { 147 | println!("Attributes:"); 148 | for a in g.attributes() { 149 | println!("\t\t{} = {:?}", a.name(), a.value()?); 150 | } 151 | } 152 | for g in g.groups() { 153 | println!(); 154 | print_group(&g)?; 155 | } 156 | 157 | Ok(()) 158 | } 159 | -------------------------------------------------------------------------------- /netcdf/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Errors that can appear when interacting with netcdf files. 2 | //! This module contains conversion traits and the result type 3 | //! used in this crate. 4 | #![allow(clippy::similar_names)] 5 | 6 | use std::num::TryFromIntError; 7 | 8 | use netcdf_sys::nc_strerror; 9 | 10 | use super::nc_type; 11 | 12 | /// Various error types that can occur in this crate 13 | #[derive(Debug)] 14 | pub enum Error { 15 | /// Errors from the wrapped netcdf library 16 | Netcdf(nc_type), 17 | /// Misc errors 18 | Str(String), 19 | /// Length of the request indices is inconsistent 20 | IndexLen, 21 | /// Length of the slice indices is inconsistent 22 | SliceLen, 23 | /// Supplied the wrong length of the buffer 24 | BufferLen { 25 | /// Wanted size of the buffer 26 | wanted: usize, 27 | /// Actual size of the buffer 28 | actual: usize, 29 | }, 30 | /// Supplied the wrong length of dimension 31 | DimensionMismatch { 32 | /// Wanted size of the dimension 33 | wanted: usize, 34 | /// Actual size of the dimension 35 | actual: usize, 36 | }, 37 | /// Some index is greater than expected 38 | IndexMismatch, 39 | /// Requested a mismatched total slice 40 | SliceMismatch, 41 | /// Requested a zero slice 42 | ZeroSlice, 43 | /// Zero stride or matched with length != 1 44 | Stride, 45 | /// Supplied the wrong type of parameter 46 | TypeMismatch, 47 | /// Does not know the type (probably library error...) 48 | TypeUnknown(nc_type), 49 | /// Variable/dimension already exists 50 | AlreadyExists, 51 | /// Could not find variable/attribute/etc 52 | NotFound(String), 53 | /// Slice lengths are ambiguous 54 | Ambiguous, 55 | /// Overflows possible lengths 56 | Overflow, 57 | /// Conversion error 58 | Conversion(TryFromIntError), 59 | /// Identifier belongs to another dataset 60 | WrongDataset, 61 | /// Name is not valid utf-8 62 | Utf8Conversion(std::string::FromUtf8Error), 63 | /// String contains NULL characters 64 | NulError(std::ffi::NulError), 65 | } 66 | 67 | impl Error { 68 | /// Was the error due to ambiguity of the 69 | /// indices or lengths? 70 | pub fn is_ambigous(&self) -> bool { 71 | matches!(self, Self::Ambiguous) 72 | } 73 | } 74 | 75 | impl std::error::Error for Error { 76 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 77 | None 78 | } 79 | } 80 | 81 | impl From<&str> for Error { 82 | fn from(s: &str) -> Self { 83 | Self::Str(s.into()) 84 | } 85 | } 86 | 87 | impl From for Error { 88 | fn from(s: String) -> Self { 89 | Self::Str(s) 90 | } 91 | } 92 | 93 | impl From for Error { 94 | fn from(nc: nc_type) -> Self { 95 | if nc == netcdf_sys::NC_EEXIST 96 | || nc == netcdf_sys::NC_EATTEXISTS 97 | || nc == netcdf_sys::NC_ENAMEINUSE 98 | { 99 | Self::AlreadyExists 100 | } else { 101 | Self::Netcdf(nc) 102 | } 103 | } 104 | } 105 | 106 | impl From for Error { 107 | fn from(e: TryFromIntError) -> Self { 108 | Self::Conversion(e) 109 | } 110 | } 111 | 112 | impl From for Error { 113 | fn from(e: std::string::FromUtf8Error) -> Self { 114 | Self::Utf8Conversion(e) 115 | } 116 | } 117 | 118 | impl From for Error { 119 | fn from(e: std::ffi::NulError) -> Self { 120 | Self::NulError(e) 121 | } 122 | } 123 | 124 | impl From for Error { 125 | fn from(_: std::convert::Infallible) -> Self { 126 | unreachable!("Infallible error can never be constructed") 127 | } 128 | } 129 | 130 | use std::fmt; 131 | impl fmt::Display for Error { 132 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 133 | match self { 134 | Self::Str(x) => x.fmt(f), 135 | Self::IndexLen => write!(f, "indices does not match in length with the variable"), 136 | Self::SliceLen => write!(f, "slices does not match in length with the variable"), 137 | Self::IndexMismatch => write!(f, "requested index is bigger than the dimension length"), 138 | Self::SliceMismatch => write!(f, "requested slice is bigger than the dimension length"), 139 | Self::DimensionMismatch { wanted, actual } => write!( 140 | f, 141 | "requested dimension ({actual}) is bigger than the dimension length ({wanted})" 142 | ), 143 | Self::ZeroSlice => write!(f, "must request a slice length larger than zero"), 144 | Self::Stride => write!(f, "invalid strides"), 145 | Self::BufferLen { wanted, actual } => write!( 146 | f, 147 | "buffer size mismatch, has size {actual}, but needs size {wanted}", 148 | ), 149 | Self::TypeMismatch => write!(f, "netcdf types does not correspond to what is defined"), 150 | Self::TypeUnknown(t) => write!(f, "netcdf type {t} is not known"), 151 | Self::AlreadyExists => write!(f, "variable/group/dimension already exists"), 152 | Self::NotFound(x) => write!(f, "could not find {x}"), 153 | Self::Netcdf(x) => { 154 | let msg; 155 | unsafe { 156 | // Threadsafe 157 | let cmsg = nc_strerror(*x); 158 | msg = std::ffi::CStr::from_ptr(cmsg); 159 | } 160 | 161 | write!(f, "netcdf error({}): {}", x, msg.to_string_lossy()) 162 | } 163 | Self::Ambiguous => write!(f, "could not find an appropriate length of the slices"), 164 | Self::Overflow => write!(f, "slice would exceed maximum size of possible buffers"), 165 | Self::Conversion(e) => e.fmt(f), 166 | Self::WrongDataset => write!(f, "this identifier does not belong in this dataset"), 167 | Self::Utf8Conversion(e) => e.fmt(f), 168 | Self::NulError(e) => write!(f, "string value contains null bytes {e}"), 169 | } 170 | } 171 | } 172 | 173 | /// Result type used in this crate 174 | pub type Result = std::result::Result; 175 | 176 | pub(crate) fn checked(err: nc_type) -> Result<()> { 177 | if err != netcdf_sys::NC_NOERR { 178 | return Err(err.into()); 179 | } 180 | Ok(()) 181 | } 182 | -------------------------------------------------------------------------------- /netcdf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust bindings for Unidata's [libnetcdf](http://www.unidata.ucar.edu/software/netcdf/) 2 | //! 3 | //! This crate allows one to store and retrieve multi-dimensional arrays from a 4 | //! `netCDF` supported format, which can be a `netCDF` file, a subset of `hdf5` files, 5 | //! or from a DAP url. 6 | //! 7 | //! 8 | //! `netCDF` files are self-contained, they contain metadata about the data contained in them. 9 | //! See the [`CF Conventions`](http://cfconventions.org/) for conventions used for climate and 10 | //! forecast models. 11 | //! 12 | //! To explore the documentation, see the [`Functions`](#functions) section, in particular 13 | //! [`open()`](open), [`create()`](create), and [`append()`](append). 14 | //! 15 | //! For more information see: 16 | //! * [The official introduction to `netCDF`](https://docs.unidata.ucar.edu/nug/current/netcdf_introduction.html) 17 | //! * [The `netCDF-c` repository](https://github.com/Unidata/netcdf-c) 18 | //! 19 | //! # Examples 20 | //! 21 | //! How to read a variable from a file: 22 | //! 23 | //! ```no_run 24 | //! # fn main() -> Result<(), Box> { 25 | //! // Open the file `simple_xy.nc`: 26 | //! let file = netcdf::open("simple_xy.nc")?; 27 | //! 28 | //! // Get the variable in this file with the name "data" 29 | //! let var = &file.variable("data").expect("Could not find variable 'data'"); 30 | //! 31 | //! // Read a single datapoint from a 1D variable as a numeric type 32 | //! let data_i32 = var.get_value::(4)?; 33 | //! let data_f32 : f32 = var.get_value(5)?; 34 | //! 35 | //! // If your variable is multi-dimensional you need to use a 36 | //! // type that supports `Selection`, such as a tuple or array 37 | //! let data_i32 = var.get_value::([40, 0, 0])?; 38 | //! let data_i32 = var.get_value::((40, 0, 0))?; 39 | //! 40 | //! // You can use `values_arr()` to get all the data from the variable. 41 | //! // This requires the `ndarray` feature 42 | //! // Passing `..` will give you the entire slice 43 | //! # #[cfg(feature = "ndarray")] 44 | //! let data = var.get::(..)?; 45 | //! 46 | //! // A subset can also be selected, the following will extract the slice at 47 | //! // `(40, 0, 0)` and get a dataset of size `100, 100` from this 48 | //! # #[cfg(feature = "ndarray")] 49 | //! let data = var.get::(([40, 0 ,0], [1, 100, 100]))?; 50 | //! # #[cfg(feature = "ndarray")] 51 | //! let data = var.get::((40, ..100, ..100))?; 52 | //! 53 | //! // You can read into an ndarray to reuse an allocation 54 | //! # #[cfg(feature = "ndarray")] 55 | //! let mut data = ndarray::Array::::zeros((100, 100)); 56 | //! # #[cfg(feature = "ndarray")] 57 | //! var.get_into((0, .., ..), data.view_mut())?; 58 | //! # Ok(()) } 59 | //! ``` 60 | //! 61 | //! How to create a new file and write to it: 62 | //! 63 | //! ```no_run 64 | //! # fn main() -> Result<(), Box> { 65 | //! // Create a new file with default settings 66 | //! let mut file = netcdf::create("crabs.nc")?; 67 | //! 68 | //! // We must create a dimension which corresponds to our data 69 | //! file.add_dimension("ncrabs", 10)?; 70 | //! // These dimensions can also be unlimited and will be resized when writing 71 | //! file.add_unlimited_dimension("time")?; 72 | //! 73 | //! // A variable can now be declared, and must be created from the dimension names. 74 | //! let mut var = file.add_variable::( 75 | //! "crab_coolness_level", 76 | //! &["time", "ncrabs"], 77 | //! )?; 78 | //! // Metadata can be added to the variable, but will not be used when 79 | //! // writing or reading data 80 | //! var.put_attribute("units", "Kelvin")?; 81 | //! var.put_attribute("add_offset", 273.15_f32)?; 82 | //! 83 | //! // Data can then be created and added to the variable 84 | //! let data : Vec = vec![42; 10]; 85 | //! var.put_values(&data, (0, ..))?; 86 | //! 87 | //! // Values can be added along the unlimited dimension, which 88 | //! // resizes along the `time` axis 89 | //! var.put_values(&data, (11, ..))?; 90 | //! 91 | //! // Using the ndarray feature you can also use 92 | //! # #[cfg(feature = "ndarray")] 93 | //! let values = ndarray::Array::from_shape_fn((5, 10), |(j, i)| (j * 10 + i) as f32); 94 | //! # #[cfg(feature = "ndarray")] 95 | //! var.put((11.., ..), values.view())?; 96 | //! # Ok(()) } 97 | //! ``` 98 | 99 | #![warn(missing_docs)] 100 | #![allow(clippy::must_use_candidate)] 101 | #![allow(clippy::missing_errors_doc)] 102 | #![allow(clippy::wildcard_imports)] 103 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 104 | 105 | use netcdf_sys::nc_type; 106 | 107 | pub(crate) mod attribute; 108 | pub(crate) mod dimension; 109 | pub(crate) mod error; 110 | pub(crate) mod extent; 111 | pub(crate) mod file; 112 | pub(crate) mod group; 113 | #[cfg(feature = "4.9.2")] 114 | pub mod rc; 115 | pub mod types; 116 | pub(crate) mod variable; 117 | 118 | pub use attribute::{Attribute, AttributeValue}; 119 | pub use dimension::{Dimension, DimensionIdentifier}; 120 | pub use error::{Error, Result}; 121 | pub use extent::{Extent, Extents}; 122 | #[cfg(feature = "has-mmap")] 123 | pub use file::FileMem; 124 | pub(crate) use file::RawFile; 125 | pub use file::{File, FileMut, Options}; 126 | pub use group::{Group, GroupMut}; 127 | pub use variable::{Endianness, NcPutGet, Variable, VariableMut}; 128 | 129 | /// Open a netcdf file in create mode 130 | /// 131 | /// Will create a `netCDF4` file and overwrite existing file 132 | pub fn create

(name: P) -> error::Result 133 | where 134 | P: AsRef, 135 | { 136 | RawFile::create_with(name.as_ref(), Options::NETCDF4) 137 | } 138 | 139 | /// Open a `netCDF` file in create mode with the given options 140 | pub fn create_with

(name: P, options: Options) -> error::Result 141 | where 142 | P: AsRef, 143 | { 144 | RawFile::create_with(name.as_ref(), options) 145 | } 146 | 147 | /// Open a `netCDF` file in append mode 148 | pub fn append

(name: P) -> error::Result 149 | where 150 | P: AsRef, 151 | { 152 | append_with(name, Options::default()) 153 | } 154 | 155 | /// Open a `netCDF` file in append mode with the given options 156 | pub fn append_with

(name: P, options: Options) -> error::Result 157 | where 158 | P: AsRef, 159 | { 160 | RawFile::append_with(name.as_ref(), options) 161 | } 162 | 163 | /// Open a `netCDF` file in read mode 164 | pub fn open

(name: P) -> error::Result 165 | where 166 | P: AsRef, 167 | { 168 | open_with(name, Options::default()) 169 | } 170 | 171 | /// Open a `netCDF` file in read mode with the given options 172 | pub fn open_with

(name: P, options: Options) -> error::Result 173 | where 174 | P: AsRef, 175 | { 176 | RawFile::open_with(name.as_ref(), options) 177 | } 178 | 179 | #[cfg(feature = "has-mmap")] 180 | /// Open a `netCDF` file from a buffer 181 | pub fn open_mem<'a>(name: Option<&str>, mem: &'a [u8]) -> error::Result> { 182 | RawFile::open_from_memory(name, mem) 183 | } 184 | 185 | /// All functions should be wrapped in this locker. Disregarding this, expect 186 | /// segfaults, especially on non-threadsafe hdf5 builds 187 | pub(crate) fn with_lock nc_type>(mut f: F) -> nc_type { 188 | let _l = netcdf_sys::libnetcdf_lock.lock().unwrap(); 189 | f() 190 | } 191 | 192 | pub(crate) mod utils { 193 | use super::error; 194 | use netcdf_sys::{NC_EMAXNAME, NC_MAX_NAME}; 195 | /// Use this function for short `netCDF` names to avoid the allocation 196 | /// for a `CString` 197 | pub(crate) fn short_name_to_bytes(name: &str) -> error::Result<[u8; NC_MAX_NAME as usize + 1]> { 198 | if name.len() > NC_MAX_NAME as _ { 199 | Err(NC_EMAXNAME.into()) 200 | } else { 201 | let len = name.bytes().position(|x| x == 0).unwrap_or(name.len()); 202 | let mut bytes = [0_u8; NC_MAX_NAME as usize + 1]; 203 | bytes[..len].copy_from_slice(name.as_bytes()); 204 | Ok(bytes) 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /netcdf/tests/attributes.rs: -------------------------------------------------------------------------------- 1 | use netcdf::AttributeValue; 2 | 3 | mod common; 4 | use common::test_location; 5 | 6 | #[test] 7 | fn attributes_read() { 8 | let f = test_location().join("patmosx_v05r03-preliminary_NOAA-19_asc_d20130630_c20140325.nc"); 9 | let file = netcdf::open(&f).unwrap(); 10 | 11 | let attr = &file 12 | .attribute("PROGLANG") 13 | .expect("Could not find attribute"); 14 | 15 | assert_eq!(attr.name(), "PROGLANG"); 16 | 17 | for attr in file.attributes() { 18 | let _val = attr.value().expect("Could not get value"); 19 | } 20 | 21 | let d = tempfile::tempdir().expect("Could not get tempdir"); 22 | let path = d.path().join("attributes_read.nc"); 23 | let mut file = netcdf::create(path).expect("Could not create file"); 24 | 25 | let var = &mut file 26 | .add_variable::("var", &[]) 27 | .expect("Could not add variable"); 28 | var.put_attribute("att", "some attribute") 29 | .expect("Could not add attribute"); 30 | assert!(var.vartype().as_basic().unwrap().is_f32()); 31 | 32 | for attr in var.attributes() { 33 | attr.value().unwrap(); 34 | } 35 | } 36 | 37 | #[test] 38 | /// Making sure attributes are updated correctly (replacing previous value) 39 | fn attribute_put() { 40 | let d = tempfile::tempdir().expect("Could not create tempdir"); 41 | let p = d.path().join("attribute_put.nc"); 42 | let mut f = netcdf::create(p).unwrap(); 43 | 44 | f.add_attribute("a", "1").unwrap(); 45 | assert_eq!(f.attribute("a").unwrap().value().unwrap(), "1".into()); 46 | f.add_attribute("b", "2").unwrap(); 47 | assert_eq!(f.attribute("b").unwrap().value().unwrap(), "2".into()); 48 | f.add_attribute("a", 2u32).unwrap(); 49 | assert_eq!(f.attribute("a").unwrap().value().unwrap(), 2u32.into()); 50 | f.add_attribute("b", "2").unwrap(); 51 | assert_eq!(f.attribute("b").unwrap().value().unwrap(), "2".into()); 52 | } 53 | #[test] 54 | #[cfg(feature = "ndarray")] 55 | fn open_pres_temp_4d() { 56 | let f = test_location().join("pres_temp_4D.nc"); 57 | 58 | let file = netcdf::open(f).unwrap(); 59 | 60 | let pres = &file.variable("pressure").unwrap(); 61 | assert_eq!(pres.dimensions()[0].name(), "time"); 62 | assert_eq!(pres.dimensions()[1].name(), "level"); 63 | assert_eq!(pres.dimensions()[2].name(), "latitude"); 64 | assert_eq!(pres.dimensions()[3].name(), "longitude"); 65 | 66 | // test var attributes 67 | assert_eq!( 68 | pres.attribute("units") 69 | .expect("Could not find attribute") 70 | .value() 71 | .unwrap(), 72 | AttributeValue::Str("hPa".to_string()) 73 | ); 74 | } 75 | #[test] 76 | fn global_attrs() { 77 | let f = test_location().join("patmosx_v05r03-preliminary_NOAA-19_asc_d20130630_c20140325.nc"); 78 | 79 | let file = netcdf::open(f).unwrap(); 80 | 81 | let ch1_attr = &file 82 | .attribute("CH1_DARK_COUNT") 83 | .expect("Could not find attribute"); 84 | let chi = ch1_attr.value().unwrap(); 85 | let eps = 1e-6; 86 | if let AttributeValue::Float(x) = chi { 87 | assert!((x - 40.65863).abs() < eps); 88 | } else { 89 | panic!("Did not get the expected attr type"); 90 | } 91 | 92 | let sensor_attr = &file.attribute("sensor").expect("Could not find attribute"); 93 | let sensor_data = sensor_attr.value().unwrap(); 94 | if let AttributeValue::Str(x) = sensor_data { 95 | assert_eq!("AVHRR/3", x); 96 | } else { 97 | panic!("Did not get the expected attr type"); 98 | } 99 | } 100 | #[test] 101 | #[allow(clippy::unnecessary_cast)] 102 | fn all_attr_types() { 103 | let d = tempfile::tempdir().unwrap(); 104 | let u8string = "Testing utf8 with æøå and even 😀"; 105 | let strs = vec!["Hi!".to_string(), "Hello!".to_string()]; 106 | { 107 | let f = d.path().join("all_attr_types.nc"); 108 | let mut file = netcdf::create(f).unwrap(); 109 | 110 | file.add_attribute("attr_byte", 3 as i8).unwrap(); 111 | file.add_attribute("attr_ubyte", 3 as u8).unwrap(); 112 | file.add_attribute("attr_short", 3 as i16).unwrap(); 113 | file.add_attribute("attr_ushort", 3 as u16).unwrap(); 114 | file.add_attribute("attr_int", 3 as i32).unwrap(); 115 | file.add_attribute("attr_uint", 3 as u32).unwrap(); 116 | file.add_attribute("attr_int64", 3 as i64).unwrap(); 117 | file.add_attribute("attr_uint64", 3 as u64).unwrap(); 118 | file.add_attribute("attr_float", 3.2 as f32).unwrap(); 119 | file.add_attribute("attr_double", 3.2 as f64).unwrap(); 120 | file.add_attribute("attr_text", "Hello world!").unwrap(); 121 | file.add_attribute("attr_str", strs.clone()).unwrap(); 122 | file.add_attribute("attr_str_slice", strs.as_slice()) 123 | .unwrap(); 124 | 125 | file.add_attribute("attr_text_utf8", u8string).unwrap(); 126 | } 127 | 128 | { 129 | let f = d.path().join("all_attr_types.nc"); 130 | let file = netcdf::open(f).unwrap(); 131 | 132 | assert_eq!( 133 | AttributeValue::Uchar(3), 134 | file.attribute("attr_ubyte").unwrap().value().unwrap() 135 | ); 136 | assert_eq!( 137 | AttributeValue::Schar(3), 138 | file.attribute("attr_byte").unwrap().value().unwrap() 139 | ); 140 | assert_eq!( 141 | AttributeValue::Ushort(3), 142 | file.attribute("attr_ushort").unwrap().value().unwrap() 143 | ); 144 | assert_eq!( 145 | AttributeValue::Short(3), 146 | file.attribute("attr_short").unwrap().value().unwrap() 147 | ); 148 | assert_eq!( 149 | AttributeValue::Int(3), 150 | file.attribute("attr_int").unwrap().value().unwrap() 151 | ); 152 | assert_eq!( 153 | AttributeValue::Uint(3), 154 | file.attribute("attr_uint").unwrap().value().unwrap() 155 | ); 156 | assert_eq!( 157 | AttributeValue::Ulonglong(3), 158 | file.attribute("attr_uint64").unwrap().value().unwrap() 159 | ); 160 | assert_eq!( 161 | AttributeValue::Longlong(3), 162 | file.attribute("attr_int64").unwrap().value().unwrap() 163 | ); 164 | assert_eq!( 165 | AttributeValue::Float(3.2), 166 | file.attribute("attr_float").unwrap().value().unwrap() 167 | ); 168 | assert_eq!( 169 | AttributeValue::Double(3.2), 170 | file.attribute("attr_double").unwrap().value().unwrap() 171 | ); 172 | assert_eq!( 173 | AttributeValue::Str("Hello world!".into()), 174 | file.attribute("attr_text").unwrap().value().unwrap() 175 | ); 176 | assert_eq!( 177 | AttributeValue::Strs(strs.clone()), 178 | file.attribute("attr_str").unwrap().value().unwrap() 179 | ); 180 | assert_eq!( 181 | AttributeValue::Strs(strs), 182 | file.attribute("attr_str_slice").unwrap().value().unwrap() 183 | ); 184 | assert_eq!( 185 | AttributeValue::Str(u8string.into()), 186 | file.attribute("attr_text_utf8").unwrap().value().unwrap() 187 | ); 188 | } 189 | } 190 | 191 | #[test] 192 | fn multi_attributes() { 193 | let d = tempfile::tempdir().unwrap(); 194 | let path = d.path().join("multi_attributes"); 195 | { 196 | let mut file = netcdf::create(&path).unwrap(); 197 | file.add_attribute("u8s", vec![1_u8, 2, 3, 4]).unwrap(); 198 | file.add_attribute("i8s", vec![1_i8, 2, 3, 4]).unwrap(); 199 | file.add_attribute("u16s", vec![1_u16, 2, 3, 4]).unwrap(); 200 | file.add_attribute("i16s", vec![1_i16, 2, 3, 4]).unwrap(); 201 | file.add_attribute("u32s", vec![1_u32, 2, 3, 4]).unwrap(); 202 | file.add_attribute("i32s", vec![1_i32, 2, 3, 4]).unwrap(); 203 | file.add_attribute("u64s", vec![1_u64, 2, 3, 4]).unwrap(); 204 | file.add_attribute("i64s", vec![1_i64, 2, 3, 4]).unwrap(); 205 | file.add_attribute("f32s", vec![1.0_f32, 2.0, 3.0, 4.0]) 206 | .unwrap(); 207 | file.add_attribute("f64s", vec![1.0_f64, 2.0, 3.0, 4.0]) 208 | .unwrap(); 209 | } 210 | let file = netcdf::open(path).unwrap(); 211 | let mut atts = 0; 212 | for att in file.attributes() { 213 | match att.name() { 214 | "u8s" => { 215 | assert_eq!(att.value().unwrap(), vec![1_u8, 2, 3, 4].into()); 216 | } 217 | "i8s" => { 218 | assert_eq!(att.value().unwrap(), vec![1_i8, 2, 3, 4].into()); 219 | } 220 | "u16s" => { 221 | assert_eq!(att.value().unwrap(), vec![1_u16, 2, 3, 4].into()); 222 | } 223 | "i16s" => { 224 | assert_eq!(att.value().unwrap(), vec![1_i16, 2, 3, 4].into()); 225 | } 226 | "u32s" => { 227 | assert_eq!(att.value().unwrap(), vec![1_u32, 2, 3, 4].into()); 228 | } 229 | "i32s" => { 230 | assert_eq!(att.value().unwrap(), vec![1_i32, 2, 3, 4].into()); 231 | } 232 | "u64s" => { 233 | assert_eq!(att.value().unwrap(), vec![1_u64, 2, 3, 4].into()); 234 | } 235 | "i64s" => { 236 | assert_eq!(att.value().unwrap(), vec![1_i64, 2, 3, 4].into()); 237 | } 238 | "f32s" => { 239 | assert_eq!(att.value().unwrap(), vec![1.0_f32, 2.0, 3.0, 4.0].into()); 240 | } 241 | "f64s" => { 242 | assert_eq!(att.value().unwrap(), vec![1.0_f64, 2.0, 3.0, 4.0].into()); 243 | } 244 | name => panic!("{} not covered", name), 245 | } 246 | atts += 1; 247 | } 248 | assert_eq!(atts, 10); 249 | } 250 | -------------------------------------------------------------------------------- /netcdf/src/dimension.rs: -------------------------------------------------------------------------------- 1 | //! Interact with netcdf dimensions 2 | #![allow(clippy::similar_names)] 3 | 4 | use std::marker::PhantomData; 5 | 6 | use netcdf_sys::*; 7 | 8 | use super::error; 9 | 10 | /// Represents a netcdf dimension 11 | #[derive(Debug, Clone)] 12 | pub struct Dimension<'g> { 13 | /// None when unlimited (size = 0) 14 | pub(crate) len: Option, 15 | pub(crate) id: DimensionIdentifier, 16 | pub(crate) _group: PhantomData<&'g nc_type>, 17 | } 18 | 19 | /// Unique identifier for a dimension in a file. Used when 20 | /// names can not be used directly, for example when dealing 21 | /// with nested groups 22 | #[derive(Debug, Copy, Clone)] 23 | pub struct DimensionIdentifier { 24 | pub(crate) ncid: nc_type, 25 | pub(crate) dimid: nc_type, 26 | } 27 | 28 | #[allow(clippy::len_without_is_empty)] 29 | impl<'g> Dimension<'g> { 30 | /// Get current length of this dimension 31 | pub fn len(&self) -> usize { 32 | if let Some(x) = self.len { 33 | x.get() 34 | } else { 35 | let mut len = 0; 36 | let err = unsafe { 37 | // Must lock in case other variables adds to the dimension length 38 | error::checked(super::with_lock(|| { 39 | nc_inq_dimlen(self.id.ncid, self.id.dimid, &mut len) 40 | })) 41 | }; 42 | 43 | // Should log or handle this somehow... 44 | err.map(|_| len).unwrap_or(0) 45 | } 46 | } 47 | 48 | /// Checks whether the dimension is growable 49 | pub fn is_unlimited(&self) -> bool { 50 | self.len.is_none() 51 | } 52 | 53 | /// Gets the name of the dimension 54 | pub fn name(&self) -> String { 55 | let mut name = vec![0_u8; NC_MAX_NAME as usize + 1]; 56 | unsafe { 57 | error::checked(super::with_lock(|| { 58 | nc_inq_dimname(self.id.ncid, self.id.dimid, name.as_mut_ptr().cast()) 59 | })) 60 | .unwrap(); 61 | } 62 | 63 | let zeropos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 64 | name.resize(zeropos, 0); 65 | String::from_utf8(name).expect("Dimension did not have a valid name") 66 | } 67 | 68 | /// Grabs the unique identifier for this dimension, which 69 | /// can be used in `add_variable_from_identifiers` 70 | pub fn identifier(&self) -> DimensionIdentifier { 71 | self.id 72 | } 73 | } 74 | 75 | pub(crate) fn from_name_toid(loc: nc_type, name: &str) -> error::Result> { 76 | let mut dimid = 0; 77 | let cname = super::utils::short_name_to_bytes(name)?; 78 | let e = unsafe { super::with_lock(|| nc_inq_dimid(loc, cname.as_ptr().cast(), &mut dimid)) }; 79 | if e == NC_EBADDIM { 80 | return Ok(None); 81 | } 82 | error::checked(e)?; 83 | 84 | Ok(Some(dimid)) 85 | } 86 | 87 | pub(crate) fn from_name<'f>(loc: nc_type, name: &str) -> error::Result>> { 88 | let mut dimid = 0; 89 | let cname = super::utils::short_name_to_bytes(name)?; 90 | let e = unsafe { super::with_lock(|| nc_inq_dimid(loc, cname.as_ptr().cast(), &mut dimid)) }; 91 | if e == NC_EBADDIM { 92 | return Ok(None); 93 | } 94 | error::checked(e)?; 95 | 96 | let mut dimlen = 0; 97 | unsafe { 98 | error::checked(super::with_lock(|| nc_inq_dimlen(loc, dimid, &mut dimlen)))?; 99 | } 100 | if dimlen != 0 { 101 | let mut nunlim = 0; 102 | unsafe { 103 | error::checked(super::with_lock(|| { 104 | nc_inq_unlimdims(loc, &mut nunlim, std::ptr::null_mut()) 105 | }))?; 106 | } 107 | if nunlim != 0 { 108 | let mut unlimdims = Vec::with_capacity(nunlim.try_into()?); 109 | unsafe { 110 | error::checked(super::with_lock(|| { 111 | nc_inq_unlimdims(loc, std::ptr::null_mut(), unlimdims.as_mut_ptr()) 112 | }))?; 113 | } 114 | unsafe { unlimdims.set_len(nunlim.try_into()?) } 115 | if unlimdims.contains(&dimid) { 116 | dimlen = 0; 117 | } 118 | } 119 | } 120 | 121 | Ok(Some(Dimension { 122 | len: core::num::NonZeroUsize::new(dimlen), 123 | id: DimensionIdentifier { ncid: loc, dimid }, 124 | _group: PhantomData, 125 | })) 126 | } 127 | 128 | pub(crate) fn dimensions_from_location<'g>( 129 | ncid: nc_type, 130 | ) -> error::Result>>> { 131 | let mut ndims = 0; 132 | unsafe { 133 | error::checked(super::with_lock(|| { 134 | nc_inq_dimids(ncid, &mut ndims, std::ptr::null_mut(), <_>::from(false)) 135 | }))?; 136 | } 137 | let mut dimids = vec![0; ndims.try_into()?]; 138 | unsafe { 139 | error::checked(super::with_lock(|| { 140 | nc_inq_dimids( 141 | ncid, 142 | std::ptr::null_mut(), 143 | dimids.as_mut_ptr(), 144 | <_>::from(false), 145 | ) 146 | }))?; 147 | } 148 | let unlimdims = { 149 | let mut nunlimdims = 0; 150 | unsafe { 151 | error::checked(super::with_lock(|| { 152 | nc_inq_unlimdims(ncid, &mut nunlimdims, std::ptr::null_mut()) 153 | }))?; 154 | } 155 | let mut unlimdims = Vec::with_capacity(nunlimdims.try_into()?); 156 | unsafe { 157 | error::checked(super::with_lock(|| { 158 | nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr()) 159 | }))?; 160 | } 161 | unsafe { 162 | unlimdims.set_len(nunlimdims.try_into()?); 163 | } 164 | unlimdims 165 | }; 166 | Ok(dimids.into_iter().map(move |dimid| { 167 | let mut dimlen = 0; 168 | if !unlimdims.contains(&dimid) { 169 | unsafe { 170 | error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen)))?; 171 | } 172 | } 173 | Ok(Dimension { 174 | len: core::num::NonZeroUsize::new(dimlen), 175 | id: DimensionIdentifier { ncid, dimid }, 176 | _group: PhantomData, 177 | }) 178 | })) 179 | } 180 | 181 | pub(crate) fn dimensions_from_variable<'g>( 182 | ncid: nc_type, 183 | varid: nc_type, 184 | ) -> error::Result>>> { 185 | let mut ndims = 0; 186 | unsafe { 187 | error::checked(super::with_lock(|| { 188 | nc_inq_varndims(ncid, varid, &mut ndims) 189 | }))?; 190 | } 191 | let mut dimids = vec![0; ndims.try_into()?]; 192 | unsafe { 193 | error::checked(super::with_lock(|| { 194 | nc_inq_vardimid(ncid, varid, dimids.as_mut_ptr()) 195 | }))?; 196 | } 197 | let unlimdims = { 198 | let mut nunlimdims = 0; 199 | unsafe { 200 | error::checked(super::with_lock(|| { 201 | nc_inq_unlimdims(ncid, &mut nunlimdims, std::ptr::null_mut()) 202 | }))?; 203 | } 204 | let mut unlimdims = Vec::with_capacity(nunlimdims.try_into()?); 205 | unsafe { 206 | error::checked(super::with_lock(|| { 207 | nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr()) 208 | }))?; 209 | } 210 | unsafe { 211 | unlimdims.set_len(nunlimdims.try_into()?); 212 | } 213 | unlimdims 214 | }; 215 | 216 | Ok(dimids.into_iter().map(move |dimid| { 217 | let mut dimlen = 0; 218 | if !unlimdims.contains(&dimid) { 219 | unsafe { 220 | error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen)))?; 221 | } 222 | } 223 | Ok(Dimension { 224 | len: core::num::NonZeroUsize::new(dimlen), 225 | id: DimensionIdentifier { ncid, dimid }, 226 | _group: PhantomData, 227 | }) 228 | })) 229 | } 230 | 231 | pub(crate) fn dimension_from_name<'f>( 232 | ncid: nc_type, 233 | name: &str, 234 | ) -> error::Result>> { 235 | let cname = super::utils::short_name_to_bytes(name)?; 236 | let mut dimid = 0; 237 | let e = unsafe { super::with_lock(|| nc_inq_dimid(ncid, cname.as_ptr().cast(), &mut dimid)) }; 238 | if e == NC_EBADDIM { 239 | return Ok(None); 240 | } 241 | error::checked(e)?; 242 | 243 | let mut dimlen = 0; 244 | unsafe { 245 | error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen))).unwrap(); 246 | } 247 | if dimlen != 0 { 248 | // Have to check if this dimension is unlimited 249 | let mut nunlim = 0; 250 | unsafe { 251 | error::checked(super::with_lock(|| { 252 | nc_inq_unlimdims(ncid, &mut nunlim, std::ptr::null_mut()) 253 | }))?; 254 | } 255 | if nunlim != 0 { 256 | let mut unlimdims = Vec::with_capacity(nunlim.try_into()?); 257 | unsafe { 258 | error::checked(super::with_lock(|| { 259 | nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr()) 260 | }))?; 261 | } 262 | unsafe { unlimdims.set_len(nunlim.try_into()?) } 263 | if unlimdims.contains(&dimid) { 264 | dimlen = 0; 265 | } 266 | } 267 | } 268 | Ok(Some(Dimension { 269 | len: core::num::NonZeroUsize::new(dimlen), 270 | id: super::dimension::DimensionIdentifier { ncid, dimid }, 271 | _group: PhantomData, 272 | })) 273 | } 274 | 275 | pub(crate) fn add_dimension_at<'f>( 276 | ncid: nc_type, 277 | name: &str, 278 | len: usize, 279 | ) -> error::Result> { 280 | let cname = super::utils::short_name_to_bytes(name)?; 281 | let mut dimid = 0; 282 | unsafe { 283 | error::checked(super::with_lock(|| { 284 | nc_def_dim(ncid, cname.as_ptr().cast(), len, &mut dimid) 285 | }))?; 286 | } 287 | Ok(Dimension { 288 | len: core::num::NonZeroUsize::new(dimid.try_into()?), 289 | id: DimensionIdentifier { ncid, dimid }, 290 | _group: PhantomData, 291 | }) 292 | } 293 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /netcdf/tests/types.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | #[test] 4 | fn test_roundtrip_types() { 5 | let d = tempfile::tempdir().unwrap(); 6 | let path = d.path().join("test_roundtrip_types.nc"); 7 | { 8 | let mut file = netcdf::create(&path).unwrap(); 9 | file.add_variable::("i8", &[]).unwrap(); 10 | file.add_variable::("u8", &[]).unwrap(); 11 | file.add_variable::("i16", &[]).unwrap(); 12 | file.add_variable::("u16", &[]).unwrap(); 13 | file.add_variable::("i32", &[]).unwrap(); 14 | file.add_variable::("u32", &[]).unwrap(); 15 | file.add_variable::("i64", &[]).unwrap(); 16 | file.add_variable::("u64", &[]).unwrap(); 17 | file.add_variable::("f32", &[]).unwrap(); 18 | file.add_variable::("f64", &[]).unwrap(); 19 | file.add_string_variable("string", &[]).unwrap(); 20 | } 21 | 22 | let file = netcdf::open(&path).unwrap(); 23 | assert_eq!(file.types().unwrap().count(), 0); 24 | let root = file.root().unwrap(); 25 | assert_eq!(root.types().count(), 0); 26 | for var in file.variables() { 27 | match var.name().as_str() { 28 | "i8" => { 29 | assert!(var.vartype().as_basic().unwrap().is_i8()); 30 | assert!(var.vartype().is_i8()); 31 | } 32 | "u8" => { 33 | assert!(var.vartype().as_basic().unwrap().is_u8()); 34 | assert!(var.vartype().is_u8()); 35 | } 36 | "i16" => { 37 | assert!(var.vartype().as_basic().unwrap().is_i16()); 38 | assert!(var.vartype().is_i16()); 39 | } 40 | "u16" => { 41 | assert!(var.vartype().as_basic().unwrap().is_u16()); 42 | assert!(var.vartype().is_u16()); 43 | } 44 | "i32" => { 45 | assert!(var.vartype().as_basic().unwrap().is_i32()); 46 | assert!(var.vartype().is_i32()); 47 | } 48 | "u32" => { 49 | assert!(var.vartype().as_basic().unwrap().is_u32()); 50 | assert!(var.vartype().is_u32()); 51 | } 52 | "i64" => { 53 | assert!(var.vartype().as_basic().unwrap().is_i64()); 54 | assert!(var.vartype().is_i64()); 55 | } 56 | "u64" => { 57 | assert!(var.vartype().as_basic().unwrap().is_u64()); 58 | assert!(var.vartype().is_u64()); 59 | } 60 | "f32" => { 61 | assert!(var.vartype().as_basic().unwrap().is_f32()); 62 | assert!(var.vartype().is_f32()); 63 | } 64 | "f64" => { 65 | assert!(var.vartype().as_basic().unwrap().is_f64()); 66 | assert!(var.vartype().is_f64()); 67 | } 68 | "string" => assert!(var.vartype().is_string()), 69 | _ => panic!("Got an unexpected varname: {}", var.name()), 70 | } 71 | } 72 | } 73 | 74 | #[test] 75 | fn add_opaque() { 76 | let d = tempfile::tempdir().unwrap(); 77 | let path = d.path().join("test_opaque.nc"); 78 | 79 | { 80 | let mut file = netcdf::create(path).unwrap(); 81 | 82 | let typ = file.add_opaque_type("opa", 42).unwrap(); 83 | assert_eq!(&typ.name(), "opa"); 84 | assert_eq!(typ.size(), 42); 85 | 86 | let mut g = file.add_group("g").unwrap(); 87 | let gtyp = g.add_opaque_type("oma", 43).unwrap(); 88 | assert_eq!(>yp.name(), "oma"); 89 | assert_eq!(gtyp.size(), 43); 90 | } 91 | 92 | // let file = netcdf::open(&path).unwrap(); 93 | // let var = file.typ("opa").unwrap(); 94 | } 95 | 96 | #[test] 97 | fn add_vlen() { 98 | let d = tempfile::tempdir().unwrap(); 99 | let path = d.path().join("test_add_vlen.nc"); 100 | 101 | { 102 | let mut file = netcdf::create(path).unwrap(); 103 | 104 | let typ = file.add_vlen_type::("v").unwrap(); 105 | assert_eq!(&typ.name(), "v"); 106 | assert!(typ.typ().is_u32()); 107 | let mut g = file.add_group("g").unwrap(); 108 | let typ = g.add_vlen_type::("w").unwrap(); 109 | assert_eq!(&typ.name(), "w"); 110 | assert!(&typ.typ().is_i32()); 111 | } 112 | } 113 | 114 | #[test] 115 | fn add_enum() { 116 | let d = tempfile::tempdir().unwrap(); 117 | let path = d.path().join("test_add_enum.nc"); 118 | 119 | { 120 | let mut file = netcdf::create(&path).unwrap(); 121 | 122 | let e = file 123 | .add_enum_type::("e", &[("a", 0), ("b", 1), ("c", 2), ("d", 3)]) 124 | .unwrap(); 125 | assert_eq!(&e.name(), "e"); 126 | assert!(e.typ().is_i32()); 127 | for member in e.members::().unwrap() { 128 | match member.0.as_str() { 129 | "a" => assert_eq!(member.1, 0), 130 | "b" => assert_eq!(member.1, 1), 131 | "c" => assert_eq!(member.1, 2), 132 | "d" => assert_eq!(member.1, 3), 133 | _ => panic!(), 134 | } 135 | } 136 | assert_eq!(&e.name_from_value(0).unwrap(), "a"); 137 | assert_eq!(&e.name_from_value(1).unwrap(), "b"); 138 | assert_eq!(&e.name_from_value(2).unwrap(), "c"); 139 | assert_eq!(&e.name_from_value(3).unwrap(), "d"); 140 | assert!(&e.name_from_value(4).is_none()); 141 | 142 | let mut g = file.add_group("g").unwrap(); 143 | let e = g 144 | .add_enum_type::("e", &[("e", -32), ("f", 41), ("g", 1241232), ("h", 0)]) 145 | .unwrap(); 146 | assert_eq!(&e.name(), "e"); 147 | assert!(e.typ().is_i64()); 148 | for member in e.members::().unwrap() { 149 | match member.0.as_str() { 150 | "e" => assert_eq!(member.1, -32), 151 | "f" => assert_eq!(member.1, 41), 152 | "g" => assert_eq!(member.1, 1241232), 153 | "h" => assert_eq!(member.1, 0), 154 | _ => panic!(), 155 | } 156 | } 157 | assert_eq!(&e.name_from_value(-32).unwrap(), "e"); 158 | assert_eq!(&e.name_from_value(41).unwrap(), "f"); 159 | assert_eq!(&e.name_from_value(1241232).unwrap(), "g"); 160 | assert_eq!(&e.name_from_value(0).unwrap(), "h"); 161 | assert!(&e.name_from_value(4).is_none()); 162 | } 163 | } 164 | 165 | #[test] 166 | fn add_compound() { 167 | let d = tempfile::tempdir().unwrap(); 168 | let path = d.path().join("test_add_compound.nc"); 169 | let mut file = netcdf::create(path).unwrap(); 170 | 171 | let mut builder = file.add_compound_type("c").unwrap(); 172 | builder.add::("u8").unwrap(); 173 | builder.add::("i8").unwrap(); 174 | builder.add_array::("ai32", &[1, 2, 3]).unwrap(); 175 | 176 | let c = builder.build().unwrap(); 177 | let e = file.add_enum_type("e", &[("a", 1), ("b", 2)]).unwrap(); 178 | 179 | let mut builder = file.add_compound_type("cc").unwrap(); 180 | builder.add_type("e", &e.into()).unwrap(); 181 | builder.add_type("c", &c.into()).unwrap(); 182 | builder.build().unwrap(); 183 | } 184 | 185 | #[test] 186 | fn read_compound_simple_nc4() { 187 | use netcdf::types::VariableType; 188 | let path = common::test_location().join("simple_nc4.nc"); 189 | let file = netcdf::open(&path).unwrap(); 190 | 191 | let group = file.group("grp2").unwrap().unwrap(); 192 | for typ in group.types() { 193 | let c = match typ { 194 | VariableType::Compound(c) => c, 195 | _ => panic!(), 196 | }; 197 | assert_eq!(&c.name(), "sample_compound_type"); 198 | let subtypes = c.fields().collect::>(); 199 | assert_eq!(subtypes.len(), 2); 200 | 201 | assert_eq!(&subtypes[0].name(), "i1"); 202 | assert_eq!(&subtypes[1].name(), "i2"); 203 | assert!(subtypes[0].typ().is_i32()); 204 | assert!(subtypes[1].typ().is_i32()); 205 | 206 | assert_eq!(subtypes[0].offset(), 0); 207 | assert_eq!(subtypes[1].offset(), std::mem::size_of::()); 208 | 209 | assert!(subtypes[0].dimensions().is_none()); 210 | assert!(subtypes[1].dimensions().is_none()); 211 | } 212 | 213 | let var = group.variable("data").unwrap(); 214 | 215 | if let VariableType::Compound(_) = var.vartype() { 216 | } else { 217 | panic!(); 218 | } 219 | 220 | let mut raws = vec![0_u8; 12 * 6 * 2 * 4]; 221 | var.get_raw_values(&mut raws, (..6, ..12)).unwrap(); 222 | 223 | use std::convert::TryInto; 224 | let intlen = 4; 225 | for i in 0..6 * 12 { 226 | let i1 = i32::from_le_bytes(raws[2 * intlen * i..][..intlen].try_into().unwrap()); 227 | let i2 = i32::from_le_bytes( 228 | raws[2 * intlen * i + intlen..][..intlen] 229 | .try_into() 230 | .unwrap(), 231 | ); 232 | assert_eq!(i1, 42); 233 | assert_eq!(i2, -42); 234 | } 235 | } 236 | 237 | #[test] 238 | fn put_get_enum() { 239 | let d = tempfile::tempdir().unwrap(); 240 | let path = d.path().join("test_put_get_enum.nc"); 241 | 242 | let bytes = (0..2 * 5).map(|i| i % 3 + 1).collect::>(); 243 | 244 | { 245 | let mut file = netcdf::create(&path).unwrap(); 246 | let e = file 247 | .add_enum_type("e", &[("one", 1_u8), ("two", 2), ("three", 3)]) 248 | .unwrap(); 249 | file.add_dimension("x", 2).unwrap(); 250 | file.add_dimension("y", 5).unwrap(); 251 | 252 | let mut var = file 253 | .add_variable_with_type("var", &["y", "x"], &e.into()) 254 | .unwrap(); 255 | 256 | unsafe { 257 | var.put_raw_values(&bytes, (..5, ..2)).unwrap(); 258 | } 259 | } 260 | 261 | let file = netcdf::open(&path).unwrap(); 262 | let var = file.variable("var").unwrap(); 263 | 264 | let mut bytes_copy = vec![0_u8; 5 * 2]; 265 | var.get_raw_values(&mut bytes_copy, (..5, ..2)).unwrap(); 266 | assert_eq!(bytes, bytes_copy); 267 | } 268 | 269 | #[test] 270 | fn put_get_vlen() { 271 | let d = tempfile::tempdir().unwrap(); 272 | let path = d.path().join("test_put_get_enum.nc"); 273 | 274 | { 275 | let mut file = netcdf::create(&path).unwrap(); 276 | file.add_dimension("x", 9).unwrap(); 277 | let v = file.add_vlen_type::("v").unwrap(); 278 | 279 | let mut var = file 280 | .add_variable_with_type("var", &["x"], &v.into()) 281 | .unwrap(); 282 | 283 | let buf = (0..9).collect::>(); 284 | 285 | for i in 0..9 { 286 | var.put_vlen(&buf[i..], [i]).unwrap(); 287 | } 288 | } 289 | 290 | let file = netcdf::open(&path).unwrap(); 291 | let var = file.variable("var").unwrap(); 292 | 293 | let buf = (0..9).collect::>(); 294 | for i in 0..9 { 295 | let v = var.get_vlen::(&[i]).unwrap(); 296 | assert_eq!(v, &buf[i..]); 297 | } 298 | } 299 | 300 | #[test] 301 | fn char() { 302 | use netcdf::types::BasicType; 303 | let d = tempfile::tempdir().unwrap(); 304 | let path = d.path().join("test_char.nc"); 305 | 306 | let mut f = netcdf::create(path).unwrap(); 307 | 308 | f.add_dimension("x", 2).unwrap(); 309 | 310 | let mut var = f 311 | .add_variable_with_type("x", &["x"], &BasicType::Char.into()) 312 | .unwrap(); 313 | 314 | let vals = [2, 3]; 315 | unsafe { 316 | var.put_raw_values(&vals, [..vals.len()]).unwrap(); 317 | } 318 | 319 | let mut retrieved_vals = [0, 0]; 320 | var.get_raw_values(&mut retrieved_vals, 0..2).unwrap(); 321 | assert_eq!(vals, retrieved_vals); 322 | } 323 | -------------------------------------------------------------------------------- /netcdf-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::process::Command; 3 | 4 | use semver::Version; 5 | 6 | macro_rules! feature { 7 | ($feature:expr) => { 8 | std::env::var(concat!("CARGO_FEATURE_", $feature)) 9 | }; 10 | } 11 | 12 | #[derive(Debug)] 13 | struct NcMetaHeader { 14 | version: Version, 15 | 16 | has_nc2: bool, 17 | has_nc4: bool, 18 | has_hdf4: bool, 19 | has_hdf5: bool, 20 | has_szip: bool, 21 | has_szip_write: bool, 22 | has_dap2: bool, 23 | has_dap4: bool, 24 | has_byterange: bool, 25 | has_diskless: bool, 26 | has_mmap: bool, 27 | has_jna: bool, 28 | has_pnetcdf: bool, 29 | has_parallel4: bool, 30 | has_parallel: bool, 31 | 32 | has_cdf5: bool, 33 | has_erange_fill: bool, 34 | relax_coord_bound: bool, 35 | dispatch_version: Option, 36 | has_par_filters: bool, 37 | has_nczarr: bool, 38 | has_multifilters: bool, 39 | has_logging: bool, 40 | has_quantize: bool, 41 | has_zstd: bool, 42 | has_benchmarks: bool, 43 | } 44 | 45 | impl NcMetaHeader { 46 | fn gather_from_includeheader(path: &std::path::Path) -> Self { 47 | macro_rules! match_prefix { 48 | ($line: expr, $prefix: expr, $item: expr) => { 49 | if let Some(item) = match_prefix_bool($line, $prefix) { 50 | $item = item; 51 | } 52 | }; 53 | } 54 | fn match_prefix<'a>(line: &'a str, prefix: &str) -> Option<&'a str> { 55 | line.strip_prefix(&format!("#define {prefix} ")) 56 | .map(|item| item.trim()) 57 | } 58 | fn match_prefix_bool(line: &str, prefix: &str) -> Option { 59 | match_prefix(line, prefix).map(|item| item.starts_with('1')) 60 | } 61 | let meta = std::fs::read_to_string(path).expect("Could not read header file"); 62 | 63 | let mut info = Self { 64 | version: Version::new(0, 0, 0), 65 | has_nc2: false, 66 | has_nc4: false, 67 | has_benchmarks: false, 68 | has_byterange: false, 69 | has_cdf5: false, 70 | has_dap2: false, 71 | has_dap4: false, 72 | has_diskless: false, 73 | dispatch_version: None, 74 | has_erange_fill: false, 75 | has_hdf4: false, 76 | has_hdf5: false, 77 | has_jna: false, 78 | has_logging: false, 79 | has_mmap: false, 80 | has_multifilters: false, 81 | has_nczarr: false, 82 | has_par_filters: false, 83 | has_parallel: false, 84 | has_parallel4: false, 85 | has_pnetcdf: false, 86 | has_quantize: false, 87 | has_szip: false, 88 | has_szip_write: false, 89 | has_zstd: false, 90 | relax_coord_bound: false, 91 | }; 92 | for line in meta.lines() { 93 | if let Some(ncversion) = match_prefix(line, "NC_VERSION") { 94 | info.version = Version::parse(ncversion.trim_matches('"')).unwrap(); 95 | } 96 | if let Some(dversion) = match_prefix(line, "NC_DISPATCH_VERSION") { 97 | let (dversion, _) = dversion.split_once(' ').unwrap(); 98 | info.dispatch_version = Some(dversion.parse().unwrap()); 99 | } 100 | match_prefix!(line, "NC_HAS_NC2", info.has_nc2); 101 | match_prefix!(line, "NC_HAS_NC4", info.has_nc4); 102 | match_prefix!(line, "NC_HAS_HDF4", info.has_hdf4); 103 | match_prefix!(line, "NC_HAS_HDF5", info.has_hdf5); 104 | match_prefix!(line, "NC_HAS_SZIP", info.has_szip); 105 | match_prefix!(line, "NC_HAS_DAP2", info.has_dap2); 106 | match_prefix!(line, "NC_HAS_DAP4", info.has_dap4); 107 | match_prefix!(line, "NC_HAS_DISKLESS", info.has_diskless); 108 | match_prefix!(line, "NC_HAS_MMAP", info.has_mmap); 109 | match_prefix!(line, "NC_HAS_JNA", info.has_jna); 110 | match_prefix!(line, "NC_HAS_PNETCDF", info.has_pnetcdf); 111 | match_prefix!(line, "NC_HAS_PARALLEL", info.has_parallel); 112 | match_prefix!(line, "NC_HAS_CDF5", info.has_cdf5); 113 | match_prefix!(line, "NC_HAS_BYTERANGE", info.has_byterange); 114 | match_prefix!(line, "NC_HAS_BENCHMARKS", info.has_benchmarks); 115 | match_prefix!(line, "NC_HAS_ERANGE_FILL", info.has_erange_fill); 116 | match_prefix!(line, "NC_HAS_ZSTD", info.has_zstd); 117 | match_prefix!(line, "NC_HAS_QUANTIZE", info.has_quantize); 118 | match_prefix!(line, "NC_HAS_LOGGING", info.has_logging); 119 | match_prefix!(line, "NC_HAS_MULTIFILTERS", info.has_multifilters); 120 | match_prefix!(line, "NC_HAS_NCZARR", info.has_nczarr); 121 | match_prefix!(line, "NC_HAS_PAR_FILTERS", info.has_par_filters); 122 | match_prefix!(line, "NC_RELAX_COORD_BOUND", info.relax_coord_bound); 123 | match_prefix!(line, "NC_HAS_PARALLEL", info.has_parallel); 124 | match_prefix!(line, "NC_HAS_PARALLEL4", info.has_parallel4); 125 | match_prefix!(line, "NC_HAS_SZIP_WRITE", info.has_szip_write); 126 | } 127 | info 128 | } 129 | 130 | fn emit_feature_flags(&self) { 131 | if self.has_dap2 || self.has_dap4 { 132 | println!("cargo:rustc-cfg=feature=\"has-dap\""); 133 | println!("cargo:has-dap=1"); 134 | } else { 135 | assert!( 136 | feature!("DAP").is_err(), 137 | "DAP requested but not found in this installation of netCDF" 138 | ); 139 | } 140 | if self.has_mmap { 141 | println!("cargo:rustc-cfg=feature=\"has-mmap\""); 142 | println!("cargo:has-mmap=1"); 143 | } else { 144 | assert!( 145 | feature!("MEMIO").is_err(), 146 | "MEMIO requested but not found in this installation of netCDF" 147 | ); 148 | } 149 | } 150 | } 151 | 152 | #[derive(Debug)] 153 | struct NcInfo { 154 | version: Option, 155 | 156 | includedir: PathBuf, 157 | libdir: PathBuf, 158 | libname: String, 159 | } 160 | 161 | fn from_utf8_to_trimmed_string(bytes: &[u8]) -> String { 162 | String::from_utf8_lossy(bytes).trim().to_owned() 163 | } 164 | 165 | impl NcInfo { 166 | fn guess() -> Self { 167 | todo!() 168 | } 169 | fn from_path(path: &Path) -> Self { 170 | Self { 171 | version: None, 172 | includedir: path.join("include"), 173 | libdir: path.join("lib"), 174 | libname: "netcdf".to_owned(), 175 | } 176 | } 177 | fn gather_from_ncconfig(search_path: Option<&Path>) -> Option { 178 | let path = if let Some(search_path) = search_path { 179 | let search_path = search_path.join("bin").join("nc-config"); 180 | search_path.as_os_str().to_owned() 181 | } else { 182 | std::ffi::OsString::from("nc-config") 183 | }; 184 | let cmd = || Command::new(&path); 185 | cmd().arg("--help").status().ok()?; 186 | 187 | let extract = |arg: &str| -> Result, Box> { 188 | let output = &cmd().arg(arg).output()?; 189 | if output.status.success() { 190 | Ok(Some(from_utf8_to_trimmed_string(&output.stdout))) 191 | } else { 192 | Ok(None) 193 | } 194 | }; 195 | 196 | let version = if let Ok(Some(version)) = extract("--version") { 197 | version.strip_prefix("netCDF ").unwrap().to_owned() 198 | } else { 199 | panic!("Could not get information from this installation of NetCDF"); 200 | }; 201 | let version = Version::parse(&version).unwrap(); 202 | 203 | let includedir = PathBuf::from(extract("--includedir").unwrap().unwrap()); 204 | let libdir = PathBuf::from(extract("--libdir").unwrap().unwrap()); 205 | let libs = extract("--libs").unwrap().unwrap(); 206 | assert!(libs.contains("-lnetcdf")); 207 | let libname = "netcdf".to_owned(); 208 | 209 | let _inc = std::fs::read_to_string(std::path::Path::new(&includedir).join("netcdf.h")) 210 | .expect("Could not find netcdf.h"); 211 | 212 | Some(Self { 213 | version: Some(version), 214 | includedir, 215 | libdir, 216 | libname, 217 | }) 218 | } 219 | } 220 | 221 | fn _check_consistent_version_linked() { 222 | // use libloading 223 | todo!() 224 | } 225 | 226 | fn main() { 227 | println!("cargo:rerun-if-changed=build.rs"); 228 | 229 | let info; 230 | if feature!("STATIC").is_ok() { 231 | let netcdf_lib = std::env::var("DEP_NETCDFSRC_LIB").unwrap(); 232 | let netcdf_path = PathBuf::from(std::env::var_os("DEP_NETCDFSRC_SEARCH").unwrap()); 233 | 234 | info = NcInfo::gather_from_ncconfig(Some(&netcdf_path.join(".."))) 235 | .unwrap_or_else(|| NcInfo::from_path(&netcdf_path.join(".."))); 236 | 237 | println!("cargo:rustc-link-search=native={}", netcdf_path.display()); 238 | println!("cargo:rustc-link-lib=static={netcdf_lib}"); 239 | } else { 240 | println!("cargo:rerun-if-env-changed=NETCDF_DIR"); 241 | 242 | let nc_dir = std::env::var_os("NETCDF_DIR") 243 | .or_else(|| std::env::var_os("NetCDF_DIR")) 244 | .map(PathBuf::from); 245 | 246 | #[cfg(windows)] 247 | let nc_dir = nc_dir.map(|d| d.join("Library")); 248 | 249 | info = if let Some(nc_dir) = nc_dir.as_ref() { 250 | NcInfo::gather_from_ncconfig(Some(nc_dir)).unwrap_or_else(|| NcInfo::from_path(nc_dir)) 251 | } else { 252 | NcInfo::gather_from_ncconfig(None).unwrap_or_else(NcInfo::guess) 253 | }; 254 | 255 | println!("cargo:rustc-link-search={}", info.libdir.display()); 256 | println!("cargo:rustc-link-lib={}", &info.libname); 257 | } 258 | 259 | let metaheader = NcMetaHeader::gather_from_includeheader( 260 | &std::path::Path::new(&info.includedir).join("netcdf_meta.h"), 261 | ); 262 | if let Some(version) = info.version { 263 | assert_eq!(version, metaheader.version, "Version mismatch"); 264 | } 265 | 266 | // panic!("{:?}", info); 267 | // Emit nc flags 268 | println!("cargo:includedir={}", info.includedir.display()); 269 | println!("cargo:nc_version={}", metaheader.version); 270 | let versions = [ 271 | Version::new(4, 4, 0), 272 | Version::new(4, 4, 1), 273 | Version::new(4, 5, 0), 274 | Version::new(4, 6, 0), 275 | Version::new(4, 6, 1), 276 | Version::new(4, 6, 2), 277 | Version::new(4, 6, 3), 278 | Version::new(4, 7, 0), 279 | Version::new(4, 7, 1), 280 | Version::new(4, 7, 2), 281 | Version::new(4, 7, 3), 282 | Version::new(4, 7, 4), 283 | Version::new(4, 8, 0), 284 | Version::new(4, 8, 1), 285 | Version::new(4, 9, 0), 286 | Version::new(4, 9, 1), 287 | Version::new(4, 9, 2), 288 | ]; 289 | 290 | if !versions.contains(&metaheader.version) { 291 | if versions 292 | .iter() 293 | .any(|x| (x.major == metaheader.version.major) && (x.minor == metaheader.version.minor)) 294 | { 295 | println!("We don't know this release, but it is just a patch difference") 296 | } else if versions.iter().any(|x| x.major == metaheader.version.major) { 297 | eprintln!("This minor version of netCDF is not known, but the major version is known and the release is unlikely to contain breaking API changes"); 298 | } else { 299 | eprintln!("This major version is not known, please file an issue if breaking API changes have been made to netCDF-c"); 300 | } 301 | } 302 | 303 | for version in versions { 304 | if metaheader.version >= version { 305 | println!( 306 | "cargo:rustc-cfg=feature=\"{}.{}.{}\"", 307 | version.major, version.minor, version.patch 308 | ); 309 | println!( 310 | "cargo:version_\"{}.{}.{}\"=1", 311 | version.major, version.minor, version.patch 312 | ); 313 | } 314 | } 315 | metaheader.emit_feature_flags(); 316 | } 317 | -------------------------------------------------------------------------------- /netcdf/src/group.rs: -------------------------------------------------------------------------------- 1 | //! All netcdf items belong in the root group, which can 2 | //! be interacted with to get the underlying data 3 | 4 | use std::marker::PhantomData; 5 | 6 | use netcdf_sys::*; 7 | 8 | use super::attribute::{Attribute, AttributeValue}; 9 | use super::dimension::Dimension; 10 | use super::error; 11 | use super::variable::{NcPutGet, Variable, VariableMut}; 12 | 13 | /// Main component of the netcdf format. Holds all variables, 14 | /// attributes, and dimensions. A group can always see the parents items, 15 | /// but a parent can not access a childs items. 16 | #[derive(Debug, Clone)] 17 | pub struct Group<'f> { 18 | pub(crate) ncid: nc_type, 19 | pub(crate) _file: PhantomData<&'f nc_type>, 20 | } 21 | 22 | #[derive(Debug)] 23 | /// Mutable access to a group. 24 | /// 25 | /// This type derefs to a [`Group`](Group), which means [`GroupMut`](Self) 26 | /// can be used where [`Group`](Group) is expected 27 | #[allow(clippy::module_name_repetitions)] 28 | pub struct GroupMut<'f>( 29 | pub(crate) Group<'f>, 30 | pub(crate) PhantomData<&'f mut nc_type>, 31 | ); 32 | 33 | impl<'f> std::ops::Deref for GroupMut<'f> { 34 | type Target = Group<'f>; 35 | fn deref(&self) -> &Self::Target { 36 | &self.0 37 | } 38 | } 39 | 40 | impl<'f> Group<'f> { 41 | /// Name of the current group 42 | pub fn name(&self) -> String { 43 | let mut name = vec![0_u8; NC_MAX_NAME as usize + 1]; 44 | unsafe { 45 | error::checked(super::with_lock(|| { 46 | nc_inq_grpname(self.ncid, name.as_mut_ptr().cast()) 47 | })) 48 | .unwrap(); 49 | } 50 | let zeropos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 51 | name.resize(zeropos, 0); 52 | 53 | String::from_utf8(name).expect("Group did not have a valid name") 54 | } 55 | /// Internal ncid of the group 56 | fn id(&self) -> nc_type { 57 | self.ncid 58 | } 59 | 60 | /// Get a variable from the group 61 | pub fn variable<'g>(&'g self, name: &str) -> Option> 62 | where 63 | 'f: 'g, 64 | { 65 | let (ncid, name) = super::group::try_get_parent_ncid_and_stem(self.id(), name).unwrap()?; 66 | Variable::find_from_name(ncid, name).unwrap() 67 | } 68 | /// Iterate over all variables in a group 69 | pub fn variables<'g>(&'g self) -> impl Iterator> 70 | where 71 | 'f: 'g, 72 | { 73 | super::variable::variables_at_ncid(self.id()) 74 | .unwrap() 75 | .map(Result::unwrap) 76 | } 77 | 78 | /// Get a single attribute 79 | pub fn attribute<'a>(&'a self, name: &str) -> Option> { 80 | let (ncid, name) = try_get_parent_ncid_and_stem(self.id(), name).unwrap()?; 81 | Attribute::find_from_name(ncid, None, name).unwrap() 82 | } 83 | /// Get all attributes in the group 84 | pub fn attributes(&self) -> impl Iterator { 85 | // Need to lock when reading the first attribute (per group) 86 | crate::attribute::AttributeIterator::new(self.ncid, None) 87 | .unwrap() 88 | .map(Result::unwrap) 89 | } 90 | /// Get the attribute value 91 | pub fn attribute_value(&self, name: &str) -> Option> { 92 | self.attribute(name).as_ref().map(Attribute::value) 93 | } 94 | 95 | /// Get a single dimension 96 | pub fn dimension<'g>(&'g self, name: &str) -> Option> 97 | where 98 | 'f: 'g, 99 | { 100 | let (ncid, name) = super::group::try_get_parent_ncid_and_stem(self.id(), name).unwrap()?; 101 | super::dimension::dimension_from_name(ncid, name).unwrap() 102 | } 103 | /// Iterator over all dimensions 104 | pub fn dimensions<'g>(&'g self) -> impl Iterator> 105 | where 106 | 'f: 'g, 107 | { 108 | super::dimension::dimensions_from_location(self.id()) 109 | .unwrap() 110 | .map(Result::unwrap) 111 | } 112 | 113 | /// Get a group 114 | pub fn group<'g>(&'g self, name: &str) -> Option> 115 | where 116 | 'f: 'g, 117 | { 118 | // We are in a group, must support netCDF-4 119 | let (ncid, name) = get_parent_ncid_and_stem(self.id(), name).unwrap(); 120 | Some(Group { 121 | ncid: try_get_ncid(ncid, name).unwrap()?, 122 | _file: PhantomData, 123 | }) 124 | } 125 | /// Iterator over all subgroups in this group 126 | pub fn groups<'g>(&'g self) -> impl Iterator> 127 | where 128 | 'f: 'g, 129 | { 130 | groups_at_ncid(self.id()).unwrap() 131 | } 132 | 133 | /// Return all types in this group 134 | pub fn types(&self) -> impl Iterator { 135 | super::types::all_at_location(self.ncid) 136 | .map(|x| x.map(Result::unwrap)) 137 | .unwrap() 138 | } 139 | } 140 | 141 | impl<'f> GroupMut<'f> { 142 | /// Get a mutable variable from the group 143 | pub fn variable_mut<'g>(&'g mut self, name: &str) -> Option> 144 | where 145 | 'f: 'g, 146 | { 147 | self.variable(name).map(|v| VariableMut(v, PhantomData)) 148 | } 149 | /// Iterate over all variables in a group, with mutable access 150 | pub fn variables_mut<'g>(&'g mut self) -> impl Iterator> 151 | where 152 | 'f: 'g, 153 | { 154 | self.variables().map(|var| VariableMut(var, PhantomData)) 155 | } 156 | 157 | /// Mutable access to subgroup 158 | pub fn group_mut<'g>(&'g mut self, name: &str) -> Option> 159 | where 160 | 'f: 'g, 161 | { 162 | self.group(name).map(|g| GroupMut(g, PhantomData)) 163 | } 164 | /// Iterator over all groups (mutable access) 165 | pub fn groups_mut<'g>(&'g mut self) -> impl Iterator> 166 | where 167 | 'f: 'g, 168 | { 169 | self.groups().map(|g| GroupMut(g, PhantomData)) 170 | } 171 | 172 | /// Add an opaque datatype, with `size` bytes 173 | pub fn add_opaque_type( 174 | &'f mut self, 175 | name: &str, 176 | size: usize, 177 | ) -> error::Result { 178 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 179 | super::types::OpaqueType::add(ncid, name, size) 180 | } 181 | 182 | /// Add a variable length datatype 183 | pub fn add_vlen_type( 184 | &'f mut self, 185 | name: &str, 186 | ) -> error::Result { 187 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 188 | super::types::VlenType::add::(ncid, name) 189 | } 190 | 191 | /// Add an enum datatype 192 | pub fn add_enum_type( 193 | &'f mut self, 194 | name: &str, 195 | mappings: &[(&str, T)], 196 | ) -> error::Result { 197 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 198 | super::types::EnumType::add::(ncid, name, mappings) 199 | } 200 | 201 | /// Build a compound type 202 | pub fn add_compound_type( 203 | &mut self, 204 | name: &str, 205 | ) -> error::Result { 206 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 207 | super::types::CompoundType::add(ncid, name) 208 | } 209 | 210 | /// Add an attribute to the group 211 | pub fn add_attribute<'a, T>(&'a mut self, name: &str, val: T) -> error::Result> 212 | where 213 | T: Into, 214 | { 215 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 216 | Attribute::put(ncid, NC_GLOBAL, name, val.into()) 217 | } 218 | 219 | /// Adds a dimension with the given name and size. A size of zero gives an unlimited dimension 220 | pub fn add_dimension<'g>(&'g mut self, name: &str, len: usize) -> error::Result> { 221 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 222 | super::dimension::add_dimension_at(ncid, name, len) 223 | } 224 | 225 | /// Adds a dimension with unbounded size 226 | pub fn add_unlimited_dimension<'g>(&'g mut self, name: &str) -> error::Result> { 227 | self.add_dimension(name, 0) 228 | } 229 | 230 | /// Add an empty group to the dataset 231 | pub fn add_group<'g>(&'g mut self, name: &str) -> error::Result> 232 | where 233 | 'f: 'g, 234 | { 235 | Ok(Self( 236 | Group { 237 | ncid: add_group_at_path(self.id(), name)?, 238 | _file: PhantomData, 239 | }, 240 | PhantomData, 241 | )) 242 | } 243 | 244 | /// Create a Variable into the dataset, with no data written into it 245 | /// 246 | /// Dimensions are identified using the name of the dimension, and will recurse upwards 247 | /// if not found in the current group. 248 | pub fn add_variable<'g, T>( 249 | &'g mut self, 250 | name: &str, 251 | dims: &[&str], 252 | ) -> error::Result> 253 | where 254 | T: NcPutGet, 255 | 'f: 'g, 256 | { 257 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 258 | VariableMut::add_from_str(ncid, T::NCTYPE, name, dims) 259 | } 260 | /// Adds a variable with a basic type of string 261 | pub fn add_string_variable<'g>( 262 | &mut self, 263 | name: &str, 264 | dims: &[&str], 265 | ) -> error::Result> { 266 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 267 | VariableMut::add_from_str(ncid, NC_STRING, name, dims) 268 | } 269 | /// Adds a variable from a set of unique identifiers, recursing upwards 270 | /// from the current group if necessary. 271 | pub fn add_variable_from_identifiers<'g, T>( 272 | &'g mut self, 273 | name: &str, 274 | dims: &[super::dimension::DimensionIdentifier], 275 | ) -> error::Result> 276 | where 277 | T: NcPutGet, 278 | { 279 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 280 | super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE) 281 | } 282 | 283 | /// Create a variable with the specified type 284 | pub fn add_variable_with_type( 285 | &'f mut self, 286 | name: &str, 287 | dims: &[&str], 288 | typ: &super::types::VariableType, 289 | ) -> error::Result> { 290 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?; 291 | VariableMut::add_from_str(ncid, typ.id(), name, dims) 292 | } 293 | } 294 | 295 | pub(crate) fn groups_at_ncid<'f>(ncid: nc_type) -> error::Result>> { 296 | let mut num_grps = 0; 297 | unsafe { 298 | error::checked(super::with_lock(|| { 299 | nc_inq_grps(ncid, &mut num_grps, std::ptr::null_mut()) 300 | }))?; 301 | } 302 | let mut grps = vec![0; num_grps.try_into()?]; 303 | unsafe { 304 | error::checked(super::with_lock(|| { 305 | nc_inq_grps(ncid, std::ptr::null_mut(), grps.as_mut_ptr()) 306 | }))?; 307 | } 308 | Ok(grps.into_iter().map(|id| Group { 309 | ncid: id, 310 | _file: PhantomData, 311 | })) 312 | } 313 | 314 | pub(crate) fn add_group_at_path(mut ncid: nc_type, path: &str) -> error::Result { 315 | let mut path = path.split('/'); 316 | let name = path.next_back().unwrap(); 317 | for name in path { 318 | ncid = match try_get_ncid(ncid, name)? { 319 | Some(ncid) => ncid, 320 | None => add_group(ncid, name)?, 321 | } 322 | } 323 | add_group(ncid, name) 324 | } 325 | 326 | pub(crate) fn add_group(mut ncid: nc_type, name: &str) -> error::Result { 327 | let byte_name = super::utils::short_name_to_bytes(name)?; 328 | unsafe { 329 | error::checked(super::with_lock(|| { 330 | nc_def_grp(ncid, byte_name.as_ptr().cast(), &mut ncid) 331 | }))?; 332 | } 333 | Ok(ncid) 334 | } 335 | 336 | pub(crate) fn try_get_ncid(mut ncid: nc_type, name: &str) -> error::Result> { 337 | let byte_name = super::utils::short_name_to_bytes(name)?; 338 | let e = 339 | unsafe { super::with_lock(|| nc_inq_grp_ncid(ncid, byte_name.as_ptr().cast(), &mut ncid)) }; 340 | if e == NC_ENOGRP { 341 | return Ok(None); 342 | } 343 | error::checked(e)?; 344 | Ok(Some(ncid)) 345 | } 346 | 347 | pub(crate) fn try_get_parent_ncid_and_stem( 348 | mut ncid: nc_type, 349 | path: &str, 350 | ) -> error::Result> { 351 | let mut path = path.split('/'); 352 | let name = path.next_back().unwrap(); 353 | for name in path { 354 | ncid = match try_get_ncid(ncid, name)? { 355 | None => return Ok(None), 356 | Some(ncid) => ncid, 357 | } 358 | } 359 | Ok(Some((ncid, name))) 360 | } 361 | 362 | pub(crate) fn get_parent_ncid_and_stem( 363 | ncid: nc_type, 364 | path: &str, 365 | ) -> error::Result<(nc_type, &str)> { 366 | try_get_parent_ncid_and_stem(ncid, path)? 367 | .ok_or_else(|| error::Error::Str("One of the child groups does not exist".into())) 368 | } 369 | -------------------------------------------------------------------------------- /netcdf-sys/src/consts.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unreadable_literal)] 2 | #![allow(non_snake_case)] 3 | #![allow(clippy::excessive_precision)] 4 | 5 | use ::std::os::raw::c_int; 6 | 7 | use super::nc_type; 8 | 9 | pub const NC_NAT: nc_type = 0; 10 | pub const NC_BYTE: nc_type = 1; 11 | pub const NC_CHAR: nc_type = 2; 12 | pub const NC_SHORT: nc_type = 3; 13 | pub const NC_INT: nc_type = 4; 14 | pub const NC_LONG: nc_type = 4; 15 | pub const NC_FLOAT: nc_type = 5; 16 | pub const NC_DOUBLE: nc_type = 6; 17 | pub const NC_UBYTE: nc_type = 7; 18 | pub const NC_USHORT: nc_type = 8; 19 | pub const NC_UINT: nc_type = 9; 20 | pub const NC_INT64: nc_type = 10; 21 | pub const NC_UINT64: nc_type = 11; 22 | pub const NC_STRING: nc_type = 12; 23 | 24 | pub const NC_MAX_ATOMIC_TYPE: nc_type = 12; 25 | 26 | pub const NC_VLEN: nc_type = 13; 27 | pub const NC_OPAQUE: nc_type = 14; 28 | pub const NC_ENUM: nc_type = 15; 29 | pub const NC_COMPOUND: nc_type = 16; 30 | 31 | pub const NC_FIRSTUSERTYPEID: nc_type = 32; 32 | 33 | pub const NC_FILL_BYTE: i8 = -127; 34 | pub const NC_FILL_CHAR: u8 = 0; 35 | pub const NC_FILL_SHORT: i16 = -32767; 36 | pub const NC_FILL_INT: i32 = -2147483647; 37 | pub const NC_FILL_FLOAT: f32 = 9.9692099683868690e+36; 38 | pub const NC_FILL_DOUBLE: f64 = 9.9692099683868690e+36; 39 | pub const NC_FILL_UBYTE: u8 = 255; 40 | pub const NC_FILL_USHORT: u16 = 65535; 41 | pub const NC_FILL_UINT: u32 = 4294967295; 42 | pub const NC_FILL_INT64: i64 = -9223372036854775806; 43 | pub const NC_FILL_UINT64: u64 = 18446744073709551614; 44 | pub const NC_FILL_STRING: &[u8] = b"\0"; 45 | 46 | pub const NC_MAX_BYTE: i8 = 127; 47 | pub const NC_MIN_BYTE: i8 = -NC_MAX_BYTE - 1; 48 | pub const NC_MAX_CHAR: u8 = 255; 49 | pub const NC_MAX_SHORT: i16 = 32767; 50 | pub const NC_MIN_SHORT: i16 = -NC_MAX_SHORT - 1; 51 | pub const NC_MAX_INT: i32 = 2147483647; 52 | pub const NC_MIN_INT: i32 = -NC_MAX_INT - 1; 53 | pub const NC_MAX_FLOAT: f32 = 3.402823466e+38; 54 | pub const NC_MIN_FLOAT: f32 = -NC_MAX_FLOAT; 55 | pub const NC_MAX_DOUBLE: f64 = 1.7976931348623157e+308; 56 | pub const NC_MIN_DOUBLE: f64 = -NC_MAX_DOUBLE; 57 | pub const NC_MAX_UBYTE: u8 = 255; 58 | pub const NC_MAX_USHORT: u16 = 65535; 59 | pub const NC_MAX_UINT: u32 = 4294967295; 60 | pub const NC_MAX_INT64: i64 = 9223372036854775807; 61 | pub const NC_MIN_INT64: i64 = -9223372036854775808; 62 | pub const NC_MAX_UINT64: u64 = 18446744073709551615; 63 | 64 | pub const _FillValue: &[u8] = b"_FillValue\0"; 65 | 66 | pub const NC_FILL: c_int = 0x000; 67 | pub const NC_NOFILL: c_int = 0x100; 68 | 69 | pub const NC_NOWRITE: c_int = 0x0000; 70 | pub const NC_WRITE: c_int = 0x0001; 71 | 72 | pub const NC_CLOBBER: c_int = 0x0000; 73 | pub const NC_NOCLOBBER: c_int = 0x0004; 74 | 75 | pub const NC_DISKLESS: c_int = 0x0008; 76 | pub const NC_MMAP: c_int = 0x0010; 77 | 78 | #[cfg(feature = "4.4.0")] 79 | pub const NC_64BIT_DATA: c_int = 0x0020; 80 | #[cfg(feature = "4.4.0")] 81 | pub const NC_CDF5: c_int = NC_64BIT_DATA; 82 | 83 | #[cfg(feature = "4.6.2")] 84 | pub const NC_UDF0: c_int = 0x0040; 85 | #[cfg(feature = "4.6.2")] 86 | pub const NC_UDF1: c_int = 0x0080; 87 | 88 | pub const NC_CLASSIC_MODEL: c_int = 0x0100; 89 | pub const NC_64BIT_OFFSET: c_int = 0x0200; 90 | 91 | pub const NC_LOCK: c_int = 0x0400; 92 | 93 | pub const NC_SHARE: c_int = 0x0800; 94 | 95 | pub const NC_NETCDF4: c_int = 0x1000; 96 | 97 | #[cfg_attr( 98 | feature = "4.6.2", 99 | deprecated(note = "Parallel I/O is initiated by calling nc_create_par and nc_open_par") 100 | )] 101 | pub const NC_MPIIO: c_int = 0x2000; 102 | #[cfg_attr( 103 | feature = "4.6.2", 104 | deprecated(note = "Parallel I/O is initiated by calling nc_create_par and nc_open_par"), 105 | allow(deprecated) 106 | )] 107 | pub const NC_PNETCDF: c_int = NC_MPIIO; 108 | #[cfg_attr( 109 | feature = "4.6.2", 110 | deprecated(note = "Parallel I/O is initiated by calling nc_create_par and nc_open_par") 111 | )] 112 | pub const NC_MPIPOSIX: c_int = { 113 | #[cfg(feature = "4.6.2")] 114 | { 115 | 0x4000 116 | } 117 | #[cfg(not(feature = "4.6.2"))] 118 | { 119 | NC_MPIIO 120 | } 121 | }; 122 | 123 | #[cfg(feature = "4.6.2")] 124 | pub const NC_PERSIST: c_int = 0x4000; 125 | 126 | pub const NC_INMEMORY: c_int = 0x8000; 127 | 128 | #[cfg(feature = "4.6.2")] 129 | pub const NC_MAX_MAGIC_NUMBER_LEN: usize = 8; 130 | 131 | pub const NC_FORMAT_CLASSIC: c_int = 1; 132 | pub const NC_FORMAT_64BIT_OFFSET: c_int = 2; 133 | pub const NC_FORMAT_64BIT: c_int = NC_FORMAT_64BIT_OFFSET; 134 | pub const NC_FORMAT_NETCDF4: c_int = 3; 135 | pub const NC_FORMAT_NETCDF4_CLASSIC: c_int = 4; 136 | pub const NC_FORMAT_64BIT_DATA: c_int = 5; 137 | pub const NC_FORMAT_CDF5: c_int = NC_FORMAT_64BIT_DATA; 138 | 139 | #[cfg(feature = "4.7.0")] 140 | pub const NC_FORMAT_ALL: c_int = 141 | NC_64BIT_OFFSET | NC_64BIT_DATA | NC_CLASSIC_MODEL | NC_NETCDF4 | NC_UDF0 | NC_UDF1; 142 | 143 | #[deprecated(note = "Use NC_FORMATX_NC3")] 144 | pub const NC_FORMAT_NC3: c_int = NC_FORMATX_NC3; 145 | #[deprecated(note = "Use NC_FORMATX_HDF5")] 146 | pub const NC_FORMAT_NC_HDF5: c_int = NC_FORMATX_NC_HDF5; 147 | #[deprecated(note = "Use NC_FORMATX_NC4")] 148 | pub const NC_FORMAT_NC4: c_int = NC_FORMATX_NC4; 149 | #[deprecated(note = "Use NC_FORMATX_HDF4")] 150 | pub const NC_FORMAT_NC_HDF4: c_int = NC_FORMATX_NC_HDF4; 151 | #[deprecated(note = "Use NC_FORMATX_PNETCDF")] 152 | pub const NC_FORMAT_PNETCDF: c_int = NC_FORMATX_PNETCDF; 153 | #[deprecated(note = "Use NC_FORMATX_DAP2")] 154 | pub const NC_FORMAT_DAP2: c_int = NC_FORMATX_DAP2; 155 | #[deprecated(note = "Use NC_FORMATX_DAP4")] 156 | pub const NC_FORMAT_DAP4: c_int = NC_FORMATX_DAP4; 157 | #[deprecated(note = "Use NC_FORMATX_UNDEFINED")] 158 | pub const NC_FORMAT_UNDEFINED: c_int = NC_FORMATX_UNDEFINED; 159 | 160 | pub const NC_FORMATX_NC3: c_int = 1; 161 | pub const NC_FORMATX_NC_HDF5: c_int = 2; 162 | pub const NC_FORMATX_NC4: c_int = NC_FORMATX_NC_HDF5; 163 | pub const NC_FORMATX_NC_HDF4: c_int = 3; 164 | pub const NC_FORMATX_PNETCDF: c_int = 4; 165 | pub const NC_FORMATX_DAP2: c_int = 5; 166 | pub const NC_FORMATX_DAP4: c_int = 6; 167 | #[cfg(feature = "4.6.2")] 168 | pub const NC_FORMATX_UDF0: c_int = 8; 169 | #[cfg(feature = "4.6.2")] 170 | pub const NC_FORMATX_UDF1: c_int = 9; 171 | #[cfg(feature = "4.7.0")] 172 | pub const NC_FORMATX_NCZARR: c_int = 10; 173 | pub const NC_FORMATX_UNDEFINED: c_int = 0; 174 | 175 | pub const NC_SIZEHINT_DEFAULT: c_int = 0; 176 | 177 | pub const NC_ALIGN_CHUNK: usize = !0; 178 | 179 | pub const NC_UNLIMITED: usize = 0; 180 | 181 | pub const NC_GLOBAL: c_int = -1; 182 | 183 | #[cfg_attr(feature = "4.5.0", deprecated(note = "Not enforced"))] 184 | pub const NC_MAX_DIMS: c_int = 1024; 185 | #[cfg_attr(feature = "4.5.0", deprecated(note = "Not enforced"))] 186 | pub const NC_MAX_ATTRS: c_int = 8192; 187 | #[cfg_attr(feature = "4.5.0", deprecated(note = "Not enforced"))] 188 | pub const NC_MAX_VARS: c_int = 8192; 189 | pub const NC_MAX_NAME: c_int = 256; 190 | pub const NC_MAX_VAR_DIMS: c_int = 1024; 191 | 192 | pub const NC_MAX_HDF4_NAME: c_int = NC_MAX_NAME; 193 | 194 | pub const NC_ENDIAN_NATIVE: c_int = 0; 195 | pub const NC_ENDIAN_LITTLE: c_int = 1; 196 | pub const NC_ENDIAN_BIG: c_int = 2; 197 | 198 | pub const NC_CHUNKED: c_int = 0; 199 | pub const NC_CONTIGUOUS: c_int = 1; 200 | #[cfg(feature = "4.7.4")] 201 | pub const NC_COMPACT: c_int = 2; 202 | #[cfg(feature = "4.8.1")] 203 | pub const NC_UNKNOWN_STORAGE: c_int = 3; 204 | #[cfg(feature = "4.8.1")] 205 | pub const NC_VIRTUAL: c_int = 4; 206 | 207 | pub const NC_NOCHECKSUM: c_int = 0; 208 | pub const NC_FLETCHER32: c_int = 1; 209 | 210 | pub const NC_NOSHUFFLE: c_int = 0; 211 | pub const NC_SHUFFLE: c_int = 1; 212 | 213 | #[cfg(feature = "4.6.0")] 214 | pub const NC_MIN_DEFLATE_LEVEL: c_int = 0; 215 | #[cfg(feature = "4.6.0")] 216 | pub const NC_MAX_DEFLATE_LEVEL: c_int = 9; 217 | 218 | pub const fn NC_ISSYSERR(err: c_int) -> bool { 219 | err > 0 220 | } 221 | 222 | pub const NC_NOERR: c_int = 0; 223 | pub const NC2_ERR: c_int = -1; 224 | 225 | pub const NC_EBADID: c_int = -33; 226 | pub const NC_ENFILE: c_int = -34; 227 | pub const NC_EEXIST: c_int = -35; 228 | pub const NC_EINVAL: c_int = -36; 229 | pub const NC_EPERM: c_int = -37; 230 | pub const NC_ENOTINDEFINE: c_int = -38; 231 | pub const NC_EINDEFINE: c_int = -39; 232 | pub const NC_EINVALCOORDS: c_int = -40; 233 | pub const NC_EMAXDIMS: c_int = -41; 234 | pub const NC_ENAMEINUSE: c_int = -42; 235 | pub const NC_ENOTATT: c_int = -43; 236 | pub const NC_EMAXATTS: c_int = -44; 237 | pub const NC_EBADTYPE: c_int = -45; 238 | pub const NC_EBADDIM: c_int = -46; 239 | pub const NC_EUNLIMPOS: c_int = -47; 240 | pub const NC_EMAXVARS: c_int = -48; 241 | pub const NC_ENOTVAR: c_int = -49; 242 | pub const NC_EGLOBAL: c_int = -50; 243 | pub const NC_ENOTNC: c_int = -51; 244 | pub const NC_ESTS: c_int = -52; 245 | pub const NC_EMAXNAME: c_int = -53; 246 | pub const NC_EUNLIMIT: c_int = -54; 247 | pub const NC_ENORECVARS: c_int = -55; 248 | pub const NC_ECHAR: c_int = -56; 249 | pub const NC_EEDGE: c_int = -57; 250 | pub const NC_ESTRIDE: c_int = -58; 251 | pub const NC_EBADNAME: c_int = -59; 252 | pub const NC_ERANGE: c_int = -60; 253 | pub const NC_ENOMEM: c_int = -61; 254 | pub const NC_EVARSIZE: c_int = -62; 255 | pub const NC_EDIMSIZE: c_int = -63; 256 | pub const NC_ETRUNC: c_int = -64; 257 | pub const NC_EAXISTYPE: c_int = -65; 258 | pub const NC_EDAP: c_int = -66; 259 | pub const NC_ECURL: c_int = -67; 260 | pub const NC_EIO: c_int = -68; 261 | pub const NC_ENODATA: c_int = -69; 262 | pub const NC_EDAPSVC: c_int = -70; 263 | pub const NC_EDAS: c_int = -71; 264 | pub const NC_EDDS: c_int = -72; 265 | pub const NC_EDMR: c_int = NC_EDDS; 266 | pub const NC_EDATADDS: c_int = -73; 267 | pub const NC_EDATADAP: c_int = NC_EDATADDS; 268 | pub const NC_EDAPURL: c_int = -74; 269 | pub const NC_EDAPCONSTRAINT: c_int = -75; 270 | pub const NC_ETRANSLATION: c_int = -76; 271 | pub const NC_EACCESS: c_int = -77; 272 | pub const NC_EAUTH: c_int = -78; 273 | pub const NC_ENOTFOUND: c_int = -90; 274 | pub const NC_ECANTREMOVE: c_int = -91; 275 | #[cfg(feature = "4.6.1")] 276 | pub const NC_EINTERNAL: c_int = -92; 277 | #[cfg(feature = "4.6.2")] 278 | pub const NC_EPNETCDF: c_int = -93; 279 | pub const NC4_FIRST_ERROR: c_int = -100; 280 | pub const NC_EHDFERR: c_int = -101; 281 | pub const NC_ECANTREAD: c_int = -102; 282 | pub const NC_ECANTWRITE: c_int = -103; 283 | pub const NC_ECANTCREATE: c_int = -104; 284 | pub const NC_EFILEMETA: c_int = -105; 285 | pub const NC_EDIMMETA: c_int = -106; 286 | pub const NC_EATTMETA: c_int = -107; 287 | pub const NC_EVARMETA: c_int = -108; 288 | pub const NC_ENOCOMPOUND: c_int = -109; 289 | pub const NC_EATTEXISTS: c_int = -110; 290 | pub const NC_ENOTNC4: c_int = -111; 291 | pub const NC_ESTRICTNC3: c_int = -112; 292 | pub const NC_ENOTNC3: c_int = -113; 293 | pub const NC_ENOPAR: c_int = -114; 294 | pub const NC_EPARINIT: c_int = -115; 295 | pub const NC_EBADGRPID: c_int = -116; 296 | pub const NC_EBADTYPID: c_int = -117; 297 | pub const NC_ETYPDEFINED: c_int = -118; 298 | pub const NC_EBADFIELD: c_int = -119; 299 | pub const NC_EBADCLASS: c_int = -120; 300 | pub const NC_EMAPTYPE: c_int = -121; 301 | pub const NC_ELATEFILL: c_int = -122; 302 | pub const NC_ELATEDEF: c_int = -123; 303 | pub const NC_EDIMSCALE: c_int = -124; 304 | pub const NC_ENOGRP: c_int = -125; 305 | pub const NC_ESTORAGE: c_int = -126; 306 | pub const NC_EBADCHUNK: c_int = -127; 307 | pub const NC_ENOTBUILT: c_int = -128; 308 | pub const NC_EDISKLESS: c_int = -129; 309 | pub const NC_ECANTEXTEND: c_int = -130; 310 | pub const NC_EMPI: c_int = -131; 311 | #[cfg(feature = "4.6.0")] 312 | pub const NC_EFILTER: c_int = -132; 313 | #[cfg(feature = "4.6.0")] 314 | pub const NC_ERCFILE: c_int = -133; 315 | #[cfg(feature = "4.6.0")] 316 | pub const NC_NULLPAD: c_int = -134; 317 | #[cfg(feature = "4.6.2")] 318 | pub const NC_EINMEMORY: c_int = -135; 319 | #[cfg(feature = "4.7.4")] 320 | pub const NC_ENOFILTER: c_int = -136; 321 | #[cfg(feature = "4.8.0")] 322 | pub const NC_ENCZARR: c_int = -137; 323 | #[cfg(feature = "4.8.0")] 324 | pub const NC_ES3: c_int = -138; 325 | #[cfg(feature = "4.8.0")] 326 | pub const NC_EEMPTY: c_int = -139; 327 | #[cfg(all(feature = "4.8.0", not(feature = "4.8.1")))] 328 | pub const NC_EFOUND: c_int = -140; 329 | #[cfg(feature = "4.8.1")] 330 | pub const NC_EOBJECT: c_int = -140; 331 | #[cfg(feature = "4.8.1")] 332 | pub const NC_ENOOBJECT: c_int = -141; 333 | #[cfg(feature = "4.8.1")] 334 | pub const NC_EPLUGIN: c_int = -142; 335 | 336 | #[rustfmt::skip] 337 | pub const NC4_LAST_ERROR: c_int = { 338 | #[cfg(not(feature = "4.6.0"))] 339 | { -131 } 340 | #[cfg(not(feature = "4.6.2"))] 341 | { -135 } 342 | #[cfg(all(feature = "4.6.2", not(feature = "4.7.4")))] 343 | { -136 } 344 | #[cfg(all(feature = "4.7.4", not(feature = "4.8.0")))] 345 | { -137 } 346 | #[cfg(all(feature = "4.8.0", not(feature = "4.8.1")))] 347 | { -140 } 348 | #[cfg(feature = "4.8.1")] 349 | { -142 } 350 | }; 351 | 352 | pub const NC_EURL: c_int = NC_EDAPURL; 353 | pub const NC_ECONSTRAINT: c_int = NC_EDAPCONSTRAINT; 354 | 355 | pub const DIM_WITHOUT_VARIABLE: &[u8] = b"This is a netCDF dimension but not a netCDF variable.\0"; 356 | 357 | mod netcdf_2 { 358 | use super::*; 359 | 360 | pub const FILL_BYTE: i8 = NC_FILL_BYTE; 361 | pub const FILL_CHAR: u8 = NC_FILL_CHAR; 362 | pub const FILL_SHORT: i16 = NC_FILL_SHORT; 363 | pub const FILL_LONG: i32 = NC_FILL_INT; 364 | pub const FILL_FLOAT: f32 = NC_FILL_FLOAT; 365 | pub const FILL_DOUBLE: f64 = NC_FILL_DOUBLE; 366 | 367 | #[cfg_attr( 368 | feature = "4.5.0", 369 | deprecated(note = "Not enforced"), 370 | allow(deprecated) 371 | )] 372 | pub const MAX_NC_DIMS: c_int = NC_MAX_DIMS; 373 | #[cfg_attr( 374 | feature = "4.5.0", 375 | deprecated(note = "Not enforced"), 376 | allow(deprecated) 377 | )] 378 | pub const MAX_NC_ATTRS: c_int = NC_MAX_ATTRS; 379 | #[cfg_attr( 380 | feature = "4.5.0", 381 | deprecated(note = "Not enforced"), 382 | allow(deprecated) 383 | )] 384 | pub const MAX_NC_VARS: c_int = NC_MAX_VARS; 385 | pub const MAX_NC_NAME: c_int = NC_MAX_NAME; 386 | pub const MAX_VAR_DIMS: c_int = NC_MAX_VAR_DIMS; 387 | 388 | pub const NC_ENTOOL: c_int = NC_EMAXNAME; 389 | pub const NC_EXDR: c_int = -32; 390 | pub const NC_SYSERR: c_int = -31; 391 | 392 | pub const NC_FATAL: c_int = 1; 393 | pub const NC_VERBOSE: c_int = 2; 394 | } 395 | 396 | pub use netcdf_2::*; 397 | 398 | pub const NC_TURN_OFF_LOGGING: c_int = -1; 399 | -------------------------------------------------------------------------------- /netcdf/src/file.rs: -------------------------------------------------------------------------------- 1 | //! Open, create, and append netcdf files 2 | #![allow(clippy::similar_names)] 3 | 4 | use std::marker::PhantomData; 5 | use std::path; 6 | 7 | use netcdf_sys::*; 8 | 9 | use super::attribute::{Attribute, AttributeValue}; 10 | use super::dimension::{self, Dimension}; 11 | use super::error; 12 | use super::group::{Group, GroupMut}; 13 | use super::variable::{NcPutGet, Variable, VariableMut}; 14 | use crate::group::{get_parent_ncid_and_stem, try_get_ncid, try_get_parent_ncid_and_stem}; 15 | 16 | #[derive(Debug)] 17 | #[repr(transparent)] 18 | pub(crate) struct RawFile { 19 | ncid: nc_type, 20 | } 21 | 22 | impl RawFile { 23 | fn close(self) -> error::Result<()> { 24 | let Self { ncid } = self; 25 | error::checked(super::with_lock(|| unsafe { nc_close(ncid) })) 26 | } 27 | } 28 | 29 | impl Drop for RawFile { 30 | fn drop(&mut self) { 31 | // Can't really do much with an error here 32 | let ncid = self.ncid; 33 | let _err = error::checked(super::with_lock(|| unsafe { nc_close(ncid) })); 34 | } 35 | } 36 | 37 | #[cfg(unix)] 38 | fn get_ffi_from_path(path: &path::Path) -> Vec { 39 | use std::os::unix::ffi::OsStrExt; 40 | let mut bytes = path.as_os_str().as_bytes().to_vec(); 41 | bytes.push(0); 42 | bytes 43 | } 44 | #[cfg(not(unix))] 45 | fn get_ffi_from_path(path: &path::Path) -> std::ffi::CString { 46 | std::ffi::CString::new(path.to_str().unwrap()).unwrap() 47 | } 48 | 49 | bitflags::bitflags! { 50 | /// Options for opening, creating, and appending files 51 | #[derive(Default)] 52 | pub struct Options: nc_type { 53 | /// Open with write permissions (use `append` for a mutable file) 54 | const WRITE = NC_WRITE; 55 | /// Overwrite existing file 56 | const NOCLOBBER = NC_NOCLOBBER; 57 | /// Reads file into memory 58 | const DISKLESS = NC_DISKLESS; 59 | /// Use 64 bit dimensions and sizes (`CDF-5` format) 60 | const _64BIT_DATA = NC_64BIT_DATA; 61 | /// Use 64 bit file offsets 62 | const _64BIT_OFFSET = NC_64BIT_OFFSET; 63 | /// Use a subset compatible with older software 64 | const CLASSIC = NC_CLASSIC_MODEL; 65 | /// Limits internal caching 66 | const SHARE = NC_SHARE; 67 | /// Use the `hdf5` storage format 68 | const NETCDF4 = NC_NETCDF4; 69 | /// Read from memory 70 | const INMEMORY = NC_INMEMORY; 71 | } 72 | } 73 | 74 | impl RawFile { 75 | /// Open a `netCDF` file in read only mode. 76 | pub(crate) fn open_with(path: &path::Path, options: Options) -> error::Result { 77 | let f = get_ffi_from_path(path); 78 | let mut ncid: nc_type = 0; 79 | unsafe { 80 | error::checked(super::with_lock(|| { 81 | nc_open(f.as_ptr().cast(), options.bits(), &mut ncid) 82 | }))?; 83 | } 84 | Ok(File(Self { ncid })) 85 | } 86 | 87 | /// Open a `netCDF` file in append mode (read/write). 88 | pub(crate) fn append_with(path: &path::Path, options: Options) -> error::Result { 89 | let file = Self::open_with(path, options | Options::WRITE)?; 90 | Ok(FileMut(file)) 91 | } 92 | 93 | /// Create a new `netCDF` file 94 | pub(crate) fn create_with(path: &path::Path, options: Options) -> error::Result { 95 | let f = get_ffi_from_path(path); 96 | let mut ncid: nc_type = -1; 97 | unsafe { 98 | error::checked(super::with_lock(|| { 99 | nc_create(f.as_ptr().cast(), options.bits(), &mut ncid) 100 | }))?; 101 | } 102 | 103 | Ok(FileMut(File(Self { ncid }))) 104 | } 105 | 106 | #[cfg(feature = "has-mmap")] 107 | pub(crate) fn open_from_memory<'buffer>( 108 | name: Option<&str>, 109 | mem: &'buffer [u8], 110 | ) -> error::Result> { 111 | let cstr = std::ffi::CString::new(name.unwrap_or("/")).unwrap(); 112 | let mut ncid = 0; 113 | unsafe { 114 | error::checked(super::with_lock(|| { 115 | nc_open_mem( 116 | cstr.as_ptr(), 117 | NC_NOWRITE, 118 | mem.len(), 119 | mem.as_ptr().cast_mut().cast(), 120 | &mut ncid, 121 | ) 122 | }))?; 123 | } 124 | 125 | Ok(FileMem(File(Self { ncid }), PhantomData)) 126 | } 127 | } 128 | 129 | #[derive(Debug)] 130 | /// Read only accessible file 131 | #[allow(clippy::module_name_repetitions)] 132 | #[repr(transparent)] 133 | pub struct File(RawFile); 134 | 135 | impl File { 136 | /// path used to open/create the file 137 | /// 138 | /// #Errors 139 | /// 140 | /// Netcdf layer could fail, or the resulting path 141 | /// could contain an invalid UTF8 sequence 142 | pub fn path(&self) -> error::Result { 143 | let name: Vec = { 144 | let mut pathlen = 0; 145 | unsafe { 146 | error::checked(super::with_lock(|| { 147 | nc_inq_path(self.0.ncid, &mut pathlen, std::ptr::null_mut()) 148 | }))?; 149 | } 150 | let mut name = vec![0_u8; pathlen + 1]; 151 | unsafe { 152 | error::checked(super::with_lock(|| { 153 | nc_inq_path(self.0.ncid, std::ptr::null_mut(), name.as_mut_ptr().cast()) 154 | }))?; 155 | } 156 | name.truncate(pathlen); 157 | name 158 | }; 159 | 160 | #[cfg(not(unix))] 161 | { 162 | Ok(std::path::PathBuf::from(String::from_utf8(name)?)) 163 | } 164 | #[cfg(unix)] 165 | { 166 | use std::os::unix::ffi::OsStrExt; 167 | let osstr = std::ffi::OsStr::from_bytes(&name); 168 | Ok(std::path::PathBuf::from(osstr)) 169 | } 170 | } 171 | 172 | /// Main entrypoint for interacting with the netcdf file. 173 | pub fn root(&self) -> Option { 174 | let mut format = 0; 175 | unsafe { error::checked(super::with_lock(|| nc_inq_format(self.ncid(), &mut format))) } 176 | .unwrap(); 177 | 178 | match format { 179 | NC_FORMAT_NETCDF4 | NC_FORMAT_NETCDF4_CLASSIC => Some(Group { 180 | ncid: self.ncid(), 181 | _file: PhantomData, 182 | }), 183 | _ => None, 184 | } 185 | } 186 | 187 | fn ncid(&self) -> nc_type { 188 | self.0.ncid 189 | } 190 | 191 | /// Get a variable from the group 192 | pub fn variable<'f>(&'f self, name: &str) -> Option> { 193 | let (ncid, name) = 194 | super::group::try_get_parent_ncid_and_stem(self.ncid(), name).unwrap()?; 195 | Variable::find_from_name(ncid, name).unwrap() 196 | } 197 | /// Iterate over all variables in a group 198 | pub fn variables(&self) -> impl Iterator { 199 | super::variable::variables_at_ncid(self.ncid()) 200 | .unwrap() 201 | .map(Result::unwrap) 202 | } 203 | /// Get a single attribute 204 | pub fn attribute<'f>(&'f self, name: &str) -> Option> { 205 | let (ncid, name) = try_get_parent_ncid_and_stem(self.ncid(), name).unwrap()?; 206 | Attribute::find_from_name(ncid, None, name).unwrap() 207 | } 208 | /// Get all attributes in the root group 209 | pub fn attributes(&self) -> impl Iterator { 210 | crate::attribute::AttributeIterator::new(self.0.ncid, None) 211 | .unwrap() 212 | .map(Result::unwrap) 213 | } 214 | 215 | /// Get a single dimension 216 | pub fn dimension<'f>(&self, name: &str) -> Option> { 217 | let (ncid, name) = 218 | super::group::try_get_parent_ncid_and_stem(self.ncid(), name).unwrap()?; 219 | super::dimension::dimension_from_name(ncid, name).unwrap() 220 | } 221 | /// Iterator over all dimensions in the root group 222 | pub fn dimensions(&self) -> impl Iterator { 223 | super::dimension::dimensions_from_location(self.ncid()) 224 | .unwrap() 225 | .map(Result::unwrap) 226 | } 227 | 228 | /// Get a group 229 | /// 230 | /// # Errors 231 | /// 232 | /// Not a `netCDF-4` file 233 | pub fn group<'f>(&'f self, name: &str) -> error::Result>> { 234 | let (ncid, name) = get_parent_ncid_and_stem(self.ncid(), name)?; 235 | try_get_ncid(ncid, name).map(|ncid: Option| { 236 | ncid.map(|ncid| Group { 237 | ncid, 238 | _file: PhantomData, 239 | }) 240 | }) 241 | } 242 | /// Iterator over all subgroups in the root group 243 | /// 244 | /// # Errors 245 | /// 246 | /// Not a `netCDF-4` file 247 | pub fn groups(&self) -> error::Result> { 248 | super::group::groups_at_ncid(self.ncid()) 249 | } 250 | /// Return all types in the root group 251 | pub fn types(&self) -> error::Result> { 252 | super::types::all_at_location(self.ncid()).map(|x| x.map(Result::unwrap)) 253 | } 254 | 255 | /// Close the file 256 | /// 257 | /// Note: This is called automatically by `Drop`, but can be useful 258 | /// if flushing data or closing the file would result in an error. 259 | pub fn close(self) -> error::Result<()> { 260 | let Self(file) = self; 261 | file.close() 262 | } 263 | } 264 | 265 | /// Mutable access to file. 266 | /// 267 | /// This type derefs to a [`File`](File), which means [`FileMut`](Self) 268 | /// can be used where [`File`](File) is expected 269 | #[derive(Debug)] 270 | #[allow(clippy::module_name_repetitions)] 271 | #[repr(transparent)] 272 | pub struct FileMut(File); 273 | 274 | impl std::ops::Deref for FileMut { 275 | type Target = File; 276 | fn deref(&self) -> &Self::Target { 277 | &self.0 278 | } 279 | } 280 | 281 | impl FileMut { 282 | /// Mutable access to the root group 283 | /// 284 | /// Return None if this can't be a root group 285 | pub fn root_mut(&mut self) -> Option { 286 | self.root().map(|root| GroupMut(root, PhantomData)) 287 | } 288 | /// Get a mutable variable from the group 289 | pub fn variable_mut<'f>(&'f mut self, name: &str) -> Option> { 290 | self.variable(name).map(|var| VariableMut(var, PhantomData)) 291 | } 292 | /// Iterate over all variables in the root group, with mutable access 293 | /// 294 | /// # Examples 295 | /// Use this to get multiple writable variables 296 | /// ```no_run 297 | /// # fn main() -> Result<(), Box> { 298 | /// let mut file = netcdf::append("file.nc")?; 299 | /// let mut vars = file.variables_mut().collect::>(); 300 | /// vars[0].put_value(1_u8, [2, 5])?; 301 | /// vars[1].put_value(1_u8, [5, 2])?; 302 | /// # Ok(()) } 303 | /// ``` 304 | pub fn variables_mut(&mut self) -> impl Iterator { 305 | self.variables().map(|var| VariableMut(var, PhantomData)) 306 | } 307 | 308 | /// Mutable access to subgroup 309 | /// 310 | /// # Errors 311 | /// 312 | /// File does not support groups 313 | pub fn group_mut<'f>(&'f mut self, name: &str) -> error::Result>> { 314 | self.group(name) 315 | .map(|g| g.map(|g| GroupMut(g, PhantomData))) 316 | } 317 | /// Iterator over all groups (mutable access) 318 | /// 319 | /// # Errors 320 | /// 321 | /// File does not support groups 322 | pub fn groups_mut(&mut self) -> error::Result> { 323 | self.groups().map(|g| g.map(|g| GroupMut(g, PhantomData))) 324 | } 325 | 326 | /// Add an attribute to the root group 327 | pub fn add_attribute<'a, T>(&'a mut self, name: &str, val: T) -> error::Result> 328 | where 329 | T: Into, 330 | { 331 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 332 | Attribute::put(ncid, NC_GLOBAL, name, val.into()) 333 | } 334 | 335 | /// Adds a dimension with the given name and size. A size of zero gives an unlimited dimension 336 | pub fn add_dimension<'f>(&'f mut self, name: &str, len: usize) -> error::Result> { 337 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 338 | super::dimension::add_dimension_at(ncid, name, len) 339 | } 340 | /// Adds a dimension with unbounded size 341 | pub fn add_unlimited_dimension(&mut self, name: &str) -> error::Result { 342 | self.add_dimension(name, 0) 343 | } 344 | 345 | /// Add an empty group to the dataset 346 | pub fn add_group<'f>(&'f mut self, name: &str) -> error::Result> { 347 | Ok(GroupMut( 348 | Group { 349 | ncid: super::group::add_group_at_path(self.ncid(), name)?, 350 | _file: PhantomData, 351 | }, 352 | PhantomData, 353 | )) 354 | } 355 | 356 | /// Create a Variable into the dataset, with no data written into it 357 | /// 358 | /// Dimensions are identified using the name of the dimension, and will recurse upwards 359 | /// if not found in the current group. 360 | pub fn add_variable<'f, T>( 361 | &'f mut self, 362 | name: &str, 363 | dims: &[&str], 364 | ) -> error::Result> 365 | where 366 | T: NcPutGet, 367 | { 368 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 369 | VariableMut::add_from_str(ncid, T::NCTYPE, name, dims) 370 | } 371 | 372 | /// Create a variable with the specified type 373 | pub fn add_variable_with_type<'f>( 374 | &'f mut self, 375 | name: &str, 376 | dims: &[&str], 377 | typ: &super::types::VariableType, 378 | ) -> error::Result> { 379 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 380 | VariableMut::add_from_str(ncid, typ.id(), name, dims) 381 | } 382 | 383 | /// Add an opaque datatype, with `size` bytes 384 | pub fn add_opaque_type( 385 | &mut self, 386 | name: &str, 387 | size: usize, 388 | ) -> error::Result { 389 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 390 | super::types::OpaqueType::add(ncid, name, size) 391 | } 392 | /// Add a variable length datatype 393 | pub fn add_vlen_type( 394 | &mut self, 395 | name: &str, 396 | ) -> error::Result { 397 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 398 | super::types::VlenType::add::(ncid, name) 399 | } 400 | /// Add an enum datatype 401 | pub fn add_enum_type( 402 | &mut self, 403 | name: &str, 404 | mappings: &[(&str, T)], 405 | ) -> error::Result { 406 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 407 | super::types::EnumType::add::(ncid, name, mappings) 408 | } 409 | 410 | /// Build a compound type 411 | pub fn add_compound_type( 412 | &mut self, 413 | name: &str, 414 | ) -> error::Result { 415 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 416 | super::types::CompoundType::add(ncid, name) 417 | } 418 | 419 | /// Adds a variable with a basic type of string 420 | pub fn add_string_variable<'f>( 421 | &'f mut self, 422 | name: &str, 423 | dims: &[&str], 424 | ) -> error::Result> { 425 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 426 | VariableMut::add_from_str(ncid, NC_STRING, name, dims) 427 | } 428 | /// Adds a variable from a set of unique identifiers, recursing upwards 429 | /// from the current group if necessary. 430 | pub fn add_variable_from_identifiers<'f, T>( 431 | &'f mut self, 432 | name: &str, 433 | dims: &[dimension::DimensionIdentifier], 434 | ) -> error::Result> 435 | where 436 | T: NcPutGet, 437 | { 438 | let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?; 439 | super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE) 440 | } 441 | 442 | /// Flush pending buffers to disk to minimise data loss in case of termination. 443 | /// 444 | /// Note: When writing and reading from the same file from multiple processes 445 | /// it is recommended to instead open the file in both the reader and 446 | /// writer process with the [`Options::SHARE`] flag. 447 | pub fn sync(&self) -> error::Result<()> { 448 | error::checked(super::with_lock(|| unsafe { 449 | netcdf_sys::nc_sync(self.ncid()) 450 | })) 451 | } 452 | 453 | /// Close the file 454 | /// 455 | /// Note: This is called automatically by `Drop`, but can be useful 456 | /// if flushing data or closing the file would result in an error. 457 | pub fn close(self) -> error::Result<()> { 458 | let Self(File(file)) = self; 459 | file.close() 460 | } 461 | } 462 | 463 | #[cfg(feature = "has-mmap")] 464 | /// The memory mapped file is kept in this structure to extend 465 | /// the lifetime of the buffer. 466 | /// 467 | /// Access a [`File`] through the `Deref` trait, 468 | /// ```no_run 469 | /// # fn main() -> Result<(), Box> { 470 | /// let buffer = &[0, 42, 1, 2]; 471 | /// let file = &netcdf::open_mem(None, buffer)?; 472 | /// 473 | /// let variables = file.variables(); 474 | /// # Ok(()) } 475 | /// ``` 476 | #[allow(clippy::module_name_repetitions)] 477 | pub struct FileMem<'buffer>(File, std::marker::PhantomData<&'buffer [u8]>); 478 | 479 | #[cfg(feature = "has-mmap")] 480 | impl<'a> std::ops::Deref for FileMem<'a> { 481 | type Target = File; 482 | fn deref(&self) -> &Self::Target { 483 | &self.0 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /netcdf-sys/src/dispatch.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::{c_char, c_int, c_longlong, c_uint, c_void}; 2 | 3 | use super::nc_type; 4 | 5 | pub const NC_DISPATCH_VERSION: usize = 5; 6 | 7 | #[repr(C)] 8 | #[derive(Copy, Clone)] 9 | pub struct NC_Dispatch { 10 | pub model: c_int, 11 | pub dispatch_version: c_int, 12 | 13 | pub create: Option< 14 | unsafe extern "C" fn( 15 | path: *const c_char, 16 | cmode: c_int, 17 | initialsz: usize, 18 | basepe: c_int, 19 | chunksizehintp: *mut usize, 20 | parameters: *mut c_void, 21 | table: *const NC_Dispatch, 22 | ncid: c_int, 23 | ) -> c_int, 24 | >, 25 | pub open: Option< 26 | unsafe extern "C" fn( 27 | path: *const c_char, 28 | mode: c_int, 29 | basepe: c_int, 30 | chunksizehintp: *mut usize, 31 | parameters: *mut c_void, 32 | table: *const NC_Dispatch, 33 | ncid: c_int, 34 | ) -> c_int, 35 | >, 36 | pub redef: Option c_int>, 37 | pub _enddef: Option c_int>, 38 | pub sync: Option c_int>, 39 | pub abort: Option c_int>, 40 | pub close: Option c_int>, 41 | pub set_fill: Option c_int>, 42 | pub inq_format: Option c_int>, 43 | pub inq_format_extended: Option c_int>, 44 | pub inq: Option< 45 | unsafe extern "C" fn(c_int, *mut c_int, *mut c_int, *mut c_int, *mut c_int) -> c_int, 46 | >, 47 | pub inq_type: Option c_int>, 48 | pub def_dim: Option c_int>, 49 | pub inq_dimid: Option c_int>, 50 | pub inq_dim: Option c_int>, 51 | pub inq_unlimdim: Option c_int>, 52 | pub rename_dim: Option c_int>, 53 | pub inq_att: Option< 54 | unsafe extern "C" fn(c_int, c_int, *const c_char, *mut nc_type, *mut usize) -> c_int, 55 | >, 56 | pub inq_attid: Option c_int>, 57 | pub inq_attname: Option c_int>, 58 | pub rename_att: 59 | Option c_int>, 60 | pub del_att: Option c_int>, 61 | pub get_att: 62 | Option c_int>, 63 | pub put_att: Option< 64 | unsafe extern "C" fn( 65 | c_int, 66 | c_int, 67 | *const c_char, 68 | nc_type, 69 | usize, 70 | *const c_void, 71 | nc_type, 72 | ) -> c_int, 73 | >, 74 | pub def_var: Option< 75 | unsafe extern "C" fn( 76 | c_int, 77 | *const c_char, 78 | nc_type, 79 | c_int, 80 | *const c_int, 81 | *mut c_int, 82 | ) -> c_int, 83 | >, 84 | pub inq_varid: Option c_int>, 85 | pub rename_var: Option c_int>, 86 | pub get_vara: Option< 87 | unsafe extern "C" fn( 88 | c_int, 89 | c_int, 90 | *const usize, 91 | *const usize, 92 | *mut c_void, 93 | nc_type, 94 | ) -> c_int, 95 | >, 96 | pub put_vara: Option< 97 | unsafe extern "C" fn( 98 | c_int, 99 | c_int, 100 | *const usize, 101 | *const usize, 102 | *const c_void, 103 | nc_type, 104 | ) -> c_int, 105 | >, 106 | pub get_vars: Option< 107 | unsafe extern "C" fn( 108 | c_int, 109 | c_int, 110 | *const usize, 111 | *const usize, 112 | *const isize, 113 | *mut c_void, 114 | nc_type, 115 | ) -> c_int, 116 | >, 117 | pub put_vars: Option< 118 | unsafe extern "C" fn( 119 | c_int, 120 | c_int, 121 | *const usize, 122 | *const usize, 123 | *const isize, 124 | *const c_void, 125 | nc_type, 126 | ) -> c_int, 127 | >, 128 | pub get_varm: Option< 129 | unsafe extern "C" fn( 130 | c_int, 131 | c_int, 132 | *const usize, 133 | *const usize, 134 | *const isize, 135 | *const isize, 136 | *mut c_void, 137 | nc_type, 138 | ) -> c_int, 139 | >, 140 | pub put_varm: Option< 141 | unsafe extern "C" fn( 142 | c_int, 143 | c_int, 144 | *const usize, 145 | *const usize, 146 | *const isize, 147 | *const isize, 148 | *const c_void, 149 | nc_type, 150 | ) -> c_int, 151 | >, 152 | pub inq_var_all: Option< 153 | unsafe extern "C" fn( 154 | ncid: c_int, 155 | varid: c_int, 156 | name: *mut c_char, 157 | xtypep: *mut nc_type, 158 | ndimsp: *mut c_int, 159 | dimidsp: *mut c_int, 160 | nattsp: *mut c_int, 161 | shufflep: *mut c_int, 162 | deflatep: *mut c_int, 163 | deflate_levelp: *mut c_int, 164 | fletcher32p: *mut c_int, 165 | contiguousp: *mut c_int, 166 | chunksizesp: *mut usize, 167 | no_fill: *mut c_int, 168 | fill_valuep: *mut c_void, 169 | endiannessp: *mut c_int, 170 | idp: *mut c_uint, 171 | nparamsp: *mut usize, 172 | params: *mut c_uint, 173 | ) -> c_int, 174 | >, 175 | pub var_par_access: Option c_int>, 176 | pub def_var_fill: Option c_int>, 177 | pub show_metadata: Option c_int>, 178 | pub inq_unlimdims: Option c_int>, 179 | pub inq_ncid: Option c_int>, 180 | pub inq_grps: Option c_int>, 181 | pub inq_grpname: Option c_int>, 182 | pub inq_grpname_full: Option c_int>, 183 | pub inq_grp_parent: Option c_int>, 184 | pub inq_grp_full_ncid: Option c_int>, 185 | pub inq_varids: 186 | Option c_int>, 187 | pub inq_dimids: 188 | Option c_int>, 189 | pub inq_typeids: 190 | Option c_int>, 191 | pub inq_type_equal: 192 | Option c_int>, 193 | pub def_grp: Option c_int>, 194 | pub rename_grp: Option c_int>, 195 | pub inq_user_type: Option< 196 | unsafe extern "C" fn( 197 | c_int, 198 | nc_type, 199 | *mut c_char, 200 | *mut usize, 201 | *mut nc_type, 202 | *mut usize, 203 | *mut c_int, 204 | ) -> c_int, 205 | >, 206 | pub inq_typeid: Option c_int>, 207 | pub def_compound: 208 | Option c_int>, 209 | pub insert_compound: 210 | Option c_int>, 211 | pub insert_array_compound: Option< 212 | unsafe extern "C" fn( 213 | c_int, 214 | nc_type, 215 | *const c_char, 216 | usize, 217 | nc_type, 218 | c_int, 219 | *const c_int, 220 | ) -> c_int, 221 | >, 222 | pub inq_compound_field: Option< 223 | unsafe extern "C" fn( 224 | c_int, 225 | nc_type, 226 | c_int, 227 | *mut c_char, 228 | *mut usize, 229 | *mut nc_type, 230 | *mut c_int, 231 | *mut c_int, 232 | ) -> c_int, 233 | >, 234 | pub inq_compound_fieldindex: 235 | Option c_int>, 236 | pub def_vlen: Option< 237 | unsafe extern "C" fn( 238 | _: c_int, 239 | _: *const c_char, 240 | base_typeid: nc_type, 241 | _: *mut nc_type, 242 | ) -> c_int, 243 | >, 244 | pub put_vlen_element: 245 | Option c_int>, 246 | pub get_vlen_element: 247 | Option c_int>, 248 | pub def_enum: 249 | Option c_int>, 250 | pub insert_enum: 251 | Option c_int>, 252 | pub inq_enum_member: 253 | Option c_int>, 254 | pub inq_enum_ident: 255 | Option c_int>, 256 | pub def_opaque: 257 | Option c_int>, 258 | pub def_var_deflate: Option c_int>, 259 | pub def_var_fletcher32: Option c_int>, 260 | pub def_var_chunking: Option c_int>, 261 | pub def_var_endian: Option c_int>, 262 | pub def_var_filter: 263 | Option c_int>, 264 | pub set_var_chunk_cache: Option c_int>, 265 | pub get_var_chunk_cache: Option< 266 | unsafe extern "C" fn( 267 | ncid: c_int, 268 | varid: c_int, 269 | sizep: *mut usize, 270 | nelemsp: *mut usize, 271 | preemptionp: *mut f32, 272 | ) -> c_int, 273 | >, 274 | pub inq_var_filter_ids: Option< 275 | unsafe extern "C" fn( 276 | ncid: c_int, 277 | varid: c_int, 278 | nfilters: *mut usize, 279 | filterids: *mut c_uint, 280 | ) -> c_int, 281 | >, 282 | pub inq_var_filter_info: Option< 283 | unsafe extern "C" fn( 284 | ncid: c_int, 285 | varid: c_int, 286 | id: c_uint, 287 | nparams: *mut usize, 288 | params: *mut c_uint, 289 | ) -> c_int, 290 | >, 291 | pub def_var_quantize: Option< 292 | unsafe extern "C" fn(ncid: c_int, varid: c_int, quantize_mode: c_int, nsd: c_int) -> c_int, 293 | >, 294 | pub inq_var_quantize: Option< 295 | unsafe extern "C" fn( 296 | ncid: c_int, 297 | varid: c_int, 298 | quantize_modep: *mut c_int, 299 | nsdp: *mut c_int, 300 | ) -> c_int, 301 | >, 302 | pub inq_filter_avail: Option c_int>, 303 | } 304 | 305 | extern "C" { 306 | pub fn NC_RO_create( 307 | path: *const c_char, 308 | cmode: c_int, 309 | initialsz: usize, 310 | basepe: c_int, 311 | chunksizehintp: *mut usize, 312 | parameters: *mut c_void, 313 | _: *const NC_Dispatch, 314 | _: c_int, 315 | ) -> c_int; 316 | pub fn NC_RO_redef(ncid: c_int) -> c_int; 317 | pub fn NC_RO__enddef( 318 | ncid: c_int, 319 | h_minfree: usize, 320 | v_align: usize, 321 | v_minfree: usize, 322 | r_align: usize, 323 | ) -> c_int; 324 | pub fn NC_RO_sync(ncid: c_int) -> c_int; 325 | pub fn NC_RO_def_var_fill(_: c_int, _: c_int, _: c_int, _: *const c_void) -> c_int; 326 | pub fn NC_RO_rename_att( 327 | ncid: c_int, 328 | varid: c_int, 329 | name: *const c_char, 330 | newname: *const c_char, 331 | ) -> c_int; 332 | pub fn NC_RO_del_att(ncid: c_int, varid: c_int, _: *const c_char) -> c_int; 333 | pub fn NC_RO_put_att( 334 | ncid: c_int, 335 | varid: c_int, 336 | name: *const c_char, 337 | datatype: nc_type, 338 | len: usize, 339 | value: *const c_void, 340 | _: nc_type, 341 | ) -> c_int; 342 | pub fn NC_RO_def_var( 343 | ncid: c_int, 344 | name: *const c_char, 345 | xtype: nc_type, 346 | ndims: c_int, 347 | dimidsp: *const c_int, 348 | varidp: *mut c_int, 349 | ) -> c_int; 350 | pub fn NC_RO_rename_var(ncid: c_int, varid: c_int, name: *const c_char) -> c_int; 351 | pub fn NC_RO_put_vara( 352 | ncid: c_int, 353 | varid: c_int, 354 | start: *const usize, 355 | count: *const usize, 356 | value: *const c_void, 357 | _: nc_type, 358 | ) -> c_int; 359 | pub fn NC_RO_def_dim(ncid: c_int, name: *const c_char, len: usize, idp: *mut c_int) -> c_int; 360 | pub fn NC_RO_rename_dim(ncid: c_int, dimid: c_int, name: *const c_char) -> c_int; 361 | pub fn NC_RO_set_fill(ncid: c_int, fillmode: c_int, old_modep: *mut c_int) -> c_int; 362 | pub fn NC_NOTNC4_def_var_filter( 363 | _: c_int, 364 | _: c_int, 365 | _: c_uint, 366 | _: usize, 367 | _: *const c_uint, 368 | ) -> c_int; 369 | pub fn NC_NOTNC4_inq_var_filter_ids( 370 | ncid: c_int, 371 | varid: c_int, 372 | nfilters: *mut usize, 373 | filterids: *mut c_uint, 374 | ) -> c_int; 375 | pub fn NC_NOTNC4_inq_var_filter_info( 376 | ncid: c_int, 377 | varid: c_int, 378 | id: c_uint, 379 | nparams: *mut usize, 380 | params: *mut c_uint, 381 | ) -> c_int; 382 | pub fn NC_NOOP_inq_var_filter_ids( 383 | ncid: c_int, 384 | varid: c_int, 385 | nfilters: *mut usize, 386 | filterids: *mut c_uint, 387 | ) -> c_int; 388 | pub fn NC_NOOP_inq_var_filter_info( 389 | ncid: c_int, 390 | varid: c_int, 391 | id: c_uint, 392 | nparams: *mut usize, 393 | params: *mut c_uint, 394 | ) -> c_int; 395 | pub fn NC_NOOP_inq_filter_avail(ncid: c_int, id: c_uint) -> c_int; 396 | pub fn NC_NOTNC4_def_grp(_: c_int, _: *const c_char, _: *mut c_int) -> c_int; 397 | pub fn NC_NOTNC4_rename_grp(_: c_int, _: *const c_char) -> c_int; 398 | pub fn NC_NOTNC4_def_compound(_: c_int, _: usize, _: *const c_char, _: *mut nc_type) -> c_int; 399 | pub fn NC_NOTNC4_insert_compound( 400 | _: c_int, 401 | _: nc_type, 402 | _: *const c_char, 403 | _: usize, 404 | _: nc_type, 405 | ) -> c_int; 406 | pub fn NC_NOTNC4_insert_array_compound( 407 | _: c_int, 408 | _: nc_type, 409 | _: *const c_char, 410 | _: usize, 411 | _: nc_type, 412 | _: c_int, 413 | _: *const c_int, 414 | ) -> c_int; 415 | pub fn NC_NOTNC4_inq_typeid(_: c_int, _: *const c_char, _: *mut nc_type) -> c_int; 416 | pub fn NC_NOTNC4_inq_compound_field( 417 | _: c_int, 418 | _: nc_type, 419 | _: c_int, 420 | _: *mut c_char, 421 | _: *mut usize, 422 | _: *mut nc_type, 423 | _: *mut c_int, 424 | _: *mut c_int, 425 | ) -> c_int; 426 | pub fn NC_NOTNC4_inq_compound_fieldindex( 427 | _: c_int, 428 | _: nc_type, 429 | _: *const c_char, 430 | _: *mut c_int, 431 | ) -> c_int; 432 | pub fn NC_NOTNC4_def_vlen( 433 | _: c_int, 434 | _: *const c_char, 435 | base_typeid: nc_type, 436 | _: *mut nc_type, 437 | ) -> c_int; 438 | pub fn NC_NOTNC4_put_vlen_element( 439 | _: c_int, 440 | _: c_int, 441 | _: *mut c_void, 442 | _: usize, 443 | _: *const c_void, 444 | ) -> c_int; 445 | pub fn NC_NOTNC4_get_vlen_element( 446 | _: c_int, 447 | _: c_int, 448 | _: *const c_void, 449 | _: *mut usize, 450 | _: *mut c_void, 451 | ) -> c_int; 452 | pub fn NC_NOTNC4_def_enum(_: c_int, _: nc_type, _: *const c_char, _: *mut nc_type) -> c_int; 453 | pub fn NC_NOTNC4_insert_enum(_: c_int, _: nc_type, _: *const c_char, _: *const c_void) 454 | -> c_int; 455 | pub fn NC_NOTNC4_inq_enum_member( 456 | _: c_int, 457 | _: nc_type, 458 | _: c_int, 459 | _: *mut c_char, 460 | _: *mut c_void, 461 | ) -> c_int; 462 | pub fn NC_NOTNC4_inq_enum_ident(_: c_int, _: nc_type, _: c_longlong, _: *mut c_char) -> c_int; 463 | pub fn NC_NOTNC4_def_opaque(_: c_int, _: usize, _: *const c_char, _: *mut nc_type) -> c_int; 464 | pub fn NC_NOTNC4_def_var_deflate(_: c_int, _: c_int, _: c_int, _: c_int, _: c_int) -> c_int; 465 | pub fn NC_NOTNC4_def_var_fletcher32(_: c_int, _: c_int, _: c_int) -> c_int; 466 | pub fn NC_NOTNC4_def_var_chunking(_: c_int, _: c_int, _: c_int, _: *const usize) -> c_int; 467 | pub fn NC_NOTNC4_def_var_endian(_: c_int, _: c_int, _: c_int) -> c_int; 468 | pub fn NC_NOTNC4_set_var_chunk_cache(_: c_int, _: c_int, _: usize, _: usize, _: f32) -> c_int; 469 | pub fn NC_NOTNC4_get_var_chunk_cache( 470 | _: c_int, 471 | _: c_int, 472 | _: *mut usize, 473 | _: *mut usize, 474 | _: *mut f32, 475 | ) -> c_int; 476 | pub fn NC_NOTNC4_var_par_access(_: c_int, _: c_int, _: c_int) -> c_int; 477 | pub fn NC_NOTNC4_inq_ncid(_: c_int, _: *const c_char, _: *mut c_int) -> c_int; 478 | pub fn NC_NOTNC4_inq_grps(_: c_int, _: *mut c_int, _: *mut c_int) -> c_int; 479 | pub fn NC_NOTNC4_inq_grpname(_: c_int, _: *mut c_char) -> c_int; 480 | pub fn NC_NOTNC4_inq_grpname_full(_: c_int, _: *mut usize, _: *mut c_char) -> c_int; 481 | pub fn NC_NOTNC4_inq_grp_parent(_: c_int, _: *mut c_int) -> c_int; 482 | pub fn NC_NOTNC4_inq_grp_full_ncid(_: c_int, _: *const c_char, _: *mut c_int) -> c_int; 483 | pub fn NC_NOTNC4_inq_varids(_: c_int, _: *mut c_int, _: *mut c_int) -> c_int; 484 | pub fn NC_NOTNC4_inq_dimids(_: c_int, _: *mut c_int, _: *mut c_int, _: c_int) -> c_int; 485 | pub fn NC_NOTNC4_inq_typeids(_: c_int, _: *mut c_int, _: *mut c_int) -> c_int; 486 | pub fn NC_NOTNC4_inq_user_type( 487 | _: c_int, 488 | _: nc_type, 489 | _: *mut c_char, 490 | _: *mut usize, 491 | _: *mut nc_type, 492 | _: *mut usize, 493 | _: *mut c_int, 494 | ) -> c_int; 495 | pub fn NC_NOTNC4_def_var_quantize(_: c_int, _: c_int, _: c_int, _: c_int) -> c_int; 496 | pub fn NC_NOTNC4_inq_var_quantize(_: c_int, _: c_int, _: *mut c_int, _: *mut c_int) -> c_int; 497 | pub fn NC_NOTNC3_get_varm( 498 | ncid: c_int, 499 | varid: c_int, 500 | start: *const usize, 501 | edges: *const usize, 502 | stride: *const isize, 503 | imapp: *const isize, 504 | value0: *mut c_void, 505 | memtype: nc_type, 506 | ) -> c_int; 507 | pub fn NC_NOTNC3_put_varm( 508 | ncid: c_int, 509 | varid: c_int, 510 | start: *const usize, 511 | edges: *const usize, 512 | stride: *const isize, 513 | imapp: *const isize, 514 | value0: *const c_void, 515 | memtype: nc_type, 516 | ) -> c_int; 517 | } 518 | -------------------------------------------------------------------------------- /netcdf/src/types.rs: -------------------------------------------------------------------------------- 1 | //! Contains functions and enums describing variable types 2 | 3 | use netcdf_sys::*; 4 | 5 | use super::error; 6 | use crate::with_lock; 7 | 8 | /// Basic numeric types 9 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 10 | pub enum BasicType { 11 | /// Signed 1 byte integer 12 | Byte, 13 | /// ISO/ASCII character 14 | Char, 15 | /// Unsigned 1 byte integer 16 | Ubyte, 17 | /// Signed 2 byte integer 18 | Short, 19 | /// Unsigned 2 byte integer 20 | Ushort, 21 | /// Signed 4 byte integer 22 | Int, 23 | /// Unsigned 4 byte integer 24 | Uint, 25 | /// Signed 8 byte integer 26 | Int64, 27 | /// Unsigned 8 byte integer 28 | Uint64, 29 | /// Single precision floating point number 30 | Float, 31 | /// Double precision floating point number 32 | Double, 33 | } 34 | 35 | impl BasicType { 36 | /// Size of the type in bytes 37 | fn size(self) -> usize { 38 | match self { 39 | Self::Byte | Self::Ubyte | Self::Char => 1, 40 | Self::Short | Self::Ushort => 2, 41 | Self::Int | Self::Uint | Self::Float => 4, 42 | Self::Int64 | Self::Uint64 | Self::Double => 8, 43 | } 44 | } 45 | /// `nc_type` of the type 46 | pub(crate) fn id(self) -> nc_type { 47 | use super::NcPutGet; 48 | match self { 49 | Self::Byte => i8::NCTYPE, 50 | Self::Char => NC_CHAR, 51 | Self::Ubyte => u8::NCTYPE, 52 | Self::Short => i16::NCTYPE, 53 | Self::Ushort => u16::NCTYPE, 54 | Self::Int => i32::NCTYPE, 55 | Self::Uint => u32::NCTYPE, 56 | Self::Int64 => i64::NCTYPE, 57 | Self::Uint64 => u64::NCTYPE, 58 | Self::Float => f32::NCTYPE, 59 | Self::Double => f64::NCTYPE, 60 | } 61 | } 62 | 63 | /// `rusty` name of the type 64 | pub fn name(self) -> &'static str { 65 | match self { 66 | Self::Byte => "i8", 67 | Self::Char => "char", 68 | Self::Ubyte => "u8", 69 | Self::Short => "i16", 70 | Self::Ushort => "u16", 71 | Self::Int => "i32", 72 | Self::Uint => "u32", 73 | Self::Int64 => "i64", 74 | Self::Uint64 => "u64", 75 | Self::Float => "f32", 76 | Self::Double => "f64", 77 | } 78 | } 79 | } 80 | 81 | #[allow(missing_docs)] 82 | impl BasicType { 83 | pub fn is_i8(self) -> bool { 84 | self == Self::Byte 85 | } 86 | pub fn is_char(self) -> bool { 87 | self == Self::Char 88 | } 89 | pub fn is_u8(self) -> bool { 90 | self == Self::Ubyte 91 | } 92 | pub fn is_i16(self) -> bool { 93 | self == Self::Short 94 | } 95 | pub fn is_u16(self) -> bool { 96 | self == Self::Ushort 97 | } 98 | pub fn is_i32(self) -> bool { 99 | self == Self::Int 100 | } 101 | pub fn is_u32(self) -> bool { 102 | self == Self::Uint 103 | } 104 | pub fn is_i64(self) -> bool { 105 | self == Self::Int64 106 | } 107 | pub fn is_u64(self) -> bool { 108 | self == Self::Uint64 109 | } 110 | pub fn is_f32(self) -> bool { 111 | self == Self::Float 112 | } 113 | pub fn is_f64(self) -> bool { 114 | self == Self::Double 115 | } 116 | } 117 | 118 | #[derive(Clone, Debug)] 119 | /// A set of bytes which with unspecified endianess 120 | pub struct OpaqueType { 121 | ncid: nc_type, 122 | id: nc_type, 123 | } 124 | 125 | impl OpaqueType { 126 | /// Get the name of this opaque type 127 | pub fn name(&self) -> String { 128 | let mut name = [0_u8; NC_MAX_NAME as usize + 1]; 129 | error::checked(super::with_lock(|| unsafe { 130 | nc_inq_opaque( 131 | self.ncid, 132 | self.id, 133 | name.as_mut_ptr().cast(), 134 | std::ptr::null_mut(), 135 | ) 136 | })) 137 | .unwrap(); 138 | 139 | let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 140 | String::from_utf8(name[..pos].to_vec()).unwrap() 141 | } 142 | /// Number of bytes this type occupies 143 | pub fn size(&self) -> usize { 144 | let mut numbytes = 0; 145 | error::checked(super::with_lock(|| unsafe { 146 | nc_inq_opaque(self.ncid, self.id, std::ptr::null_mut(), &mut numbytes) 147 | })) 148 | .unwrap(); 149 | numbytes 150 | } 151 | pub(crate) fn add(location: nc_type, name: &str, size: usize) -> error::Result { 152 | let name = super::utils::short_name_to_bytes(name)?; 153 | let mut id = 0; 154 | error::checked(super::with_lock(|| unsafe { 155 | nc_def_opaque(location, size, name.as_ptr().cast(), &mut id) 156 | }))?; 157 | 158 | Ok(Self { ncid: location, id }) 159 | } 160 | } 161 | 162 | /// Type of variable length 163 | #[derive(Debug, Clone)] 164 | pub struct VlenType { 165 | ncid: nc_type, 166 | id: nc_type, 167 | } 168 | 169 | impl VlenType { 170 | /// Name of the type 171 | pub fn name(&self) -> String { 172 | let mut name = [0_u8; NC_MAX_NAME as usize + 1]; 173 | error::checked(super::with_lock(|| unsafe { 174 | nc_inq_vlen( 175 | self.ncid, 176 | self.id, 177 | name.as_mut_ptr().cast(), 178 | std::ptr::null_mut(), 179 | std::ptr::null_mut(), 180 | ) 181 | })) 182 | .unwrap(); 183 | 184 | let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 185 | String::from_utf8(name[..pos].to_vec()).unwrap() 186 | } 187 | 188 | pub(crate) fn add(location: nc_type, name: &str) -> error::Result 189 | where 190 | T: super::NcPutGet, 191 | { 192 | let name = super::utils::short_name_to_bytes(name)?; 193 | let mut id = 0; 194 | error::checked(super::with_lock(|| unsafe { 195 | nc_def_vlen(location, name.as_ptr().cast(), T::NCTYPE, &mut id) 196 | }))?; 197 | 198 | Ok(Self { ncid: location, id }) 199 | } 200 | 201 | /// Internal type 202 | pub fn typ(&self) -> BasicType { 203 | let mut bastyp = 0; 204 | error::checked(super::with_lock(|| unsafe { 205 | nc_inq_vlen( 206 | self.ncid, 207 | self.id, 208 | std::ptr::null_mut(), 209 | std::ptr::null_mut(), 210 | &mut bastyp, 211 | ) 212 | })) 213 | .unwrap(); 214 | 215 | match bastyp { 216 | NC_BYTE => BasicType::Byte, 217 | NC_UBYTE => BasicType::Ubyte, 218 | NC_SHORT => BasicType::Short, 219 | NC_USHORT => BasicType::Ushort, 220 | NC_INT => BasicType::Int, 221 | NC_UINT => BasicType::Uint, 222 | NC_INT64 => BasicType::Int64, 223 | NC_UINT64 => BasicType::Uint64, 224 | NC_FLOAT => BasicType::Float, 225 | NC_DOUBLE => BasicType::Double, 226 | _ => panic!("Did not expect typeid {bastyp} in this context"), 227 | } 228 | } 229 | } 230 | 231 | #[derive(Debug, Clone)] 232 | /// Multiple string values stored as integer type 233 | pub struct EnumType { 234 | ncid: nc_type, 235 | id: nc_type, 236 | } 237 | 238 | impl EnumType { 239 | pub(crate) fn add( 240 | ncid: nc_type, 241 | name: &str, 242 | mappings: &[(&str, T)], 243 | ) -> error::Result { 244 | let name = super::utils::short_name_to_bytes(name)?; 245 | let mut id = 0; 246 | error::checked(super::with_lock(|| unsafe { 247 | nc_def_enum(ncid, T::NCTYPE, name.as_ptr().cast(), &mut id) 248 | }))?; 249 | 250 | for (name, val) in mappings { 251 | let name = super::utils::short_name_to_bytes(name)?; 252 | error::checked(super::with_lock(|| unsafe { 253 | nc_insert_enum(ncid, id, name.as_ptr().cast(), (val as *const T).cast()) 254 | }))?; 255 | } 256 | 257 | Ok(Self { ncid, id }) 258 | } 259 | 260 | /// Get the base type of the enum 261 | pub fn typ(&self) -> BasicType { 262 | let mut typ = 0; 263 | error::checked(super::with_lock(|| unsafe { 264 | nc_inq_enum( 265 | self.ncid, 266 | self.id, 267 | std::ptr::null_mut(), 268 | &mut typ, 269 | std::ptr::null_mut(), 270 | std::ptr::null_mut(), 271 | ) 272 | })) 273 | .unwrap(); 274 | match typ { 275 | NC_BYTE => BasicType::Byte, 276 | NC_UBYTE => BasicType::Ubyte, 277 | NC_SHORT => BasicType::Short, 278 | NC_USHORT => BasicType::Ushort, 279 | NC_INT => BasicType::Int, 280 | NC_UINT => BasicType::Uint, 281 | NC_INT64 => BasicType::Int64, 282 | NC_UINT64 => BasicType::Uint64, 283 | NC_FLOAT => BasicType::Float, 284 | NC_DOUBLE => BasicType::Double, 285 | _ => panic!("Did not expect typeid {typ} in this context"), 286 | } 287 | } 288 | 289 | /// Get a single member from an index 290 | /// 291 | /// # Safety 292 | /// Does not check type of enum 293 | unsafe fn member_at(&self, idx: usize) -> error::Result<(String, T)> { 294 | let mut name = [0_u8; NC_MAX_NAME as usize + 1]; 295 | let mut t = std::mem::MaybeUninit::::uninit(); 296 | let idx = idx.try_into()?; 297 | super::with_lock(|| { 298 | nc_inq_enum_member( 299 | self.ncid, 300 | self.id, 301 | idx, 302 | name.as_mut_ptr().cast(), 303 | t.as_mut_ptr().cast(), 304 | ) 305 | }); 306 | 307 | let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 308 | let name = String::from_utf8(name[..pos].to_vec()).unwrap(); 309 | Ok((name, t.assume_init())) 310 | } 311 | 312 | /// Get all members of the enum 313 | pub fn members( 314 | &self, 315 | ) -> error::Result + '_> { 316 | let mut typ = 0; 317 | let mut nummembers = 0; 318 | error::checked(super::with_lock(|| unsafe { 319 | nc_inq_enum( 320 | self.ncid, 321 | self.id, 322 | std::ptr::null_mut(), 323 | &mut typ, 324 | std::ptr::null_mut(), 325 | &mut nummembers, 326 | ) 327 | })) 328 | .unwrap(); 329 | if typ != T::NCTYPE { 330 | return Err(error::Error::TypeMismatch); 331 | } 332 | 333 | Ok((0..nummembers).map(move |idx| unsafe { self.member_at::(idx) }.unwrap())) 334 | } 335 | 336 | /// Name of the type 337 | pub fn name(&self) -> String { 338 | let mut name = [0_u8; NC_MAX_NAME as usize + 1]; 339 | error::checked(super::with_lock(|| unsafe { 340 | nc_inq_enum( 341 | self.ncid, 342 | self.id, 343 | name.as_mut_ptr().cast(), 344 | std::ptr::null_mut(), 345 | std::ptr::null_mut(), 346 | std::ptr::null_mut(), 347 | ) 348 | })) 349 | .unwrap(); 350 | 351 | let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 352 | String::from_utf8(name[..pos].to_vec()).unwrap() 353 | } 354 | 355 | /// Get the name from the enum value 356 | pub fn name_from_value(&self, value: i64) -> Option { 357 | let mut name = [0_u8; NC_MAX_NAME as usize + 1]; 358 | let e = super::with_lock(|| unsafe { 359 | nc_inq_enum_ident(self.ncid, self.id, value, name.as_mut_ptr().cast()) 360 | }); 361 | if e == NC_EINVAL { 362 | return None; 363 | } 364 | 365 | error::checked(e).unwrap(); 366 | 367 | let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 368 | Some(String::from_utf8(name[..pos].to_vec()).unwrap()) 369 | } 370 | 371 | /// Size in bytes of this type 372 | fn size(&self) -> usize { 373 | self.typ().size() 374 | } 375 | } 376 | 377 | /// A type consisting of other types 378 | #[derive(Debug, Clone)] 379 | pub struct CompoundType { 380 | ncid: nc_type, 381 | id: nc_type, 382 | } 383 | 384 | impl CompoundType { 385 | pub(crate) fn add(ncid: nc_type, name: &str) -> error::Result { 386 | let name = super::utils::short_name_to_bytes(name)?; 387 | 388 | Ok(CompoundBuilder { 389 | ncid, 390 | name, 391 | size: 0, 392 | comp: Vec::new(), 393 | }) 394 | } 395 | 396 | /// Size in bytes of this type 397 | fn size(&self) -> usize { 398 | let mut size = 0; 399 | error::checked(super::with_lock(|| unsafe { 400 | nc_inq_compound( 401 | self.ncid, 402 | self.id, 403 | std::ptr::null_mut(), 404 | &mut size, 405 | std::ptr::null_mut(), 406 | ) 407 | })) 408 | .unwrap(); 409 | size 410 | } 411 | 412 | /// Get the name of this type 413 | pub fn name(&self) -> String { 414 | let mut name = [0_u8; NC_MAX_NAME as usize + 1]; 415 | error::checked(super::with_lock(|| unsafe { 416 | nc_inq_compound( 417 | self.ncid, 418 | self.id, 419 | name.as_mut_ptr().cast(), 420 | std::ptr::null_mut(), 421 | std::ptr::null_mut(), 422 | ) 423 | })) 424 | .unwrap(); 425 | 426 | let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 427 | String::from_utf8(name[..pos].to_vec()).unwrap() 428 | } 429 | 430 | /// Get the fields of the compound 431 | pub fn fields(&self) -> impl Iterator { 432 | let ncid = self.ncid; 433 | let parent_id = self.id; 434 | 435 | let mut nfields = 0; 436 | error::checked(super::with_lock(|| unsafe { 437 | nc_inq_compound_nfields(ncid, parent_id, &mut nfields) 438 | })) 439 | .unwrap(); 440 | 441 | (0..nfields).map(move |x| CompoundField { 442 | ncid, 443 | parent: parent_id, 444 | id: x, 445 | }) 446 | } 447 | } 448 | 449 | /// Subfield of a compound 450 | pub struct CompoundField { 451 | ncid: nc_type, 452 | parent: nc_type, 453 | id: usize, 454 | } 455 | 456 | impl CompoundField { 457 | /// Name of the compound field 458 | pub fn name(&self) -> String { 459 | let mut name = [0_u8; NC_MAX_NAME as usize + 1]; 460 | let idx = self.id.try_into().unwrap(); 461 | error::checked(super::with_lock(|| unsafe { 462 | nc_inq_compound_fieldname(self.ncid, self.parent, idx, name.as_mut_ptr().cast()) 463 | })) 464 | .unwrap(); 465 | 466 | let pos = name.iter().position(|&x| x == 0).unwrap_or(name.len()); 467 | String::from_utf8(name[..pos].to_vec()).unwrap() 468 | } 469 | 470 | /// type of the field 471 | pub fn typ(&self) -> VariableType { 472 | let mut typ = 0; 473 | let id = self.id.try_into().unwrap(); 474 | error::checked(super::with_lock(|| unsafe { 475 | nc_inq_compound_fieldtype(self.ncid, self.parent, id, &mut typ) 476 | })) 477 | .unwrap(); 478 | 479 | VariableType::from_id(self.ncid, typ).unwrap() 480 | } 481 | 482 | /// Offset in bytes of this field in the compound type 483 | pub fn offset(&self) -> usize { 484 | let mut offset = 0; 485 | let id = self.id.try_into().unwrap(); 486 | error::checked(super::with_lock(|| unsafe { 487 | nc_inq_compound_field( 488 | self.ncid, 489 | self.parent, 490 | id, 491 | std::ptr::null_mut(), 492 | &mut offset, 493 | std::ptr::null_mut(), 494 | std::ptr::null_mut(), 495 | std::ptr::null_mut(), 496 | ) 497 | })) 498 | .unwrap(); 499 | 500 | offset 501 | } 502 | 503 | /// Get dimensionality of this compound field 504 | pub fn dimensions(&self) -> Option> { 505 | let mut num_dims = 0; 506 | let id = self.id.try_into().unwrap(); 507 | error::checked(super::with_lock(|| unsafe { 508 | nc_inq_compound_fieldndims(self.ncid, self.parent, id, &mut num_dims) 509 | })) 510 | .unwrap(); 511 | 512 | if num_dims == 0 { 513 | return None; 514 | } 515 | 516 | let mut dims = vec![0; num_dims.try_into().unwrap()]; 517 | error::checked(super::with_lock(|| unsafe { 518 | nc_inq_compound_fielddim_sizes(self.ncid, self.parent, id, dims.as_mut_ptr()) 519 | })) 520 | .unwrap(); 521 | 522 | Some(dims.iter().map(|&x| x.try_into().unwrap()).collect()) 523 | } 524 | } 525 | 526 | /// A builder for a compound type 527 | #[must_use] 528 | pub struct CompoundBuilder { 529 | ncid: nc_type, 530 | name: [u8; NC_MAX_NAME as usize + 1], 531 | size: usize, 532 | comp: Vec<( 533 | VariableType, 534 | [u8; NC_MAX_NAME as usize + 1], 535 | Option>, 536 | )>, 537 | } 538 | 539 | impl CompoundBuilder { 540 | /// Add a type to the compound 541 | pub fn add_type(&mut self, name: &str, var: &VariableType) -> error::Result<&mut Self> { 542 | self.comp 543 | .push((var.clone(), super::utils::short_name_to_bytes(name)?, None)); 544 | 545 | self.size += var.size(); 546 | Ok(self) 547 | } 548 | 549 | /// Add a basic numeric type 550 | pub fn add(&mut self, name: &str) -> error::Result<&mut Self> { 551 | let var = VariableType::from_id(self.ncid, T::NCTYPE)?; 552 | self.add_type(name, &var) 553 | } 554 | 555 | /// Add an array of a basic type 556 | pub fn add_array( 557 | &mut self, 558 | name: &str, 559 | dims: &[usize], 560 | ) -> error::Result<&mut Self> { 561 | let var = VariableType::from_id(self.ncid, T::NCTYPE)?; 562 | self.add_array_type(name, &var, dims) 563 | } 564 | 565 | /// Add a type as an array 566 | pub fn add_array_type( 567 | &mut self, 568 | name: &str, 569 | var: &VariableType, 570 | dims: &[usize], 571 | ) -> error::Result<&mut Self> { 572 | self.comp.push(( 573 | var.clone(), 574 | super::utils::short_name_to_bytes(name)?, 575 | Some(dims.iter().map(|&x| x.try_into().unwrap()).collect()), 576 | )); 577 | 578 | self.size += var.size() * dims.iter().product::(); 579 | Ok(self) 580 | } 581 | 582 | /// Finalize the compound type 583 | pub fn build(self) -> error::Result { 584 | let mut id = 0; 585 | error::checked(super::with_lock(|| unsafe { 586 | nc_def_compound(self.ncid, self.size, self.name.as_ptr().cast(), &mut id) 587 | }))?; 588 | 589 | let mut offset = 0; 590 | for (typ, name, dims) in &self.comp { 591 | match dims { 592 | None => { 593 | error::checked(super::with_lock(|| unsafe { 594 | nc_insert_compound(self.ncid, id, name.as_ptr().cast(), offset, typ.id()) 595 | }))?; 596 | offset += typ.size(); 597 | } 598 | Some(dims) => { 599 | let dimlen = dims.len().try_into().unwrap(); 600 | error::checked(super::with_lock(|| unsafe { 601 | nc_insert_array_compound( 602 | self.ncid, 603 | id, 604 | name.as_ptr().cast(), 605 | offset, 606 | typ.id(), 607 | dimlen, 608 | dims.as_ptr(), 609 | ) 610 | }))?; 611 | offset += typ.size() 612 | * dims 613 | .iter() 614 | .map(|x: &i32| -> usize { (*x).try_into().unwrap() }) 615 | .product::(); 616 | } 617 | } 618 | } 619 | 620 | Ok(CompoundType { 621 | ncid: self.ncid, 622 | id, 623 | }) 624 | } 625 | } 626 | 627 | /// Description of the variable 628 | #[derive(Debug, Clone)] 629 | pub enum VariableType { 630 | /// A basic numeric type 631 | Basic(BasicType), 632 | /// A string type 633 | String, 634 | /// Some bytes 635 | Opaque(OpaqueType), 636 | /// Variable length array 637 | Vlen(VlenType), 638 | /// Enum type 639 | Enum(EnumType), 640 | /// Compound type 641 | Compound(CompoundType), 642 | } 643 | 644 | impl VariableType { 645 | /// Get the basic type, if this type is a simple numeric type 646 | pub fn as_basic(&self) -> Option { 647 | match self { 648 | Self::Basic(x) => Some(*x), 649 | _ => None, 650 | } 651 | } 652 | 653 | /// Size in bytes of the type 654 | pub(crate) fn size(&self) -> usize { 655 | match self { 656 | Self::Basic(b) => b.size(), 657 | Self::String => panic!("A string does not have a defined size"), 658 | Self::Enum(e) => e.size(), 659 | Self::Opaque(o) => o.size(), 660 | Self::Vlen(_) => panic!("A variable length array does not have a defined size"), 661 | Self::Compound(c) => c.size(), 662 | } 663 | } 664 | 665 | /// Id of this type 666 | pub(crate) fn id(&self) -> nc_type { 667 | match self { 668 | Self::Basic(b) => b.id(), 669 | Self::String => NC_STRING, 670 | Self::Enum(e) => e.id, 671 | Self::Opaque(o) => o.id, 672 | Self::Vlen(v) => v.id, 673 | Self::Compound(c) => c.id, 674 | } 675 | } 676 | 677 | /// Get the name of the type. The basic numeric types will 678 | /// have `rusty` names (u8/i32/f64/string) 679 | pub fn name(&self) -> String { 680 | match self { 681 | Self::Basic(b) => b.name().into(), 682 | Self::String => "string".into(), 683 | Self::Enum(e) => e.name(), 684 | Self::Opaque(o) => o.name(), 685 | Self::Vlen(v) => v.name(), 686 | Self::Compound(c) => c.name(), 687 | } 688 | } 689 | } 690 | 691 | #[allow(missing_docs)] 692 | impl VariableType { 693 | pub fn is_string(&self) -> bool { 694 | matches!(self, Self::String) 695 | } 696 | pub fn is_i8(&self) -> bool { 697 | self.as_basic().map_or(false, BasicType::is_i8) 698 | } 699 | pub fn is_u8(&self) -> bool { 700 | self.as_basic().map_or(false, BasicType::is_u8) 701 | } 702 | pub fn is_i16(&self) -> bool { 703 | self.as_basic().map_or(false, BasicType::is_i16) 704 | } 705 | pub fn is_u16(&self) -> bool { 706 | self.as_basic().map_or(false, BasicType::is_u16) 707 | } 708 | pub fn is_i32(&self) -> bool { 709 | self.as_basic().map_or(false, BasicType::is_i32) 710 | } 711 | pub fn is_u32(&self) -> bool { 712 | self.as_basic().map_or(false, BasicType::is_u32) 713 | } 714 | pub fn is_i64(&self) -> bool { 715 | self.as_basic().map_or(false, BasicType::is_i64) 716 | } 717 | pub fn is_u64(&self) -> bool { 718 | self.as_basic().map_or(false, BasicType::is_u64) 719 | } 720 | pub fn is_f32(&self) -> bool { 721 | self.as_basic().map_or(false, BasicType::is_f32) 722 | } 723 | pub fn is_f64(&self) -> bool { 724 | self.as_basic().map_or(false, BasicType::is_f64) 725 | } 726 | } 727 | 728 | impl VariableType { 729 | /// Get the variable type from the id 730 | pub(crate) fn from_id(ncid: nc_type, xtype: nc_type) -> error::Result { 731 | match xtype { 732 | NC_CHAR => Ok(Self::Basic(BasicType::Char)), 733 | NC_BYTE => Ok(Self::Basic(BasicType::Byte)), 734 | NC_UBYTE => Ok(Self::Basic(BasicType::Ubyte)), 735 | NC_SHORT => Ok(Self::Basic(BasicType::Short)), 736 | NC_USHORT => Ok(Self::Basic(BasicType::Ushort)), 737 | NC_INT => Ok(Self::Basic(BasicType::Int)), 738 | NC_UINT => Ok(Self::Basic(BasicType::Uint)), 739 | NC_INT64 => Ok(Self::Basic(BasicType::Int64)), 740 | NC_UINT64 => Ok(Self::Basic(BasicType::Uint64)), 741 | NC_FLOAT => Ok(Self::Basic(BasicType::Float)), 742 | NC_DOUBLE => Ok(Self::Basic(BasicType::Double)), 743 | NC_STRING => Ok(Self::String), 744 | xtype => { 745 | let mut base_xtype = 0; 746 | error::checked(super::with_lock(|| unsafe { 747 | nc_inq_user_type( 748 | ncid, 749 | xtype, 750 | std::ptr::null_mut(), 751 | std::ptr::null_mut(), 752 | std::ptr::null_mut(), 753 | std::ptr::null_mut(), 754 | &mut base_xtype, 755 | ) 756 | }))?; 757 | match base_xtype { 758 | NC_VLEN => Ok(VlenType { ncid, id: xtype }.into()), 759 | NC_OPAQUE => Ok(OpaqueType { ncid, id: xtype }.into()), 760 | NC_ENUM => Ok(EnumType { ncid, id: xtype }.into()), 761 | NC_COMPOUND => Ok(CompoundType { ncid, id: xtype }.into()), 762 | _ => panic!("Unexpected base type: {base_xtype}"), 763 | } 764 | } 765 | } 766 | } 767 | } 768 | 769 | pub(crate) fn all_at_location( 770 | ncid: nc_type, 771 | ) -> error::Result>> { 772 | let typeids = { 773 | let mut num_typeids = 0; 774 | error::checked(with_lock(|| unsafe { 775 | nc_inq_typeids(ncid, &mut num_typeids, std::ptr::null_mut()) 776 | }))?; 777 | let mut typeids = vec![0; num_typeids.try_into()?]; 778 | error::checked(with_lock(|| unsafe { 779 | nc_inq_typeids(ncid, std::ptr::null_mut(), typeids.as_mut_ptr()) 780 | }))?; 781 | typeids 782 | }; 783 | Ok(typeids 784 | .into_iter() 785 | .map(move |x| VariableType::from_id(ncid, x))) 786 | } 787 | 788 | impl From for VariableType { 789 | fn from(v: CompoundType) -> Self { 790 | Self::Compound(v) 791 | } 792 | } 793 | 794 | impl From for VariableType { 795 | fn from(v: BasicType) -> Self { 796 | Self::Basic(v) 797 | } 798 | } 799 | 800 | impl From for VariableType { 801 | fn from(v: EnumType) -> Self { 802 | Self::Enum(v) 803 | } 804 | } 805 | 806 | impl From for VariableType { 807 | fn from(v: VlenType) -> Self { 808 | Self::Vlen(v) 809 | } 810 | } 811 | 812 | impl From for VariableType { 813 | fn from(v: OpaqueType) -> Self { 814 | Self::Opaque(v) 815 | } 816 | } 817 | --------------------------------------------------------------------------------