use crate::errors::{Error, Result};
use crate::math::{adjlon, consts::PI};
use crate::transform::Direction;
use std::fmt::{self, Display};
pub(crate) const REL_TOLERANCE_HGRIDSHIFT: f64 = 1.0e-5;
#[derive(Debug)]
pub(crate) struct Lp {
pub lam: f64,
pub phi: f64,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) struct GridId([u8; 8]);
impl Default for GridId {
fn default() -> Self {
Self::root()
}
}
impl PartialEq<[u8; 8]> for GridId {
fn eq(&self, other: &[u8; 8]) -> bool {
self.0 == *other
}
}
impl GridId {
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0).unwrap_or("<n/a>")
}
pub const fn root() -> Self {
Self([0u8; 8])
}
}
impl From<[u8; 8]> for GridId {
fn from(v: [u8; 8]) -> Self {
Self(v)
}
}
impl From<u64> for GridId {
fn from(v: u64) -> Self {
Self(v.to_ne_bytes())
}
}
impl From<(u32, u32)> for GridId {
fn from(p: (u32, u32)) -> Self {
let mut v: [u8; 8] = [0; 8];
v[..4].copy_from_slice(&p.0.to_ne_bytes());
v[4..].copy_from_slice(&p.1.to_ne_bytes());
Self(v)
}
}
#[derive(Debug)]
pub struct Grid {
pub(crate) id: GridId,
pub(crate) lineage: GridId,
pub(crate) ll: Lp,
pub(crate) ur: Lp,
pub(crate) del: Lp,
pub(crate) lim: Lp,
pub(crate) epsilon: f64,
pub(crate) cvs: Box<[Lp]>,
}
impl Display for Grid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Id: {}", self.id.as_str())?;
writeln!(f, "Lineage: {}", self.lineage.as_str())?;
writeln!(
f,
"Lower Left ({:>12.7}, {:>12.7})",
self.ll.lam.to_degrees(),
self.ll.phi.to_degrees()
)?;
writeln!(
f,
"Upper Right ({:>12.7}, {:>12.7})",
self.ur.lam.to_degrees(),
self.ur.phi.to_degrees()
)?;
writeln!(
f,
"Size ({} x {})",
self.lim.lam as usize, self.lim.phi as usize
)?;
writeln!(
f,
"Delta ({},{})",
self.del.lam.to_degrees(),
self.del.phi.to_degrees()
)
}
}
impl Grid {
#[inline]
pub fn is_child_of(&self, other: &Grid) -> bool {
self.lineage == other.id
}
#[inline]
pub fn is_root(&self) -> bool {
const ROOT: GridId = GridId::root();
self.lineage == ROOT
}
pub fn matches(&self, lam: f64, phi: f64, _z: f64) -> bool {
!(self.ll.phi - self.epsilon > phi
|| self.ll.lam - self.epsilon > lam
|| self.ll.phi + (self.lim.phi - 1.) * self.del.phi + self.epsilon < phi
|| self.ll.lam + (self.lim.lam - 1.) * self.del.lam + self.epsilon < lam)
}
#[inline]
pub fn num_rows(&self) -> usize {
self.lim.phi as usize
}
#[inline]
pub fn row_len(&self) -> usize {
self.lim.lam as usize
}
pub fn gs_count(&self) -> usize {
self.num_rows() * self.row_len()
}
pub(crate) fn nad_cvt(
&self,
dir: Direction,
lam: f64,
phi: f64,
z: f64,
) -> Result<(f64, f64, f64)> {
match dir {
Direction::Forward => self.nad_cvt_forward(lam, phi, z),
Direction::Inverse => self.nad_cvt_inverse(lam, phi, z),
}
}
fn nad_cvt_forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
let (t_lam, t_phi) = self.nad_intr(
adjlon(lam - self.ll.lam - PI) + PI,
phi - self.ll.phi,
)?;
Ok((lam + t_lam, phi + t_phi, z))
}
fn nad_cvt_inverse(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
const MAX_ITER: usize = 10;
const TOL: f64 = 1.0e-24;
let (tb_lam, tb_phi) = (adjlon(lam - self.ll.lam - PI) + PI, phi - self.ll.phi);
let (mut t_lam, mut t_phi) = self.nad_intr(tb_lam, tb_phi)?;
t_lam = tb_lam - t_lam;
t_phi = tb_phi - t_phi;
let mut i = MAX_ITER;
while i > 0 {
if let Ok((del_lam, del_phi)) = self.nad_intr(t_lam, t_phi) {
let (dif_lam, dif_phi) = (t_lam + del_lam - tb_lam, t_phi + del_phi - tb_phi);
t_lam -= dif_lam;
t_phi -= dif_phi;
if dif_lam * dif_lam + dif_phi * dif_phi <= TOL {
break;
}
i -= 1;
} else {
break;
}
}
if i == 0 {
crate::log::error!("Inverse grid shift iterator failed to converge");
return Err(Error::InverseGridShiftConvError);
}
Ok((adjlon(t_lam + self.ll.lam), t_phi + self.ll.phi, z))
}
fn nad_intr(&self, lam: f64, phi: f64) -> Result<(f64, f64)> {
let (t_lam, t_phi) = (lam / self.del.lam, phi / self.del.phi);
fn _check_lim(t: f64, lim: f64) -> Result<(f64, f64)> {
let mut i = if t.is_nan() { 0. } else { t.floor() };
let mut f = t - i;
if i < 0. {
if i == -1. && f > 1. - 10. * REL_TOLERANCE_HGRIDSHIFT {
i += 1.;
f = 0.;
} else {
return Err(Error::PointOutsideNadShiftArea);
}
} else {
let n = i + 1.;
if n >= lim {
if n == lim && f < 10. * REL_TOLERANCE_HGRIDSHIFT {
i -= 1.;
f = 1.;
} else {
return Err(Error::PointOutsideNadShiftArea);
}
}
}
Ok((i, f))
}
let (i_lam, f_lam) = _check_lim(t_lam, self.lim.lam)?;
let (i_phi, f_phi) = _check_lim(t_phi, self.lim.phi)?;
let mut index = (i_phi * self.lim.lam + i_lam) as usize;
let f00 = &self.cvs[index];
let f10 = &self.cvs[index + 1];
index += self.lim.lam as usize;
let f01 = &self.cvs[index];
let f11 = &self.cvs[index + 1];
let m00 = (1. - f_lam) * (1. - f_phi);
let m01 = (1. - f_lam) * f_phi;
let m10 = f_lam * (1. - f_phi);
let m11 = f_lam * f_phi;
Ok((
m00 * f00.lam + m10 * f10.lam + m01 * f01.lam + m11 * f11.lam,
m00 * f00.phi + m10 * f10.phi + m01 * f01.phi + m11 * f11.phi,
))
}
}