use crate::foundation::Lattice;
use ndarray::Array2;
const IDW_POWER: f64 = 2.0;
pub(crate) fn grid_idw(coords: &[[f64; 3]], lattice: &Lattice) -> Array2<f64> {
let mut out = Array2::from_elem((lattice.ncol, lattice.nrow), f64::NAN);
for j in 0..lattice.nrow {
for i in 0..lattice.ncol {
let (x, y) = lattice.node_xy(i, j);
out[[i, j]] = idw_at(coords, x, y);
}
}
out
}
fn idw_at(coords: &[[f64; 3]], x: f64, y: f64) -> f64 {
let mut wsum = 0.0;
let mut vsum = 0.0;
for c in coords {
let d2 = (c[0] - x).powi(2) + (c[1] - y).powi(2);
if d2 == 0.0 {
return c[2]; }
let w = 1.0 / d2.powf(IDW_POWER / 2.0);
wsum += w;
vsum += w * c[2];
}
if wsum > 0.0 {
vsum / wsum
} else {
f64::NAN
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exact_at_coincident_sample() {
let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 2, 2);
let coords = [
[0.0, 0.0, 10.0],
[1.0, 0.0, 20.0],
[0.0, 1.0, 30.0],
[1.0, 1.0, 40.0],
];
let out = grid_idw(&coords, &lattice);
assert_eq!(out[[0, 0]], 10.0);
assert_eq!(out[[1, 0]], 20.0);
assert_eq!(out[[0, 1]], 30.0);
assert_eq!(out[[1, 1]], 40.0);
}
#[test]
fn interpolated_value_bounded_by_samples() {
let lattice = Lattice::regular(0.5, 0.0, 1.0, 1.0, 1, 1); let coords = [[0.0, 0.0, 0.0], [1.0, 0.0, 100.0]];
let v = grid_idw(&coords, &lattice)[[0, 0]];
assert!(v > 0.0 && v < 100.0, "v = {v}");
assert!((v - 50.0).abs() < 1e-9, "v = {v}");
}
#[test]
fn symmetric_layout_averages() {
let lattice = Lattice::regular(0.5, 0.5, 1.0, 1.0, 1, 1);
let coords = [
[0.0, 0.0, 10.0],
[1.0, 0.0, 20.0],
[0.0, 1.0, 30.0],
[1.0, 1.0, 40.0],
];
let v = grid_idw(&coords, &lattice)[[0, 0]];
assert!((v - 25.0).abs() < 1e-9, "v = {v}");
}
}