use ndarray::Array2;
use crate::cell_size::CellSize;
use crate::kernel::{grid_map, horn_gradients};
pub const ASPECT_FLAT: f64 = -1.0;
pub fn aspect(dem: &Array2<f64>, cell_size: CellSize) -> Array2<f64> {
let (h, w) = dem.dim();
if h < 3 || w < 3 {
return Array2::from_elem((h, w), ASPECT_FLAT);
}
grid_map(h, w, |row, col| {
let (dzdx, dzdy) = horn_gradients(dem, row, col, cell_size);
if dzdx.abs() < f64::EPSILON && dzdy.abs() < f64::EPSILON {
return ASPECT_FLAT;
}
let mut deg = (-dzdx).atan2(dzdy).to_degrees();
if deg < 0.0 {
deg += 360.0;
}
if deg >= 360.0 {
deg -= 360.0;
}
deg
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flat_dem_returns_sentinel() {
let dem = Array2::from_elem((10, 10), 100.0);
let a = aspect(&dem, CellSize::square(1.0).unwrap());
for &v in a.iter() {
assert_eq!(v, ASPECT_FLAT, "flat DEM should have aspect -1");
}
}
#[test]
fn east_increasing_faces_west() {
let dem = Array2::from_shape_fn((10, 10), |(_, c)| c as f64 * 10.0);
let a = aspect(&dem, CellSize::square(1.0).unwrap());
let v = a[[5, 5]];
assert!(
(v - 270.0).abs() < 1.0,
"east-increasing slope should face ~270 (west), got {v}"
);
}
#[test]
fn north_increasing_faces_south() {
let dem = Array2::from_shape_fn((10, 10), |(r, _)| (9 - r) as f64 * 10.0);
let a = aspect(&dem, CellSize::square(1.0).unwrap());
let v = a[[5, 5]];
assert!(
(v - 180.0).abs() < 1.0,
"north-increasing slope should face ~180 (south), got {v}"
);
}
#[test]
fn range_check() {
let dem = Array2::from_shape_fn((20, 20), |(r, c)| {
(r as f64 * 2.0 + c as f64 * 3.0).sin() * 50.0
});
let a = aspect(&dem, CellSize::square(1.0).unwrap());
for &v in a.iter() {
assert!(
v == ASPECT_FLAT || (0.0..360.0).contains(&v),
"aspect {v} out of range"
);
}
}
#[test]
fn small_grid_returns_flat() {
let dem = Array2::from_elem((2, 2), 50.0);
let a = aspect(&dem, CellSize::square(1.0).unwrap());
for &v in a.iter() {
assert_eq!(v, ASPECT_FLAT);
}
}
#[test]
fn consistency_with_slope() {
let dem = Array2::from_elem((10, 10), 42.0);
let s = crate::slope(&dem, CellSize::square(1.0).unwrap());
let a = aspect(&dem, CellSize::square(1.0).unwrap());
for r in 0..10 {
for c in 0..10 {
if s[[r, c]].abs() < 1e-6 {
assert_eq!(a[[r, c]], ASPECT_FLAT);
}
}
}
}
}