use super::SpatialGrid;
use crate::errors::{RSCMError, RSCMResult};
use crate::timeseries::FloatValue;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum HemisphericRegion {
Northern = 0,
Southern = 1,
}
impl From<HemisphericRegion> for usize {
fn from(r: HemisphericRegion) -> usize {
r as usize
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct HemisphericGrid {
weights: [FloatValue; 2],
}
impl HemisphericGrid {
pub fn equal_weights() -> Self {
Self {
weights: [0.5, 0.5],
}
}
pub fn with_weights(weights: [FloatValue; 2]) -> Self {
let sum: FloatValue = weights.iter().sum();
assert!(
(sum - 1.0).abs() < 1e-6,
"Weights must sum to 1.0, got {}",
sum
);
Self { weights }
}
pub fn weights(&self) -> &[FloatValue; 2] {
&self.weights
}
}
impl Default for HemisphericGrid {
fn default() -> Self {
Self::equal_weights()
}
}
impl SpatialGrid for HemisphericGrid {
fn grid_name(&self) -> &'static str {
"Hemispheric"
}
fn size(&self) -> usize {
2
}
fn region_names(&self) -> &[String] {
static NAMES: std::sync::OnceLock<Vec<String>> = std::sync::OnceLock::new();
NAMES.get_or_init(|| {
vec![
"Northern Hemisphere".to_string(),
"Southern Hemisphere".to_string(),
]
})
}
fn aggregate_global(&self, values: &[FloatValue]) -> FloatValue {
assert_eq!(values.len(), 2, "HemisphericGrid expects exactly 2 values");
values
.iter()
.zip(self.weights.iter())
.map(|(v, w)| v * w)
.sum()
}
fn transform_to<G: SpatialGrid>(
&self,
values: &[FloatValue],
target: &G,
) -> RSCMResult<Vec<FloatValue>> {
assert_eq!(
values.len(),
self.size(),
"Values length must match grid size"
);
match target.size() {
1 => {
Ok(vec![self.aggregate_global(values)])
}
2 => {
Ok(values.to_vec())
}
4 => {
Err(RSCMError::UnsupportedGridTransformation {
from: self.grid_name().to_string(),
to: target.grid_name().to_string(),
})
}
_ => Err(RSCMError::UnsupportedGridTransformation {
from: self.grid_name().to_string(),
to: target.grid_name().to_string(),
}),
}
}
}