#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BBox {
pub xmin: f64,
pub ymin: f64,
pub xmax: f64,
pub ymax: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Lattice {
pub xori: f64,
pub yori: f64,
pub xinc: f64,
pub yinc: f64,
pub ncol: usize,
pub nrow: usize,
pub rotation_deg: f64,
pub yflip: bool,
}
impl Lattice {
pub fn regular(xori: f64, yori: f64, xinc: f64, yinc: f64, ncol: usize, nrow: usize) -> Self {
Self {
xori,
yori,
xinc,
yinc,
ncol,
nrow,
rotation_deg: 0.0,
yflip: false,
}
}
pub fn yflip_factor(&self) -> f64 {
if self.yflip {
-1.0
} else {
1.0
}
}
pub fn node_xy(&self, i: usize, j: usize) -> (f64, f64) {
let (s, c) = self.rotation_deg.to_radians().sin_cos();
let di = i as f64 * self.xinc;
let dj = j as f64 * self.yinc * self.yflip_factor();
(self.xori + di * c - dj * s, self.yori + di * s + dj * c)
}
pub fn xy_to_ij(&self, x: f64, y: f64) -> Option<(f64, f64)> {
if self.xinc == 0.0 || self.yinc == 0.0 {
return None;
}
let (s, c) = self.rotation_deg.to_radians().sin_cos();
let dx = x - self.xori;
let dy = y - self.yori;
let u = dx * c + dy * s; let v = -dx * s + dy * c; Some((u / self.xinc, v / (self.yinc * self.yflip_factor())))
}
pub fn bbox(&self) -> BBox {
let ni = self.ncol.saturating_sub(1);
let nj = self.nrow.saturating_sub(1);
let corners = [
self.node_xy(0, 0),
self.node_xy(ni, 0),
self.node_xy(0, nj),
self.node_xy(ni, nj),
];
let xmin = corners.iter().map(|p| p.0).fold(f64::INFINITY, f64::min);
let xmax = corners
.iter()
.map(|p| p.0)
.fold(f64::NEG_INFINITY, f64::max);
let ymin = corners.iter().map(|p| p.1).fold(f64::INFINITY, f64::min);
let ymax = corners
.iter()
.map(|p| p.1)
.fold(f64::NEG_INFINITY, f64::max);
BBox {
xmin,
ymin,
xmax,
ymax,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn origin_and_roundtrip() {
let g = Lattice::regular(1000.0, 2000.0, 25.0, 50.0, 10, 8);
assert_eq!(g.node_xy(0, 0), (1000.0, 2000.0));
let (x, y) = g.node_xy(3, 4);
let (fi, fj) = g.xy_to_ij(x, y).unwrap();
assert_relative_eq!(fi, 3.0, epsilon = 1e-9);
assert_relative_eq!(fj, 4.0, epsilon = 1e-9);
}
#[test]
fn rotation_roundtrips() {
let mut g = Lattice::regular(0.0, 0.0, 10.0, 10.0, 5, 5);
g.rotation_deg = 30.0;
let (x, y) = g.node_xy(2, 1);
let (fi, fj) = g.xy_to_ij(x, y).unwrap();
assert_relative_eq!(fi, 2.0, epsilon = 1e-9);
assert_relative_eq!(fj, 1.0, epsilon = 1e-9);
}
#[test]
fn degenerate_geometry_has_no_inverse() {
let g = Lattice::regular(0.0, 0.0, 0.0, 10.0, 5, 5);
assert!(g.xy_to_ij(1.0, 1.0).is_none());
}
}