Skip to main content

terrand/
cell_size.rs

1//! Cell-size metadata for regular DEM grids.
2
3use crate::error::{Error, Result};
4
5/// Cell dimensions of a regular grid.
6///
7/// Both `x` and `y` are in the same linear units as the elevation values
8/// (typically meters). For geographic DEMs whose native units are degrees,
9/// convert to meters first or use approximate meter-equivalent values.
10///
11/// # Examples
12///
13/// ```
14/// use terrand::CellSize;
15///
16/// // 30-meter SRTM grid
17/// let cs = CellSize::square(30.0).unwrap();
18/// assert_eq!(cs.x(), 30.0);
19/// assert_eq!(cs.y(), 30.0);
20///
21/// // Non-square pixels
22/// let cs = CellSize::new(25.0, 30.0).unwrap();
23/// ```
24#[derive(Clone, Copy, Debug, PartialEq)]
25pub struct CellSize {
26    /// Cell width in the x (column) direction.
27    x: f64,
28    /// Cell height in the y (row) direction.
29    y: f64,
30}
31
32impl CellSize {
33    /// Create a cell size with independent x and y dimensions.
34    ///
35    /// Both dimensions must be positive and finite.
36    ///
37    /// # Errors
38    ///
39    /// Returns [`Error::InvalidCellSize`] if either dimension is zero,
40    /// negative, NaN, or infinite.
41    #[inline]
42    pub fn new(x: f64, y: f64) -> Result<Self> {
43        if x.is_finite() && y.is_finite() && x > 0.0 && y > 0.0 {
44            Ok(Self { x, y })
45        } else {
46            Err(Error::InvalidCellSize { x, y })
47        }
48    }
49
50    /// Create a square cell size where x and y are equal.
51    ///
52    /// The dimension must be positive and finite.
53    ///
54    /// # Errors
55    ///
56    /// Returns [`Error::InvalidCellSize`] if `size` is zero, negative, NaN, or
57    /// infinite.
58    #[inline]
59    pub fn square(size: f64) -> Result<Self> {
60        Self::new(size, size)
61    }
62
63    /// Cell width in the x (column) direction.
64    #[inline]
65    pub fn x(&self) -> f64 {
66        self.x
67    }
68
69    /// Cell height in the y (row) direction.
70    #[inline]
71    pub fn y(&self) -> f64 {
72        self.y
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_square() {
82        let cs = CellSize::square(30.0).unwrap();
83        assert_eq!(cs.x(), 30.0);
84        assert_eq!(cs.y(), 30.0);
85    }
86
87    #[test]
88    fn test_non_square() {
89        let cs = CellSize::new(25.0, 30.0).unwrap();
90        assert_eq!(cs.x(), 25.0);
91        assert_eq!(cs.y(), 30.0);
92    }
93
94    #[test]
95    fn rejects_invalid_dimensions() {
96        for (x, y) in [
97            (0.0, 1.0),
98            (1.0, 0.0),
99            (-1.0, 1.0),
100            (1.0, -1.0),
101            (f64::NAN, 1.0),
102            (1.0, f64::NAN),
103            (f64::INFINITY, 1.0),
104            (1.0, f64::NEG_INFINITY),
105        ] {
106            assert!(CellSize::new(x, y).is_err(), "accepted ({x}, {y})");
107        }
108        assert!(CellSize::square(0.0).is_err());
109    }
110}