use crate::error::StaticError;
use crate::grid::Grid;
use crate::model::model::Georef;
use crate::model::pipeline::areal_lattice;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct ValueRange {
pub min: f64,
pub max: f64,
}
impl ValueRange {
pub(crate) fn of(it: impl Iterator<Item = f64>) -> Self {
let (mut min, mut max) = (f64::INFINITY, f64::NEG_INFINITY);
for v in it {
if v.is_finite() {
min = min.min(v);
max = max.max(v);
}
}
if min <= max {
Self { min, max }
} else {
Self {
min: f64::NAN,
max: f64::NAN,
}
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct GridFrame {
pub origin_x: f64,
pub origin_y: f64,
pub spacing_x: f64,
pub spacing_y: f64,
pub ncol: usize,
pub nrow: usize,
#[serde(default, skip_serializing_if = "is_zero_rotation")]
pub rotation_deg: f64,
#[serde(default, skip_serializing_if = "is_false")]
pub yflip: bool,
}
fn is_zero_rotation(value: &f64) -> bool {
*value == 0.0
}
fn is_false(value: &bool) -> bool {
!*value
}
impl GridFrame {
pub(crate) fn of_grid(grid: &Grid, georef: Option<Georef>) -> Result<Self, StaticError> {
if let Some(g) = georef {
let dims = grid.dims();
let (ncol, nrow) = (dims.ni, dims.nj);
if ncol < 2 || nrow < 2 {
return Err(StaticError::InvalidInput(format!(
"view frame needs an areal lattice of at least 2x2 columns, got {ncol}x{nrow}"
)));
}
return Ok(Self {
origin_x: g.origin_x,
origin_y: g.origin_y,
spacing_x: g.spacing_x,
spacing_y: g.spacing_y,
ncol,
nrow,
rotation_deg: g.rotation_deg,
yflip: g.yflip,
});
}
let lat = areal_lattice(grid)?;
Ok(Self {
origin_x: lat.xori,
origin_y: lat.yori,
spacing_x: lat.xinc,
spacing_y: lat.yinc,
ncol: lat.ncol,
nrow: lat.nrow,
rotation_deg: lat.rotation_deg,
yflip: lat.yflip,
})
}
#[must_use]
pub fn intrinsic_to_world(self, fi: f64, fj: f64) -> (f64, f64) {
Georef {
origin_x: self.origin_x,
origin_y: self.origin_y,
spacing_x: self.spacing_x,
spacing_y: self.spacing_y,
rotation_deg: self.rotation_deg,
yflip: self.yflip,
}
.intrinsic_to_world(fi, fj)
}
#[must_use]
pub fn world_to_intrinsic(self, x: f64, y: f64) -> Option<(f64, f64)> {
Georef {
origin_x: self.origin_x,
origin_y: self.origin_y,
spacing_x: self.spacing_x,
spacing_y: self.spacing_y,
rotation_deg: self.rotation_deg,
yflip: self.yflip,
}
.world_to_intrinsic(x, y)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ScalarLayer {
pub name: String,
pub units: String,
pub values: Vec<f64>,
pub range: ValueRange,
}
impl ScalarLayer {
pub(crate) fn new(name: impl Into<String>, units: impl Into<String>, values: Vec<f64>) -> Self {
let range = ValueRange::of(values.iter().copied());
Self {
name: name.into(),
units: units.into(),
values,
range,
}
}
}