#[allow(dead_code)] type PairResult<'a> = Option<(&'a String, &'a String, &'a Vec<(usize, usize)>)>;
#[allow(dead_code)] type TripletResult<'a> = Option<(
&'a String,
&'a String,
&'a String,
&'a Vec<(usize, usize, usize)>,
)>;
use crate::errors::Nc2ParquetError;
use serde::Deserialize;
#[derive(Debug, Clone)]
pub enum FilterResult {
Single {
dimension: String,
indices: Vec<usize>,
},
Pairs {
lat_dimension: String,
lon_dimension: String,
pairs: Vec<(usize, usize)>,
},
Triplets {
time_dimension: String,
lat_dimension: String,
lon_dimension: String,
triplets: Vec<(usize, usize, usize)>,
},
}
impl FilterResult {
#[allow(dead_code)] pub(crate) fn as_single(&self) -> Option<(&String, &Vec<usize>)> {
if let FilterResult::Single { dimension, indices } = self {
Some((dimension, indices))
} else {
None
}
}
#[allow(dead_code)] pub(crate) fn as_pairs(&self) -> PairResult<'_> {
if let FilterResult::Pairs {
lat_dimension,
lon_dimension,
pairs,
} = self
{
Some((lat_dimension, lon_dimension, pairs))
} else {
None
}
}
#[allow(dead_code)] pub(crate) fn as_triplets(&self) -> TripletResult<'_> {
if let FilterResult::Triplets {
time_dimension,
lat_dimension,
lon_dimension,
triplets,
} = self
{
Some((time_dimension, lat_dimension, lon_dimension, triplets))
} else {
None
}
}
#[allow(dead_code)] pub(crate) fn len(&self) -> usize {
match self {
FilterResult::Single { indices, .. } => indices.len(),
FilterResult::Pairs { pairs, .. } => pairs.len(),
FilterResult::Triplets { triplets, .. } => triplets.len(),
}
}
#[allow(dead_code)] pub(crate) fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub trait NCFilter {
fn apply(&self, file: &netcdf::File) -> Result<FilterResult, Nc2ParquetError>;
}
#[derive(Deserialize)]
pub struct NCRangeFilter {
pub(crate) dimension_name: String,
pub(crate) min_value: f64,
pub(crate) max_value: f64,
}
impl NCRangeFilter {
pub fn new(dimension_name: &str, min_value: f64, max_value: f64) -> Self {
NCRangeFilter {
dimension_name: dimension_name.to_string(),
min_value,
max_value,
}
}
pub fn from_json(json_str: &str) -> Result<Self, Nc2ParquetError> {
let f: NCRangeFilter = serde_json::from_str(json_str)
.map_err(|e| Nc2ParquetError::Serialization(e.to_string()))?;
Ok(f)
}
}
impl NCFilter for NCRangeFilter {
fn apply(&self, file: &netcdf::File) -> Result<FilterResult, Nc2ParquetError> {
if let Some(var) = file.variable(&self.dimension_name) {
let values = var.get::<f64, _>(..)?;
let filtered_indices: Vec<usize> = values
.iter()
.enumerate()
.filter(|(_, val)| **val >= self.min_value && **val <= self.max_value)
.map(|(idx, _)| idx)
.collect();
Ok(FilterResult::Single {
dimension: self.dimension_name.clone(),
indices: filtered_indices,
})
} else {
Err(Nc2ParquetError::DimensionNotFound(
self.dimension_name.clone(),
))
}
}
}
#[derive(Deserialize)]
pub struct NCListFilter {
pub(crate) dimension_name: String,
pub(crate) values: Vec<f64>,
}
impl NCListFilter {
pub fn new(dimension_name: &str, values: Vec<f64>) -> Self {
NCListFilter {
dimension_name: dimension_name.to_string(),
values,
}
}
pub fn from_json(json_str: &str) -> Result<Self, Nc2ParquetError> {
let f: NCListFilter = serde_json::from_str(json_str)
.map_err(|e| Nc2ParquetError::Serialization(e.to_string()))?;
Ok(f)
}
}
impl NCFilter for NCListFilter {
fn apply(&self, file: &netcdf::File) -> Result<FilterResult, Nc2ParquetError> {
if let Some(var) = file.variable(&self.dimension_name) {
let coord_values = var.get::<f64, _>(..)?;
let filtered_indices: Vec<usize> = coord_values
.iter()
.enumerate()
.filter(|(_, val)| self.values.contains(val))
.map(|(idx, _)| idx)
.collect();
Ok(FilterResult::Single {
dimension: self.dimension_name.clone(),
indices: filtered_indices,
})
} else {
Err(Nc2ParquetError::DimensionNotFound(
self.dimension_name.clone(),
))
}
}
}
#[derive(Deserialize)]
pub struct NC2DPointFilter {
pub(crate) lat_dimension_name: String,
pub(crate) lon_dimension_name: String,
pub(crate) points: Vec<(f64, f64)>,
pub(crate) tolerance: f64,
}
impl NC2DPointFilter {
pub fn new(
lat_dimension_name: &str,
lon_dimension_name: &str,
points: Vec<(f64, f64)>,
tolerance: f64,
) -> Self {
NC2DPointFilter {
lat_dimension_name: lat_dimension_name.to_string(),
lon_dimension_name: lon_dimension_name.to_string(),
points,
tolerance,
}
}
pub fn from_json(json_str: &str) -> Result<Self, Nc2ParquetError> {
let f: NC2DPointFilter = serde_json::from_str(json_str)
.map_err(|e| Nc2ParquetError::Serialization(e.to_string()))?;
Ok(f)
}
}
impl NCFilter for NC2DPointFilter {
fn apply(&self, file: &netcdf::File) -> Result<FilterResult, Nc2ParquetError> {
let lat_var = file
.variable(&self.lat_dimension_name)
.ok_or_else(|| Nc2ParquetError::DimensionNotFound(self.lat_dimension_name.clone()))?;
let lon_var = file
.variable(&self.lon_dimension_name)
.ok_or_else(|| Nc2ParquetError::DimensionNotFound(self.lon_dimension_name.clone()))?;
let lat_values = lat_var.get::<f64, _>(..)?;
let lon_values = lon_var.get::<f64, _>(..)?;
let mut filtered_indices = Vec::new();
for &(target_lat, target_lon) in &self.points {
for (i, &lat) in lat_values.iter().enumerate() {
if (lat - target_lat).abs() <= self.tolerance {
for (j, &lon) in lon_values.iter().enumerate() {
if (lon - target_lon).abs() <= self.tolerance {
filtered_indices.push((i, j));
}
}
}
}
}
Ok(FilterResult::Pairs {
lat_dimension: self.lat_dimension_name.clone(),
lon_dimension: self.lon_dimension_name.clone(),
pairs: filtered_indices,
})
}
}
#[derive(Deserialize)]
pub struct NC3DPointFilter {
pub(crate) time_dimension_name: String,
pub(crate) lat_dimension_name: String,
pub(crate) lon_dimension_name: String,
pub(crate) steps: Vec<f64>,
pub(crate) points: Vec<(f64, f64)>,
pub(crate) tolerance: f64,
}
impl NC3DPointFilter {
pub fn new(
time_dimension_name: &str,
lat_dimension_name: &str,
lon_dimension_name: &str,
steps: Vec<f64>,
points: Vec<(f64, f64)>,
tolerance: f64,
) -> Self {
NC3DPointFilter {
time_dimension_name: time_dimension_name.to_string(),
lat_dimension_name: lat_dimension_name.to_string(),
lon_dimension_name: lon_dimension_name.to_string(),
steps,
points,
tolerance,
}
}
pub fn from_json(json_str: &str) -> Result<Self, Nc2ParquetError> {
let f: NC3DPointFilter = serde_json::from_str(json_str)
.map_err(|e| Nc2ParquetError::Serialization(e.to_string()))?;
Ok(f)
}
}
impl NCFilter for NC3DPointFilter {
fn apply(&self, file: &netcdf::File) -> Result<FilterResult, Nc2ParquetError> {
let time_var = file
.variable(&self.time_dimension_name)
.ok_or_else(|| Nc2ParquetError::DimensionNotFound(self.time_dimension_name.clone()))?;
let lat_var = file
.variable(&self.lat_dimension_name)
.ok_or_else(|| Nc2ParquetError::DimensionNotFound(self.lat_dimension_name.clone()))?;
let lon_var = file
.variable(&self.lon_dimension_name)
.ok_or_else(|| Nc2ParquetError::DimensionNotFound(self.lon_dimension_name.clone()))?;
let time_values = time_var.get::<f64, _>(..)?;
let lat_values = lat_var.get::<f64, _>(..)?;
let lon_values = lon_var.get::<f64, _>(..)?;
let filtered_time_indices: Vec<usize> = time_values
.iter()
.enumerate()
.filter(|(_, val)| self.steps.contains(val))
.map(|(idx, _)| idx)
.collect();
let mut filtered_indices = Vec::new();
for &(target_lat, target_lon) in &self.points {
for (i, &lat) in lat_values.iter().enumerate() {
if (lat - target_lat).abs() <= self.tolerance {
for (j, &lon) in lon_values.iter().enumerate() {
if (lon - target_lon).abs() <= self.tolerance {
for &t_idx in &filtered_time_indices {
filtered_indices.push((t_idx, i, j));
}
}
}
}
}
}
Ok(FilterResult::Triplets {
time_dimension: self.time_dimension_name.clone(),
lat_dimension: self.lat_dimension_name.clone(),
lon_dimension: self.lon_dimension_name.clone(),
triplets: filtered_indices,
})
}
}
#[allow(dead_code)] pub(crate) fn filter_factory(json_str: &str) -> Result<Box<dyn NCFilter>, Nc2ParquetError> {
let v: serde_json::Value = serde_json::from_str(json_str)
.map_err(|e| Nc2ParquetError::Serialization(e.to_string()))?;
if let Some(filter_kind) = v.get("kind").and_then(|t| t.as_str()) {
match filter_kind {
"range" => {
let filter = NCRangeFilter::from_json(json_str)?;
Ok(Box::new(filter))
}
"list" => {
let filter = NCListFilter::from_json(json_str)?;
Ok(Box::new(filter))
}
"2d_point" => {
let filter = NC2DPointFilter::from_json(json_str)?;
Ok(Box::new(filter))
}
"3d_point" => {
let filter = NC3DPointFilter::from_json(json_str)?;
Ok(Box::new(filter))
}
_ => Err(Nc2ParquetError::Filter(format!(
"Unknown filter kind: {}",
filter_kind
))),
}
} else {
Err(Nc2ParquetError::Filter(
"Missing 'kind' field in JSON".to_string(),
))
}
}