use std::collections::HashMap;
use rustyhdf5::AttrValue;
use crate::cf::{self, CfAttributes};
use crate::dimension::Dimension;
use crate::error::Error;
use crate::types::{dtype_to_nctype, NcType};
pub struct Variable<'f> {
name: String,
dataset: rustyhdf5::Dataset<'f>,
dims: Vec<Dimension>,
attrs_cache: Option<HashMap<String, AttrValue>>,
}
impl<'f> Variable<'f> {
pub(crate) fn new(
name: String,
dataset: rustyhdf5::Dataset<'f>,
dims: Vec<Dimension>,
) -> Self {
Self {
name,
dataset,
dims,
attrs_cache: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn dimensions(&self) -> &[Dimension] {
&self.dims
}
pub fn shape(&self) -> Result<Vec<u64>, Error> {
Ok(self.dataset.shape()?)
}
pub fn nc_type(&self) -> Result<NcType, Error> {
let dtype = self.dataset.dtype()?;
Ok(dtype_to_nctype(&dtype))
}
pub fn attrs(&mut self) -> Result<&HashMap<String, AttrValue>, Error> {
if self.attrs_cache.is_none() {
self.attrs_cache = Some(self.dataset.attrs()?);
}
Ok(self.attrs_cache.as_ref().unwrap())
}
pub fn cf_attributes(&mut self) -> Result<CfAttributes, Error> {
let attrs = self.attrs()?;
Ok(cf::extract_cf_attributes(attrs))
}
pub fn read_f64(&mut self) -> Result<Vec<f64>, Error> {
let raw = self.dataset.read_f64()?;
let cf = self.cf_attributes()?;
Ok(cf::apply_scale_offset(&raw, &cf))
}
pub fn read_raw_f64(&self) -> Result<Vec<f64>, Error> {
Ok(self.dataset.read_f64()?)
}
pub fn read_raw_f32(&self) -> Result<Vec<f32>, Error> {
Ok(self.dataset.read_f32()?)
}
pub fn read_raw_i32(&self) -> Result<Vec<i32>, Error> {
Ok(self.dataset.read_i32()?)
}
pub fn read_raw_i64(&self) -> Result<Vec<i64>, Error> {
Ok(self.dataset.read_i64()?)
}
pub fn read_raw_u64(&self) -> Result<Vec<u64>, Error> {
Ok(self.dataset.read_u64()?)
}
pub fn read_string(&self) -> Result<Vec<String>, Error> {
Ok(self.dataset.read_string()?)
}
pub fn read_raw(&self) -> Result<Vec<u8>, Error> {
let dtype = self.dataset.dtype()?;
match dtype {
rustyhdf5::DType::F64 => {
let vals = self.dataset.read_f64()?;
Ok(vals.iter().flat_map(|v| v.to_le_bytes()).collect())
}
rustyhdf5::DType::F32 => {
let vals = self.dataset.read_f32()?;
Ok(vals.iter().flat_map(|v| v.to_le_bytes()).collect())
}
rustyhdf5::DType::I32 => {
let vals = self.dataset.read_i32()?;
Ok(vals.iter().flat_map(|v| v.to_le_bytes()).collect())
}
rustyhdf5::DType::I64 => {
let vals = self.dataset.read_i64()?;
Ok(vals.iter().flat_map(|v| v.to_le_bytes()).collect())
}
rustyhdf5::DType::U64 => {
let vals = self.dataset.read_u64()?;
Ok(vals.iter().flat_map(|v| v.to_le_bytes()).collect())
}
_ => {
let vals = self.dataset.read_f64()?;
Ok(vals.iter().flat_map(|v| v.to_le_bytes()).collect())
}
}
}
pub fn is_coordinate(&self) -> bool {
self.dims.len() == 1 && self.dims[0].name == self.name
}
}
impl std::fmt::Debug for Variable<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Variable")
.field("name", &self.name)
.field("dims", &self.dims)
.finish()
}
}
pub(crate) fn build_variables<'f>(
group: &rustyhdf5::Group<'f>,
available_dims: &[Dimension],
) -> Result<Vec<Variable<'f>>, Error> {
let dataset_names = group.datasets()?;
let mut variables = Vec::new();
for ds_name in &dataset_names {
let ds = group.dataset(ds_name)?;
let shape = ds.shape()?;
let var_dims = match_dimensions_to_variable(&shape, available_dims);
variables.push(Variable::new(ds_name.clone(), ds, var_dims));
}
Ok(variables)
}
pub(crate) fn match_dimensions_to_variable(shape: &[u64], available_dims: &[Dimension]) -> Vec<Dimension> {
let mut result = Vec::with_capacity(shape.len());
let mut used = vec![false; available_dims.len()];
for &dim_size in shape {
let mut matched = false;
for (i, dim) in available_dims.iter().enumerate() {
if !used[i] && dim.size == dim_size {
result.push(dim.clone());
used[i] = true;
matched = true;
break;
}
}
if !matched {
result.push(Dimension {
name: format!("dim_{dim_size}"),
size: dim_size,
is_unlimited: false,
});
}
}
result
}