use ndarray::Array2;
use crate::CellSize;
pub(crate) fn grid_map<T, F>(h: usize, w: usize, f: F) -> Array2<T>
where
T: Send,
F: Fn(usize, usize) -> T + Sync,
{
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
let data: Vec<T> = (0..h * w)
.into_par_iter()
.map(|i| f(i / w, i % w))
.collect();
Array2::from_shape_vec((h, w), data).unwrap()
}
#[cfg(not(feature = "parallel"))]
{
Array2::from_shape_fn((h, w), |(y, x)| f(y, x))
}
}
#[inline]
pub(crate) fn get_extrapolated(
dem: &Array2<f64>,
row: usize,
col: usize,
dy: isize,
dx: isize,
) -> f64 {
let (h, w) = dem.dim();
let iy = row as isize + dy;
let ix = col as isize + dx;
let hi = h as isize;
let wi = w as isize;
if iy < 0 {
let cx = ix.clamp(0, wi - 1) as usize;
return 2.0 * dem[[0, cx]] - dem[[1, cx]];
} else if iy >= hi {
let cx = ix.clamp(0, wi - 1) as usize;
return 2.0 * dem[[h - 1, cx]] - dem[[h - 2, cx]];
}
let ny = iy as usize;
if ix < 0 {
return 2.0 * dem[[ny, 0]] - dem[[ny, 1]];
} else if ix >= wi {
return 2.0 * dem[[ny, w - 1]] - dem[[ny, w - 2]];
}
dem[[ny, ix as usize]]
}
#[inline]
pub(crate) fn get_clamped(dem: &Array2<f64>, row: usize, col: usize, dy: isize, dx: isize) -> f64 {
let (h, w) = dem.dim();
let ny = (row as isize + dy).clamp(0, h as isize - 1) as usize;
let nx = (col as isize + dx).clamp(0, w as isize - 1) as usize;
dem[[ny, nx]]
}
#[inline]
pub(crate) fn horn_gradients(
dem: &Array2<f64>,
row: usize,
col: usize,
cs: CellSize,
) -> (f64, f64) {
let g = |dy, dx| get_extrapolated(dem, row, col, dy, dx);
let a = g(-1, -1);
let b = g(-1, 0);
let c = g(-1, 1);
let d = g(0, -1);
let f = g(0, 1);
let gv = g(1, -1);
let h = g(1, 0);
let i = g(1, 1);
let dzdx = ((c + 2.0 * f + i) - (a + 2.0 * d + gv)) / (8.0 * cs.x());
let dzdy = ((gv + 2.0 * h + i) - (a + 2.0 * b + c)) / (8.0 * cs.y());
(dzdx, dzdy)
}
#[inline]
pub(crate) fn horn_second_derivatives(
dem: &Array2<f64>,
row: usize,
col: usize,
cs: CellSize,
) -> (f64, f64, f64) {
let g = |dy, dx| get_clamped(dem, row, col, dy, dx);
let a = g(-1, -1);
let b = g(-1, 0);
let c = g(-1, 1);
let d = g(0, -1);
let e = g(0, 0);
let f = g(0, 1);
let gv = g(1, -1);
let h = g(1, 0);
let i = g(1, 1);
let d2zdx2 = (d - 2.0 * e + f) / (cs.x() * cs.x());
let d2zdy2 = (b - 2.0 * e + h) / (cs.y() * cs.y());
let d2zdxdy = ((c + gv) - (a + i)) / (4.0 * cs.x() * cs.y());
(d2zdx2, d2zdy2, d2zdxdy)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_extrapolated_interior() {
let dem = Array2::from_shape_fn((5, 5), |(r, c)| (r * 5 + c) as f64);
assert_eq!(get_extrapolated(&dem, 2, 2, 0, 0), 12.0);
assert_eq!(get_extrapolated(&dem, 2, 2, -1, 0), 7.0);
}
#[test]
fn test_get_extrapolated_edge() {
let dem = Array2::from_shape_fn((5, 5), |(r, _)| r as f64 * 10.0);
assert_eq!(get_extrapolated(&dem, 0, 2, -1, 0), -10.0);
assert_eq!(get_extrapolated(&dem, 4, 2, 1, 0), 50.0);
}
#[test]
fn test_horn_gradients_flat() {
let dem = Array2::from_elem((5, 5), 100.0);
let (dzdx, dzdy) = horn_gradients(&dem, 2, 2, CellSize::square(1.0).unwrap());
assert!(dzdx.abs() < 1e-10);
assert!(dzdy.abs() < 1e-10);
}
#[test]
fn test_horn_gradients_x_slope() {
let dem = Array2::from_shape_fn((5, 5), |(_, c)| c as f64 * 10.0);
let (dzdx, dzdy) = horn_gradients(&dem, 2, 2, CellSize::square(1.0).unwrap());
assert!((dzdx - 10.0).abs() < 1e-6, "dzdx should be 10, got {dzdx}");
assert!(dzdy.abs() < 1e-6, "dzdy should be ~0, got {dzdy}");
}
#[test]
fn test_horn_second_derivatives_flat() {
let dem = Array2::from_elem((5, 5), 100.0);
let (d2x, d2y, d2xy) = horn_second_derivatives(&dem, 2, 2, CellSize::square(1.0).unwrap());
assert!(d2x.abs() < 1e-10);
assert!(d2y.abs() < 1e-10);
assert!(d2xy.abs() < 1e-10);
}
#[test]
fn test_horn_second_derivatives_parabolic() {
let dem = Array2::from_shape_fn((5, 5), |(_, c)| (c as f64).powi(2));
let (d2x, _, _) = horn_second_derivatives(&dem, 2, 2, CellSize::square(1.0).unwrap());
assert!(
(d2x - 2.0).abs() < 1e-6,
"d2z/dx2 of x^2 should be 2, got {d2x}"
);
}
}