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}