#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Point3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Point3 {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BBox {
pub xmin: f64,
pub ymin: f64,
pub xmax: f64,
pub ymax: f64,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GridGeometry {
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 GridGeometry {
pub fn yflip_factor(&self) -> f64 {
if self.yflip {
-1.0
} else {
1.0
}
}
pub fn is_axis_aligned(&self) -> bool {
self.rotation_deg == 0.0
}
pub(crate) fn to_lattice(&self) -> petektools::Lattice {
petektools::Lattice {
xori: self.xori,
yori: self.yori,
xinc: self.xinc,
yinc: self.yinc,
ncol: self.ncol,
nrow: self.nrow,
rotation_deg: self.rotation_deg,
yflip: self.yflip,
}
}
pub fn node_xy(&self, i: usize, j: usize) -> (f64, f64) {
self.to_lattice().node_xy(i, j)
}
pub fn xy_to_ij(&self, x: f64, y: f64) -> Option<(f64, f64)> {
self.to_lattice().xy_to_ij(x, y)
}
pub fn bbox(&self) -> BBox {
let bbox = self.to_lattice().bbox();
BBox {
xmin: bbox.xmin,
ymin: bbox.ymin,
xmax: bbox.xmax,
ymax: bbox.ymax,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn geom(rotation_deg: f64, yflip: bool) -> GridGeometry {
GridGeometry {
xori: 1000.0,
yori: 2000.0,
xinc: 50.0,
yinc: 25.0,
ncol: 3,
nrow: 4,
rotation_deg,
yflip,
}
}
#[test]
fn origin_node_is_origin() {
let g = geom(30.0, false);
let (x, y) = g.node_xy(0, 0);
assert_relative_eq!(x, 1000.0);
assert_relative_eq!(y, 2000.0);
}
#[test]
fn node_xy_inverse_roundtrip_rotated() {
let g = geom(30.0, false);
for &(i, j) in &[(0, 0), (2, 0), (0, 3), (2, 3), (1, 2)] {
let (x, y) = g.node_xy(i, j);
let (fi, fj) = g.xy_to_ij(x, y).unwrap();
assert_relative_eq!(fi, i as f64, epsilon = 1e-9);
assert_relative_eq!(fj, j as f64, epsilon = 1e-9);
}
}
#[test]
fn node_xy_inverse_roundtrip_yflip() {
let g = geom(15.0, true);
for &(i, j) in &[(0, 0), (2, 0), (0, 3), (1, 2)] {
let (x, y) = g.node_xy(i, j);
let (fi, fj) = g.xy_to_ij(x, y).unwrap();
assert_relative_eq!(fi, i as f64, epsilon = 1e-9);
assert_relative_eq!(fj, j as f64, epsilon = 1e-9);
}
}
#[test]
fn fractional_world_cell_roundtrip_matches_tools_lattice() {
let g = geom(30.0, true);
let lattice = g.to_lattice();
for &(i, j) in &[(0, 0), (2, 0), (0, 3), (1, 2)] {
assert_eq!(g.node_xy(i, j), lattice.node_xy(i, j));
}
let (origin_x, origin_y) = g.node_xy(0, 0);
let (i_x, i_y) = g.node_xy(1, 0);
let (j_x, j_y) = g.node_xy(0, 1);
let (fi, fj) = (1.25, 2.5);
let world = (
origin_x + fi * (i_x - origin_x) + fj * (j_x - origin_x),
origin_y + fi * (i_y - origin_y) + fj * (j_y - origin_y),
);
let own = g.xy_to_ij(world.0, world.1).unwrap();
let tools = lattice.xy_to_ij(world.0, world.1).unwrap();
assert_eq!(own, tools);
assert_relative_eq!(own.0, fi, epsilon = 1e-10);
assert_relative_eq!(own.1, fj, epsilon = 1e-10);
assert_eq!(g.bbox().xmin, lattice.bbox().xmin);
assert_eq!(g.bbox().ymax, lattice.bbox().ymax);
}
#[test]
fn zero_rotation_delegation_is_bit_compatible() {
let g = geom(0.0, false);
assert_eq!(g.node_xy(2, 3), (1100.0, 2075.0));
assert_eq!(g.xy_to_ij(1025.0, 2037.5), Some((0.5, 1.5)));
}
#[test]
fn axis_aligned_node_positions() {
let g = geom(0.0, false);
assert_relative_eq!(g.node_xy(2, 0).0, 1100.0); assert_relative_eq!(g.node_xy(0, 3).1, 2075.0); }
}