use crate::error::{AlgorithmError, Result};
use oxigdal_core::buffer::RasterBuffer;
#[derive(Debug, Clone, Copy, Default)]
pub struct ZonalStatistics {
pub zone_id: i32,
pub count: u64,
pub sum: f64,
pub mean: f64,
pub min: f64,
pub max: f64,
pub std_dev: f64,
}
pub fn compute_zonal_stats(
values: &RasterBuffer,
zones: &RasterBuffer,
) -> Result<Vec<ZonalStatistics>> {
if values.width() != zones.width() || values.height() != zones.height() {
return Err(AlgorithmError::InvalidDimensions {
message: "Rasters must have same dimensions",
actual: values.width() as usize,
expected: zones.width() as usize,
});
}
let mut stats_map: std::collections::HashMap<i32, ZonalStatistics> =
std::collections::HashMap::new();
for y in 0..values.height() {
for x in 0..values.width() {
let zone_id = zones.get_pixel(x, y).map_err(AlgorithmError::Core)? as i32;
let value = values.get_pixel(x, y).map_err(AlgorithmError::Core)?;
let stats = stats_map.entry(zone_id).or_insert_with(|| ZonalStatistics {
zone_id,
min: f64::MAX,
max: f64::MIN,
..Default::default()
});
stats.count += 1;
stats.sum += value;
stats.min = stats.min.min(value);
stats.max = stats.max.max(value);
}
}
for stats in stats_map.values_mut() {
stats.mean = stats.sum / stats.count as f64;
}
for y in 0..values.height() {
for x in 0..values.width() {
let zone_id = zones.get_pixel(x, y).map_err(AlgorithmError::Core)? as i32;
let value = values.get_pixel(x, y).map_err(AlgorithmError::Core)?;
if let Some(stats) = stats_map.get_mut(&zone_id) {
let diff = value - stats.mean;
stats.std_dev += diff * diff;
}
}
}
for stats in stats_map.values_mut() {
stats.std_dev = (stats.std_dev / stats.count as f64).sqrt();
}
Ok(stats_map.into_values().collect())
}
#[cfg(test)]
mod tests {
use super::*;
use oxigdal_core::types::RasterDataType;
#[test]
fn test_zonal_stats() {
let mut values = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
let mut zones = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
for y in 0..5 {
for x in 0..5 {
values.set_pixel(x, y, (x + y) as f64).ok();
zones.set_pixel(x, y, (x / 2) as f64).ok();
}
}
let result = compute_zonal_stats(&values, &zones);
assert!(result.is_ok());
}
}