#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NcType {
Byte,
Short,
Int,
Float,
Double,
Char,
}
impl NcType {
pub fn as_str(&self) -> &'static str {
match self {
NcType::Byte => "byte",
NcType::Short => "short",
NcType::Int => "int",
NcType::Float => "float",
NcType::Double => "double",
NcType::Char => "char",
}
}
}
#[derive(Debug, Clone)]
pub struct NcDimension {
pub name: String,
pub size: usize,
pub unlimited: bool,
}
#[derive(Debug, Clone)]
pub struct NcAttribute {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct NcVariable {
pub name: String,
pub nc_type: NcType,
pub dims: Vec<String>,
pub attrs: Vec<NcAttribute>,
}
impl NcVariable {
pub fn new(name: &str, nc_type: NcType, dims: Vec<&str>) -> Self {
Self { name: name.into(), nc_type, dims: dims.into_iter().map(|s| s.into()).collect(), attrs: vec![] }
}
pub fn add_attr(&mut self, name: &str, value: &str) {
self.attrs.push(NcAttribute { name: name.into(), value: value.into() });
}
}
#[derive(Debug, Clone, Default)]
pub struct NetCdfFileDef {
pub filename: String,
pub dimensions: Vec<NcDimension>,
pub global_attrs: Vec<NcAttribute>,
pub variables: Vec<NcVariable>,
}
pub fn new_netcdf_file(filename: &str) -> NetCdfFileDef {
NetCdfFileDef {
filename: filename.into(),
global_attrs: vec![NcAttribute { name: "Conventions".into(), value: "CF-1.8".into() }],
..Default::default()
}
}
pub fn add_dimension(file: &mut NetCdfFileDef, name: &str, size: usize, unlimited: bool) {
file.dimensions.push(NcDimension { name: name.into(), size, unlimited });
}
pub fn add_nc_variable(file: &mut NetCdfFileDef, var: NcVariable) {
file.variables.push(var);
}
pub fn export_mesh_netcdf(positions: &[[f32; 3]]) -> NetCdfFileDef {
let mut file = new_netcdf_file("mesh.nc");
add_dimension(&mut file, "vertex", positions.len(), false);
add_dimension(&mut file, "ndim", 3, false);
let mut pos_var = NcVariable::new("positions", NcType::Float, vec!["vertex", "ndim"]);
pos_var.add_attr("units", "meter");
pos_var.add_attr("long_name", "vertex positions");
add_nc_variable(&mut file, pos_var);
file
}
pub fn to_cdl_string(file: &NetCdfFileDef) -> String {
let mut s = format!("netcdf {} {{\n", file.filename.trim_end_matches(".nc"));
s.push_str("dimensions:\n");
for d in &file.dimensions {
let sz = if d.unlimited { "UNLIMITED".into() } else { d.size.to_string() };
s.push_str(&format!("\t{} = {} ;\n", d.name, sz));
}
s.push_str("variables:\n");
for v in &file.variables {
let dims = v.dims.join(", ");
s.push_str(&format!("\t{} {}({}) ;\n", v.nc_type.as_str(), v.name, dims));
for a in &v.attrs {
s.push_str(&format!("\t\t{}:{} = \"{}\" ;\n", v.name, a.name, a.value));
}
}
s.push_str("// global attributes:\n");
for ga in &file.global_attrs {
s.push_str(&format!("\t\t:{} = \"{}\" ;\n", ga.name, ga.value));
}
s.push_str("}\n");
s
}
pub fn variable_count(file: &NetCdfFileDef) -> usize {
file.variables.len()
}
pub fn dimension_count(file: &NetCdfFileDef) -> usize {
file.dimensions.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_netcdf_file_has_conventions() {
let f = new_netcdf_file("x.nc");
assert!(f.global_attrs.iter().any(|a| a.name == "Conventions"));
}
#[test]
fn test_add_dimension_increases_count() {
let mut f = new_netcdf_file("x.nc");
add_dimension(&mut f, "time", 100, true);
assert_eq!(dimension_count(&f), 1);
}
#[test]
fn test_add_variable_increases_count() {
let mut f = new_netcdf_file("x.nc");
add_nc_variable(&mut f, NcVariable::new("temp", NcType::Float, vec!["time"]));
assert_eq!(variable_count(&f), 1);
}
#[test]
fn test_export_mesh_netcdf_dims() {
let p = vec![[0.0f32;3]; 10];
let f = export_mesh_netcdf(&p);
assert_eq!(dimension_count(&f), 2);
}
#[test]
fn test_to_cdl_contains_filename() {
let f = new_netcdf_file("data.nc");
assert!(to_cdl_string(&f).contains("data"));
}
#[test]
fn test_to_cdl_contains_variable() {
let p = vec![[1.0f32,2.0,3.0]];
let f = export_mesh_netcdf(&p);
assert!(to_cdl_string(&f).contains("positions"));
}
#[test]
fn test_nc_type_as_str() {
assert_eq!(NcType::Float.as_str(), "float");
}
#[test]
fn test_variable_attr_stored() {
let mut v = NcVariable::new("x", NcType::Double, vec!["n"]);
v.add_attr("units", "km");
assert_eq!(v.attrs[0].name, "units");
}
#[test]
fn test_unlimited_dimension_in_cdl() {
let mut f = new_netcdf_file("x.nc");
add_dimension(&mut f, "time", 0, true);
assert!(to_cdl_string(&f).contains("UNLIMITED"));
}
#[test]
fn test_no_variables_zero_count() {
let f = new_netcdf_file("x.nc");
assert_eq!(variable_count(&f), 0);
}
}