diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bcd1ed..0265de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - CoordinateSet: xyz(), set_xyz(), xyzt(), set_xyzt() methods +- Vector space operators (Add, Sub, Mul, Div) for all built + in coordinate tuple types (Coor4D, Coor3D, Coor2D, Coor32) ### Changed diff --git a/src/coordinate/coor3d.rs b/src/coordinate/coor3d.rs index fdb411d..5da4ddd 100644 --- a/src/coordinate/coor3d.rs +++ b/src/coordinate/coor3d.rs @@ -1,5 +1,4 @@ use super::*; -use std::ops::{Add, Div, Mul, Sub}; /// Generic 3D coordinate tuple, with no fixed interpretation of the elements #[derive(Debug, Default, PartialEq, Copy, Clone)] @@ -23,63 +22,6 @@ impl CoordinateTuple for Coor3D { } } -// ----- O P E R A T O R T R A I T S ------------------------------------------------- - -impl Add for Coor3D { - type Output = Self; - fn add(self, other: Self) -> Self { - Coor3D([ - self.0[0] + other.0[0], - self.0[1] + other.0[1], - self.0[2] + other.0[2], - ]) - } -} - -impl Add<&Coor3D> for Coor3D { - type Output = Self; - fn add(self, other: &Self) -> Self { - Coor3D([ - self.0[0] + other.0[0], - self.0[1] + other.0[1], - self.0[2] + other.0[2], - ]) - } -} - -impl Sub for Coor3D { - type Output = Self; - fn sub(self, other: Self) -> Self { - Coor3D([ - self.0[0] - other.0[0], - self.0[1] - other.0[1], - self.0[2] - other.0[2], - ]) - } -} - -impl Mul for Coor3D { - type Output = Self; - fn mul(self, other: Self) -> Self { - Coor3D([ - self.0[0] * other.0[0], - self.0[1] * other.0[1], - self.0[2] * other.0[2], - ]) - } -} - -impl Div for Coor3D { - type Output = Self; - fn div(self, other: Self) -> Self { - Coor3D([ - self.0[0] / other.0[0], - self.0[1] / other.0[1], - self.0[2] / other.0[2], - ]) - } -} - // ----- C O N S T R U C T O R S --------------------------------------------- /// Constructors @@ -177,6 +119,7 @@ impl Coor3D { #[cfg(test)] mod tests { use super::*; + use std::ops::{Add, Div, Mul}; #[test] fn distances() { diff --git a/src/coordinate/coor4d.rs b/src/coordinate/coor4d.rs index 4c2231d..e559dae 100644 --- a/src/coordinate/coor4d.rs +++ b/src/coordinate/coor4d.rs @@ -1,5 +1,4 @@ use crate::coordinate::*; -use std::ops::{Add, Div, Mul, Sub}; /// Generic 4D coordinate tuple, with no fixed interpretation of the elements #[derive(Debug, Default, PartialEq, Copy, Clone)] @@ -23,68 +22,6 @@ impl CoordinateTuple for Coor4D { } } -// ----- O P E R A T O R T R A I T S ------------------------------------------------- - -impl Add for Coor4D { - type Output = Self; - fn add(self, other: Self) -> Self { - Coor4D([ - self.0[0] + other.0[0], - self.0[1] + other.0[1], - self.0[2] + other.0[2], - self.0[3] + other.0[3], - ]) - } -} - -impl Add<&Coor4D> for Coor4D { - type Output = Self; - fn add(self, other: &Self) -> Self { - Coor4D([ - self.0[0] + other.0[0], - self.0[1] + other.0[1], - self.0[2] + other.0[2], - self.0[3] + other.0[3], - ]) - } -} - -impl Sub for Coor4D { - type Output = Self; - fn sub(self, other: Self) -> Self { - Coor4D([ - self.0[0] - other.0[0], - self.0[1] - other.0[1], - self.0[2] - other.0[2], - self.0[3] - other.0[3], - ]) - } -} - -impl Mul for Coor4D { - type Output = Self; - fn mul(self, other: Self) -> Self { - Coor4D([ - self.0[0] * other.0[0], - self.0[1] * other.0[1], - self.0[2] * other.0[2], - self.0[3] * other.0[3], - ]) - } -} - -impl Div for Coor4D { - type Output = Self; - fn div(self, other: Self) -> Self { - Coor4D([ - self.0[0] / other.0[0], - self.0[1] / other.0[1], - self.0[2] / other.0[2], - self.0[3] / other.0[3], - ]) - } -} - // ----- C O N S T R U C T O R S --------------------------------------------- /// Constructors @@ -161,6 +98,7 @@ impl Coor4D { #[cfg(test)] mod tests { use super::*; + use std::ops::{Add, Div, Mul}; #[test] fn distances() { diff --git a/src/coordinate/mod.rs b/src/coordinate/mod.rs index 5b9960a..b7bf12a 100644 --- a/src/coordinate/mod.rs +++ b/src/coordinate/mod.rs @@ -1,34 +1,11 @@ use crate::prelude::*; +pub mod set; +pub mod tuple; + pub mod coor2d; pub mod coor32; pub mod coor3d; pub mod coor4d; -pub mod set; - -// Implement indexing for the primary CoorND types -use std::ops::{Index, IndexMut}; - -macro_rules! coord_indexing { - ($type:ty, $output:ty) => { - impl Index for $type { - type Output = $output; - fn index(&self, i: usize) -> &Self::Output { - &self.0[i] - } - } - - impl IndexMut for $type { - fn index_mut(&mut self, i: usize) -> &mut Self::Output { - &mut self.0[i] - } - } - }; -} - -coord_indexing!(Coor2D, f64); -coord_indexing!(Coor3D, f64); -coord_indexing!(Coor4D, f64); -coord_indexing!(Coor32, f32); /// Methods for changing the coordinate representation of angles. /// Dimensionality untold, the methods operate on the first two @@ -142,364 +119,3 @@ pub trait CoordinateMetadata { // Preliminary empty blanket implementation: Defaults for all items, for all types impl CoordinateMetadata for T where T: ?Sized {} - -/// CoordinateTuple is the ISO-19111 atomic spatial/spatiotemporal -/// referencing element. So loosely speaking, a CoordinateSet is a -/// collection of CoordinateTuples. -/// -/// Note that (despite the formal name) the underlying data structure -/// need not be a tuple: It can be any item, for which it makes sense -/// to implement the CoordinateTuple trait. -/// -/// The CoordinateTuple trait provides a number of convenience accessors -/// for accessing single coordinate elements or tuples of subsets. -/// These accessors are pragmatically named (x, y, xy, etc.). While these -/// names may be geodetically naïve, they are suggestive, practical, and -/// aligns well with the internal coordinate order convention of most -/// Geodesy operators. -/// -/// All accessors have default implementations, except the 3 methods -/// [`nth_unchecked()`](Self::nth_unchecked()), -/// [`set_nth_unchecked()`](Self::set_nth_unchecked) and -/// [`dim()`](Self::dim()), -/// which must be provided by the implementer. -/// -/// When accessing dimensions outside of the domain of the CoordinateTuple, -/// [NaN](f64::NAN) will be returned. -pub trait CoordinateTuple { - /// Construct a new `CoordinateTuple``, with all elements set to `fill` - fn new(fill: f64) -> Self; - - /// Access the n'th (0-based) element of the CoordinateTuple. - /// May panic if n >= DIMENSION. - /// See also [`nth()`](Self::nth). - fn nth_unchecked(&self, n: usize) -> f64; - - /// Replace the n'th (0-based) element of the `CoordinateTuple` with `value`. - /// May panic if `n >=` [`dim()`](Self::dim()). - /// See also [`set_nth()`](Self::set_nth). - fn set_nth_unchecked(&mut self, n: usize, value: f64); - - /// Native dimension of the coordinate tuple - fn dim(&self) -> usize; - - /// Access the n'th (0-based) element of the CoordinateTuple. - /// Returns NaN if `n >= DIMENSION`. - /// See also [`nth()`](Self::nth_unchecked). - fn nth(&self, n: usize) -> f64 { - if n < self.dim() { - self.nth_unchecked(n) - } else { - f64::NAN - } - } - - // Note: We use nth_unchecked and explicitly check for dimension in - // y(), z() and t(), rather than leaving the check to nth(...). - // This is because the checks in these cases are constant expressions, and - // hence can be eliminated by the compiler in the concrete implementations. - - /// Pragmatically named accessor for the first element of the CoordinateTuple. - fn x(&self) -> f64 { - self.nth_unchecked(0) - } - - /// Pragmatically named accessor for the second element of the CoordinateTuple. - fn y(&self) -> f64 { - if self.dim() > 1 { - self.nth_unchecked(1) - } else { - f64::NAN - } - } - - /// Pragmatically named accessor for the third element of the CoordinateTuple. - fn z(&self) -> f64 { - if self.dim() > 2 { - self.nth_unchecked(2) - } else { - f64::NAN - } - } - - /// Pragmatically named accessor for the fourth element of the CoordinateTuple. - fn t(&self) -> f64 { - if self.dim() > 3 { - self.nth_unchecked(3) - } else { - f64::NAN - } - } - - /// A tuple containing the first two components of the CoordinateTuple. - fn xy(&self) -> (f64, f64) { - (self.x(), self.y()) - } - - /// A tuple containing the first three components of the CoordinateTuple. - fn xyz(&self) -> (f64, f64, f64) { - (self.x(), self.y(), self.z()) - } - - /// A tuple containing the first four components of the CoordinateTuple. - fn xyzt(&self) -> (f64, f64, f64, f64) { - (self.x(), self.y(), self.z(), self.t()) - } - - /// A tuple containing the first two components of the CoordinateTuple - /// converted from radians to degrees - fn xy_to_degrees(&self) -> (f64, f64) { - (self.x().to_degrees(), self.y().to_degrees()) - } - - /// A tuple containing the first three components of the CoordinateTuple, - /// with the first two converted from radians to degrees. - fn xyz_to_degrees(&self) -> (f64, f64, f64) { - (self.x().to_degrees(), self.y().to_degrees(), self.z()) - } - - /// A tuple containing the first four components of the CoordinateTuple, - /// with the first two converted from radians to degrees. - fn xyzt_to_degrees(&self) -> (f64, f64, f64, f64) { - ( - self.x().to_degrees(), - self.y().to_degrees(), - self.z(), - self.t(), - ) - } - - /// A tuple containing the first two components of the CoordinateTuple, - /// converted from radians to seconds-of-arc - fn xy_to_arcsec(&self) -> (f64, f64) { - (self.x().to_degrees() * 3600., self.y().to_degrees() * 3600.) - } - - /// A tuple containing the first three components of the CoordinateTuple, - /// with the first two converted to seconds-of-arc - fn xyz_to_arcsec(&self) -> (f64, f64, f64) { - ( - self.x().to_degrees() * 3600., - self.y().to_degrees() * 3600., - self.z(), - ) - } - - /// A tuple containing the first four components of the CoordinateTuple, - /// with the first two converted to seconds-of-arc - fn xyzt_to_arcsec(&self) -> (f64, f64, f64, f64) { - ( - self.x().to_degrees() * 3600., - self.y().to_degrees() * 3600., - self.z(), - self.t(), - ) - } - - /// A tuple containing the first two components of the CoordinateTuple, - /// converted from degrees to radians - fn xy_to_radians(&self) -> (f64, f64) { - (self.x().to_radians(), self.y().to_radians()) - } - - /// A tuple containing the first three components of the CoordinateTuple, - /// with the first two converted from degrees to radians - fn xyz_to_radians(&self) -> (f64, f64, f64) { - (self.x().to_radians(), self.y().to_radians(), self.z()) - } - - /// A tuple containing the first four components of the CoordinateTuple, - /// with the first two converted from degrees to radians - fn xyzt_to_radians(&self) -> (f64, f64, f64, f64) { - ( - self.x().to_radians(), - self.y().to_radians(), - self.z(), - self.t(), - ) - } - - /// Fill all elements of `self` with `value` - fn fill(&mut self, value: f64) { - for n in 0..self.dim() { - self.set_nth_unchecked(n, value); - } - } - - /// Replace the n'th (0-based) element of the `CoordinateTuple` with `value`. - /// If `n >=` [`dim()`](Self::dim()) fill the coordinate with `f64::NAN`. - /// See also [`set_nth_unchecked()`](Self::set_nth_unchecked). - fn set_nth(&mut self, n: usize, value: f64) { - if n < self.dim() { - self.set_nth_unchecked(n, value) - } else { - self.fill(f64::NAN); - } - } - - /// Replace the two first elements of the `CoordinateTuple` with `x` and `y`. - /// If the dimension is less than 2, fill the coordinate with `f64::NAN`. - /// See also [`set_nth_unchecked()`](Self::set_nth_unchecked). - fn set_xy(&mut self, x: f64, y: f64) { - if self.dim() > 1 { - self.set_nth_unchecked(0, x); - self.set_nth_unchecked(1, y); - } else { - self.fill(f64::NAN); - } - } - - /// Replace the three first elements of the `CoordinateTuple` with `x`, `y` and `z`. - /// If the dimension is less than 3, fill the coordinate with `f64::NAN`. - fn set_xyz(&mut self, x: f64, y: f64, z: f64) { - if self.dim() > 2 { - self.set_nth_unchecked(0, x); - self.set_nth_unchecked(1, y); - self.set_nth_unchecked(2, z); - } else { - self.fill(f64::NAN); - } - } - - /// Replace the four first elements of the `CoordinateTuple` with `x`, `y` `z` and `t`. - /// If the dimension is less than 4, fill the coordinate with `f64::NAN`. - fn set_xyzt(&mut self, x: f64, y: f64, z: f64, t: f64) { - if self.dim() > 3 { - self.set_nth_unchecked(0, x); - self.set_nth_unchecked(1, y); - self.set_nth_unchecked(2, z); - self.set_nth_unchecked(3, t); - } else { - self.fill(f64::NAN); - } - } - - /// Replace the `N` first (up to [`dim()`](Self::dim())) elements of `self` with the - /// elements of `value` - #[allow(clippy::needless_range_loop)] - fn update(&mut self, value: &[f64]) { - let n = value.len().min(self.dim()); - for i in 0..n { - self.set_nth_unchecked(i, value[i]) - } - } - - /// Euclidean distance between two points in the 2D plane. - /// - /// Primarily used to compute the distance between two projected points - /// in their projected plane. Typically, this distance will differ from - /// the actual distance in the real world. - /// - /// # See also: - /// - /// [`hypot3`](Self::hypot3), - /// [`distance`](crate::ellipsoid::Ellipsoid::distance) - /// - /// # Examples - /// - /// ``` - /// use geodesy::prelude::*; - /// let t = 1000 as f64; - /// let p0 = Coor2D::origin(); - /// let p1 = Coor2D::raw(t, t); - /// assert_eq!(p0.hypot2(&p1), t.hypot(t)); - /// ``` - #[must_use] - fn hypot2(&self, other: &Self) -> f64 - where - Self: Sized, - { - let (u, v) = self.xy(); - let (x, y) = other.xy(); - (u - x).hypot(v - y) - } - - /// Euclidean distance between two points in the 3D space. - /// - /// Primarily used to compute the distance between two points in the - /// 3D cartesian space. The typical case is GNSS-observations, in which - /// case, the distance computed will reflect the actual distance - /// in the real world. - /// - /// The distance is computed in the subspace spanned by the first, - /// second and third coordinate of the `Coor3D`s - /// - /// # See also: - /// - /// [`hypot2()`](Self::hypot2), - /// [`distance`](crate::ellipsoid::Ellipsoid::distance) - /// - /// # Examples - /// - /// ``` - /// use geodesy::prelude::*; - /// let t = 1000 as f64; - /// let p0 = Coor3D::origin(); - /// let p1 = Coor3D::raw(t, t, t); - /// assert_eq!(p0.hypot3(&p1), t.hypot(t).hypot(t)); - /// ``` - #[must_use] - fn hypot3(&self, other: &Self) -> f64 - where - Self: Sized, - { - if self.dim() < 3 { - return f64::NAN; - } - let (u, v, w) = self.xyz(); - let (x, y, z) = other.xyz(); - (u - x).hypot(v - y).hypot(w - z) - } - - fn scale(&self, factor: f64) -> Self - where - Self: Sized + Copy, - { - let mut res = *self; - for i in 0..self.dim() { - res.set_nth(i, self.nth(i) * factor); - } - res - } - - fn dot(&self, other: Self) -> f64 - where - Self: Sized, - { - let mut res = 0.; - for i in 0..self.dim() { - res += self.nth(i) * other.nth(i); - } - res - } -} - -// The CoordiateTuple trait is implemented for the main -// newtypes Coor2D, Coor3D, Coor4D, Coor32 in their files -// below. But for good measure, let's also implement it -// for a plain 2D f64 tuple - -#[rustfmt::skip] -impl CoordinateTuple for (f64, f64) { - fn new(fill: f64) -> Self { - (fill, fill) - } - - fn dim(&self) -> usize { 2 } - - fn nth_unchecked(&self, n: usize) -> f64 { - match n { - 0 => self.0, - 1 => self.1, - _ => panic!() - } - } - - fn set_nth_unchecked(&mut self, n: usize, value: f64) { - match n { - 0 => self.0 = value, - 1 => self.1 = value, - _ => () - } - } -} diff --git a/src/coordinate/tuple.rs b/src/coordinate/tuple.rs new file mode 100644 index 0000000..7b3b7d8 --- /dev/null +++ b/src/coordinate/tuple.rs @@ -0,0 +1,497 @@ +use super::*; + +// ---- Indexing for the primary CoorND types ---- + +use std::ops::{Index, IndexMut}; + +macro_rules! coord_indexing { + ($type:ty, $output:ty) => { + impl Index for $type { + type Output = $output; + fn index(&self, i: usize) -> &Self::Output { + &self.0[i] + } + } + + impl IndexMut for $type { + fn index_mut(&mut self, i: usize) -> &mut Self::Output { + &mut self.0[i] + } + } + }; +} + +coord_indexing!(Coor2D, f64); +coord_indexing!(Coor3D, f64); +coord_indexing!(Coor4D, f64); +coord_indexing!(Coor32, f32); + +// ---- Vector space operators for the primary CoorND types ---- + +use std::ops::{Add, Div, Mul, Sub}; + +// Helper for the coord_operator! macro +macro_rules! coor4d { + ($symbol:tt, $self:ident, $other:ident) => { + Coor4D([ + $self.0[0] $symbol ($other.0[0] as f64), + $self.0[1] $symbol ($other.0[1] as f64), + $self.0[2] $symbol ($other.0[2] as f64), + $self.0[3] $symbol ($other.0[3] as f64), + ]) + } +} + +// Helper for the coord_operator! macro +macro_rules! coor3d { + ($symbol:tt, $self:ident, $other:ident) => { + Coor3D([ + $self.0[0] $symbol ($other.0[0] as f64), + $self.0[1] $symbol ($other.0[1] as f64), + $self.0[2] $symbol ($other.0[2] as f64), + ]) + } +} + +// Helper for the coord_operator! macro +macro_rules! coor2d { + ($symbol:tt, $self:ident, $other:ident) => { + Coor2D([ + $self.0[0] $symbol ($other.0[0] as f64), + $self.0[1] $symbol ($other.0[1] as f64), + ]) + } +} + +// Helper for the coord_operator! macro +macro_rules! coor32 { + ($symbol:tt, $self:ident, $other:ident) => { + Coor32([ + $self.0[0] $symbol ($other.0[0] as f32), + $self.0[1] $symbol ($other.0[1] as f32), + ]) + } +} + +// Generate the vector space operators Add, Sub, Mul, Div for $type +macro_rules! coord_operator { + ($type:ty, $othertype:ty, $typemacro:ident, $op:ident, $symbol:tt, $function:ident) => { + impl $op<$othertype> for $type { + type Output = Self; + fn $function(self, other: $othertype) -> Self { + $typemacro!($symbol, self, other) + } + } + }; +} + +coord_operator!(Coor4D, Coor4D, coor4d, Add, +, add); +coord_operator!(Coor4D, Coor4D, coor4d, Sub, -, sub); +coord_operator!(Coor4D, Coor4D, coor4d, Mul, *, mul); +coord_operator!(Coor4D, Coor4D, coor4d, Div, /, div); + +coord_operator!(Coor4D, &Coor4D, coor4d, Add, +, add); +coord_operator!(Coor4D, &Coor4D, coor4d, Sub, -, sub); +coord_operator!(Coor4D, &Coor4D, coor4d, Mul, *, mul); +coord_operator!(Coor4D, &Coor4D, coor4d, Div, /, div); + +coord_operator!(Coor3D, Coor3D, coor3d, Add, +, add); +coord_operator!(Coor3D, Coor3D, coor3d, Sub, -, sub); +coord_operator!(Coor3D, Coor3D, coor3d, Mul, *, mul); +coord_operator!(Coor3D, Coor3D, coor3d, Div, /, div); + +coord_operator!(Coor3D, &Coor3D, coor3d, Add, +, add); +coord_operator!(Coor3D, &Coor3D, coor3d, Sub, -, sub); +coord_operator!(Coor3D, &Coor3D, coor3d, Mul, *, mul); +coord_operator!(Coor3D, &Coor3D, coor3d, Div, /, div); + +coord_operator!(Coor2D, Coor2D, coor2d, Add, +, add); +coord_operator!(Coor2D, Coor2D, coor2d, Sub, -, sub); +coord_operator!(Coor2D, Coor2D, coor2d, Mul, *, mul); +coord_operator!(Coor2D, Coor2D, coor2d, Div, /, div); + +coord_operator!(Coor2D, &Coor2D, coor2d, Add, +, add); +coord_operator!(Coor2D, &Coor2D, coor2d, Sub, -, sub); +coord_operator!(Coor2D, &Coor2D, coor2d, Mul, *, mul); +coord_operator!(Coor2D, &Coor2D, coor2d, Div, /, div); + +coord_operator!(Coor2D, Coor32, coor2d, Add, +, add); +coord_operator!(Coor2D, Coor32, coor2d, Sub, -, sub); +coord_operator!(Coor2D, Coor32, coor2d, Mul, *, mul); +coord_operator!(Coor2D, Coor32, coor2d, Div, /, div); + +coord_operator!(Coor2D, &Coor32, coor2d, Add, +, add); +coord_operator!(Coor2D, &Coor32, coor2d, Sub, -, sub); +coord_operator!(Coor2D, &Coor32, coor2d, Mul, *, mul); +coord_operator!(Coor2D, &Coor32, coor2d, Div, /, div); + +coord_operator!(Coor32, Coor32, coor32, Add, +, add); +coord_operator!(Coor32, Coor32, coor32, Sub, -, sub); +coord_operator!(Coor32, Coor32, coor32, Mul, *, mul); +coord_operator!(Coor32, Coor32, coor32, Div, /, div); + +coord_operator!(Coor32, &Coor32, coor32, Add, +, add); +coord_operator!(Coor32, &Coor32, coor32, Sub, -, sub); +coord_operator!(Coor32, &Coor32, coor32, Mul, *, mul); +coord_operator!(Coor32, &Coor32, coor32, Div, /, div); + +/// CoordinateTuple is the ISO-19111 atomic spatial/spatiotemporal +/// referencing element. So loosely speaking, a CoordinateSet is a +/// collection of CoordinateTuples. +/// +/// Note that (despite the formal name) the underlying data structure +/// need not be a tuple: It can be any item, for which it makes sense +/// to implement the CoordinateTuple trait. +/// +/// The CoordinateTuple trait provides a number of convenience accessors +/// for accessing single coordinate elements or tuples of subsets. +/// These accessors are pragmatically named (x, y, xy, etc.). While these +/// names may be geodetically naïve, they are suggestive, practical, and +/// aligns well with the internal coordinate order convention of most +/// Geodesy operators. +/// +/// All accessors have default implementations, except the 3 methods +/// [`nth_unchecked()`](Self::nth_unchecked()), +/// [`set_nth_unchecked()`](Self::set_nth_unchecked) and +/// [`dim()`](Self::dim()), +/// which must be provided by the implementer. +/// +/// When accessing dimensions outside of the domain of the CoordinateTuple, +/// [NaN](f64::NAN) will be returned. +pub trait CoordinateTuple { + /// Construct a new `CoordinateTuple``, with all elements set to `fill` + fn new(fill: f64) -> Self; + + /// Access the n'th (0-based) element of the CoordinateTuple. + /// May panic if n >= DIMENSION. + /// See also [`nth()`](Self::nth). + fn nth_unchecked(&self, n: usize) -> f64; + + /// Replace the n'th (0-based) element of the `CoordinateTuple` with `value`. + /// May panic if `n >=` [`dim()`](Self::dim()). + /// See also [`set_nth()`](Self::set_nth). + fn set_nth_unchecked(&mut self, n: usize, value: f64); + + /// Native dimension of the coordinate tuple + fn dim(&self) -> usize; + + /// Access the n'th (0-based) element of the CoordinateTuple. + /// Returns NaN if `n >= DIMENSION`. + /// See also [`nth()`](Self::nth_unchecked). + fn nth(&self, n: usize) -> f64 { + if n < self.dim() { + self.nth_unchecked(n) + } else { + f64::NAN + } + } + + // Note: We use nth_unchecked and explicitly check for dimension in + // y(), z() and t(), rather than leaving the check to nth(...). + // This is because the checks in these cases are constant expressions, and + // hence can be eliminated by the compiler in the concrete implementations. + + /// Pragmatically named accessor for the first element of the CoordinateTuple. + fn x(&self) -> f64 { + self.nth_unchecked(0) + } + + /// Pragmatically named accessor for the second element of the CoordinateTuple. + fn y(&self) -> f64 { + if self.dim() > 1 { + self.nth_unchecked(1) + } else { + f64::NAN + } + } + + /// Pragmatically named accessor for the third element of the CoordinateTuple. + fn z(&self) -> f64 { + if self.dim() > 2 { + self.nth_unchecked(2) + } else { + f64::NAN + } + } + + /// Pragmatically named accessor for the fourth element of the CoordinateTuple. + fn t(&self) -> f64 { + if self.dim() > 3 { + self.nth_unchecked(3) + } else { + f64::NAN + } + } + + /// A tuple containing the first two components of the CoordinateTuple. + fn xy(&self) -> (f64, f64) { + (self.x(), self.y()) + } + + /// A tuple containing the first three components of the CoordinateTuple. + fn xyz(&self) -> (f64, f64, f64) { + (self.x(), self.y(), self.z()) + } + + /// A tuple containing the first four components of the CoordinateTuple. + fn xyzt(&self) -> (f64, f64, f64, f64) { + (self.x(), self.y(), self.z(), self.t()) + } + + /// A tuple containing the first two components of the CoordinateTuple + /// converted from radians to degrees + fn xy_to_degrees(&self) -> (f64, f64) { + (self.x().to_degrees(), self.y().to_degrees()) + } + + /// A tuple containing the first three components of the CoordinateTuple, + /// with the first two converted from radians to degrees. + fn xyz_to_degrees(&self) -> (f64, f64, f64) { + (self.x().to_degrees(), self.y().to_degrees(), self.z()) + } + + /// A tuple containing the first four components of the CoordinateTuple, + /// with the first two converted from radians to degrees. + fn xyzt_to_degrees(&self) -> (f64, f64, f64, f64) { + ( + self.x().to_degrees(), + self.y().to_degrees(), + self.z(), + self.t(), + ) + } + + /// A tuple containing the first two components of the CoordinateTuple, + /// converted from radians to seconds-of-arc + fn xy_to_arcsec(&self) -> (f64, f64) { + (self.x().to_degrees() * 3600., self.y().to_degrees() * 3600.) + } + + /// A tuple containing the first three components of the CoordinateTuple, + /// with the first two converted to seconds-of-arc + fn xyz_to_arcsec(&self) -> (f64, f64, f64) { + ( + self.x().to_degrees() * 3600., + self.y().to_degrees() * 3600., + self.z(), + ) + } + + /// A tuple containing the first four components of the CoordinateTuple, + /// with the first two converted to seconds-of-arc + fn xyzt_to_arcsec(&self) -> (f64, f64, f64, f64) { + ( + self.x().to_degrees() * 3600., + self.y().to_degrees() * 3600., + self.z(), + self.t(), + ) + } + + /// A tuple containing the first two components of the CoordinateTuple, + /// converted from degrees to radians + fn xy_to_radians(&self) -> (f64, f64) { + (self.x().to_radians(), self.y().to_radians()) + } + + /// A tuple containing the first three components of the CoordinateTuple, + /// with the first two converted from degrees to radians + fn xyz_to_radians(&self) -> (f64, f64, f64) { + (self.x().to_radians(), self.y().to_radians(), self.z()) + } + + /// A tuple containing the first four components of the CoordinateTuple, + /// with the first two converted from degrees to radians + fn xyzt_to_radians(&self) -> (f64, f64, f64, f64) { + ( + self.x().to_radians(), + self.y().to_radians(), + self.z(), + self.t(), + ) + } + + /// Fill all elements of `self` with `value` + fn fill(&mut self, value: f64) { + for n in 0..self.dim() { + self.set_nth_unchecked(n, value); + } + } + + /// Replace the n'th (0-based) element of the `CoordinateTuple` with `value`. + /// If `n >=` [`dim()`](Self::dim()) fill the coordinate with `f64::NAN`. + /// See also [`set_nth_unchecked()`](Self::set_nth_unchecked). + fn set_nth(&mut self, n: usize, value: f64) { + if n < self.dim() { + self.set_nth_unchecked(n, value) + } else { + self.fill(f64::NAN); + } + } + + /// Replace the two first elements of the `CoordinateTuple` with `x` and `y`. + /// If the dimension is less than 2, fill the coordinate with `f64::NAN`. + /// See also [`set_nth_unchecked()`](Self::set_nth_unchecked). + fn set_xy(&mut self, x: f64, y: f64) { + if self.dim() > 1 { + self.set_nth_unchecked(0, x); + self.set_nth_unchecked(1, y); + } else { + self.fill(f64::NAN); + } + } + + /// Replace the three first elements of the `CoordinateTuple` with `x`, `y` and `z`. + /// If the dimension is less than 3, fill the coordinate with `f64::NAN`. + fn set_xyz(&mut self, x: f64, y: f64, z: f64) { + if self.dim() > 2 { + self.set_nth_unchecked(0, x); + self.set_nth_unchecked(1, y); + self.set_nth_unchecked(2, z); + } else { + self.fill(f64::NAN); + } + } + + /// Replace the four first elements of the `CoordinateTuple` with `x`, `y` `z` and `t`. + /// If the dimension is less than 4, fill the coordinate with `f64::NAN`. + fn set_xyzt(&mut self, x: f64, y: f64, z: f64, t: f64) { + if self.dim() > 3 { + self.set_nth_unchecked(0, x); + self.set_nth_unchecked(1, y); + self.set_nth_unchecked(2, z); + self.set_nth_unchecked(3, t); + } else { + self.fill(f64::NAN); + } + } + + /// Replace the `N` first (up to [`dim()`](Self::dim())) elements of `self` with the + /// elements of `value` + #[allow(clippy::needless_range_loop)] + fn update(&mut self, value: &[f64]) { + let n = value.len().min(self.dim()); + for i in 0..n { + self.set_nth_unchecked(i, value[i]) + } + } + + /// Euclidean distance between two points in the 2D plane. + /// + /// Primarily used to compute the distance between two projected points + /// in their projected plane. Typically, this distance will differ from + /// the actual distance in the real world. + /// + /// # See also: + /// + /// [`hypot3`](Self::hypot3), + /// [`distance`](crate::ellipsoid::Ellipsoid::distance) + /// + /// # Examples + /// + /// ``` + /// use geodesy::prelude::*; + /// let t = 1000 as f64; + /// let p0 = Coor2D::origin(); + /// let p1 = Coor2D::raw(t, t); + /// assert_eq!(p0.hypot2(&p1), t.hypot(t)); + /// ``` + #[must_use] + fn hypot2(&self, other: &Self) -> f64 + where + Self: Sized, + { + let (u, v) = self.xy(); + let (x, y) = other.xy(); + (u - x).hypot(v - y) + } + + /// Euclidean distance between two points in the 3D space. + /// + /// Primarily used to compute the distance between two points in the + /// 3D cartesian space. The typical case is GNSS-observations, in which + /// case, the distance computed will reflect the actual distance + /// in the real world. + /// + /// The distance is computed in the subspace spanned by the first, + /// second and third coordinate of the `Coor3D`s + /// + /// # See also: + /// + /// [`hypot2()`](Self::hypot2), + /// [`distance`](crate::ellipsoid::Ellipsoid::distance) + /// + /// # Examples + /// + /// ``` + /// use geodesy::prelude::*; + /// let t = 1000 as f64; + /// let p0 = Coor3D::origin(); + /// let p1 = Coor3D::raw(t, t, t); + /// assert_eq!(p0.hypot3(&p1), t.hypot(t).hypot(t)); + /// ``` + #[must_use] + fn hypot3(&self, other: &Self) -> f64 + where + Self: Sized, + { + if self.dim() < 3 { + return f64::NAN; + } + let (u, v, w) = self.xyz(); + let (x, y, z) = other.xyz(); + (u - x).hypot(v - y).hypot(w - z) + } + + fn scale(&self, factor: f64) -> Self + where + Self: Sized + Copy, + { + let mut res = *self; + for i in 0..self.dim() { + res.set_nth(i, self.nth(i) * factor); + } + res + } + + fn dot(&self, other: Self) -> f64 + where + Self: Sized, + { + let mut res = 0.; + for i in 0..self.dim() { + res += self.nth(i) * other.nth(i); + } + res + } +} + +// The CoordiateTuple trait is implemented for the main +// newtypes Coor2D, Coor3D, Coor4D, Coor32 in their files +// below. But for good measure, let's also implement it +// for a plain 2D f64 tuple + +#[rustfmt::skip] +impl CoordinateTuple for (f64, f64) { + fn new(fill: f64) -> Self { + (fill, fill) + } + + fn dim(&self) -> usize { 2 } + + fn nth_unchecked(&self, n: usize) -> f64 { + match n { + 0 => self.0, + 1 => self.1, + _ => panic!() + } + } + + fn set_nth_unchecked(&mut self, n: usize, value: f64) { + match n { + 0 => self.0 = value, + 1 => self.1 = value, + _ => () + } + } +} diff --git a/src/ellipsoid/geodesics.rs b/src/ellipsoid/geodesics.rs index e0b3b1f..83a3ffe 100644 --- a/src/ellipsoid/geodesics.rs +++ b/src/ellipsoid/geodesics.rs @@ -1,4 +1,4 @@ -use crate::coordinate::CoordinateTuple; +use crate::coordinate::tuple::CoordinateTuple; use super::*; diff --git a/src/lib.rs b/src/lib.rs index aebaacc..62742a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,9 +179,9 @@ pub use crate::coordinate::coor3d::Coor3D; pub use crate::coordinate::coor4d::Coor4D; // Coordinate traits pub use crate::coordinate::set::CoordinateSet; +pub use crate::coordinate::tuple::CoordinateTuple; pub use crate::coordinate::AngularUnits; pub use crate::coordinate::CoordinateMetadata; -pub use crate::coordinate::CoordinateTuple; // ---- Et cetera ----