From a6410f8614c2ea53017a940ae60c41caf0f01c23 Mon Sep 17 00:00:00 2001 From: Thomas Knudsen Date: Wed, 10 Apr 2024 16:26:47 +0200 Subject: [PATCH 1/3] CoordinateSet: Better macro orthogonality --- src/coordinate/set.rs | 110 +++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 61 deletions(-) diff --git a/src/coordinate/set.rs b/src/coordinate/set.rs index 284b580..66e40b5 100644 --- a/src/coordinate/set.rs +++ b/src/coordinate/set.rs @@ -2,8 +2,31 @@ use super::*; // Some helper macros, simplifying the macros for the actual data types +// Produce the correct len() method for arrays, slices, and vecs +macro_rules! length { + (array) => { + fn len(&self) -> usize { + N + } + }; + + (slice) => { + fn len(&self) -> usize { + (**self).len() + } + }; + + (vec) => { + fn len(&self) -> usize { + self.len() + } + } +} + macro_rules! coordinate_set_impl_2d_subset { - ($dim:expr) => { + ($dim:expr, $len:ident) => { + length!($len); + fn dim(&self) -> usize { $dim } @@ -19,8 +42,8 @@ macro_rules! coordinate_set_impl_2d_subset { } macro_rules! coordinate_set_impl_3d_subset { - ($dim:expr) => { - coordinate_set_impl_2d_subset!($dim); + ($dim:expr, $len:ident) => { + coordinate_set_impl_2d_subset!($dim, $len); fn xyz(&self, index: usize) -> (f64, f64, f64) { self[index].xyz() @@ -31,6 +54,7 @@ macro_rules! coordinate_set_impl_3d_subset { }; } + // ----- CoordinateSet implementations for some Coor2D containers ------------ /// By default, the CoordinateSet implementations for Coor2D return `0` and `f64::NAN` @@ -52,8 +76,8 @@ macro_rules! coordinate_set_impl_3d_subset { /// coordinate dimension. macro_rules! coordinate_set_impl_for_coor2d { - () => { - coordinate_set_impl_2d_subset!(2); + ($kind:ident) => { + coordinate_set_impl_2d_subset!(2, $kind); fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0], self[index][1], 0., f64::NAN]) @@ -66,31 +90,22 @@ macro_rules! coordinate_set_impl_for_coor2d { } impl CoordinateSet for [Coor2D; N] { - fn len(&self) -> usize { - N - } - coordinate_set_impl_for_coor2d!(); + coordinate_set_impl_for_coor2d!(array); } impl CoordinateSet for &mut [Coor2D] { - fn len(&self) -> usize { - (**self).len() - } - coordinate_set_impl_for_coor2d!(); + coordinate_set_impl_for_coor2d!(slice); } impl CoordinateSet for Vec { - fn len(&self) -> usize { - self.len() - } - coordinate_set_impl_for_coor2d!(); + coordinate_set_impl_for_coor2d!(vec); } // ----- CoordinateSet implementations for some Coor32 containers ------------ macro_rules! coordinate_set_impl_for_coor32 { - () => { - coordinate_set_impl_2d_subset!(2); + ($kind:ident) => { + coordinate_set_impl_2d_subset!(2, $kind); fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0] as f64, self[index][1] as f64, 0., f64::NAN]) @@ -103,31 +118,22 @@ macro_rules! coordinate_set_impl_for_coor32 { } impl CoordinateSet for [Coor32; N] { - fn len(&self) -> usize { - N - } - coordinate_set_impl_for_coor32!(); + coordinate_set_impl_for_coor32!(array); } impl CoordinateSet for &mut [Coor32] { - fn len(&self) -> usize { - (**self).len() - } - coordinate_set_impl_for_coor32!(); + coordinate_set_impl_for_coor32!(slice); } impl CoordinateSet for Vec { - fn len(&self) -> usize { - self.len() - } - coordinate_set_impl_for_coor32!(); + coordinate_set_impl_for_coor32!(vec); } // ----- CoordinateSet implementations for some Coor3D containers ------------ macro_rules! coordinate_set_impl_for_coor3d { - () => { - coordinate_set_impl_3d_subset!(3); + ($kind:ident) => { + coordinate_set_impl_3d_subset!(3, $kind); fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0], self[index][1], self[index][2], f64::NAN]) @@ -140,31 +146,22 @@ macro_rules! coordinate_set_impl_for_coor3d { } impl CoordinateSet for [Coor3D; N] { - fn len(&self) -> usize { - N - } - coordinate_set_impl_for_coor3d!(); + coordinate_set_impl_for_coor3d!(array); } impl CoordinateSet for &mut [Coor3D] { - fn len(&self) -> usize { - (**self).len() - } - coordinate_set_impl_for_coor3d!(); + coordinate_set_impl_for_coor3d!(slice); } impl CoordinateSet for Vec { - fn len(&self) -> usize { - self.len() - } - coordinate_set_impl_for_coor3d!(); + coordinate_set_impl_for_coor3d!(vec); } // ----- CoordinateSet implementations for some Coor4D containers ------------ macro_rules! coordinate_set_impl_for_coor4d { - () => { - coordinate_set_impl_3d_subset!(4); + ($kind:ident) => { + coordinate_set_impl_3d_subset!(4, $kind); fn get_coord(&self, index: usize) -> Coor4D { self[index] @@ -184,25 +181,16 @@ macro_rules! coordinate_set_impl_for_coor4d { }; } -impl CoordinateSet for &mut [Coor4D] { - fn len(&self) -> usize { - (**self).len() - } - coordinate_set_impl_for_coor4d!(); +impl CoordinateSet for [Coor4D; N] { + coordinate_set_impl_for_coor4d!(array); } -impl CoordinateSet for [Coor4D; N] { - fn len(&self) -> usize { - N - } - coordinate_set_impl_for_coor4d!(); +impl CoordinateSet for &mut [Coor4D] { + coordinate_set_impl_for_coor4d!(slice); } impl CoordinateSet for Vec { - fn len(&self) -> usize { - self.len() - } - coordinate_set_impl_for_coor4d!(); + coordinate_set_impl_for_coor4d!(vec); } /// User defined values for third and fourth coordinate dimension. From a111d530d81308470ef6cdffb342a109fb8b2fa6 Mon Sep 17 00:00:00 2001 From: Thomas Knudsen Date: Wed, 10 Apr 2024 22:29:47 +0200 Subject: [PATCH 2/3] Streamline the coordinate tree - CoordinateSet trait moved to set.rs - Improved doc comments in coorXX.rs - Centralize (by macro) the implementation of indexing for CoorXX - Cleanup --- CHANGELOG.md | 3 + src/coordinate/coor2d.rs | 42 +++---- src/coordinate/coor32.rs | 24 ++-- src/coordinate/coor3d.rs | 40 +++--- src/coordinate/coor4d.rs | 26 ++-- src/coordinate/mod.rs | 264 ++++++++++++++------------------------- src/coordinate/set.rs | 91 +++++++++++++- src/lib.rs | 2 +- 8 files changed, 257 insertions(+), 235 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e46ab..2bcd1ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- `CoordinateTuple` trait now requires implementation of the constructor + method `new(fill: f64)`, returning an object of `dim()` copies of `fill`. + ### Removed - CHANGES.md diff --git a/src/coordinate/coor2d.rs b/src/coordinate/coor2d.rs index a63ce68..28256b9 100644 --- a/src/coordinate/coor2d.rs +++ b/src/coordinate/coor2d.rs @@ -5,18 +5,21 @@ use crate::math::angular; #[derive(Debug, Default, PartialEq, Copy, Clone)] pub struct Coor2D(pub [f64; 2]); -use std::ops::{Index, IndexMut}; +impl CoordinateTuple for Coor2D { + fn new(fill: f64) -> Self { + Coor2D([fill; 2]) + } -impl Index for Coor2D { - type Output = f64; - fn index(&self, i: usize) -> &Self::Output { - &self.0[i] + fn dim(&self) -> usize { + 2 + } + + fn nth_unchecked(&self, n: usize) -> f64 { + self.0[n] } -} -impl IndexMut for Coor2D { - fn index_mut(&mut self, i: usize) -> &mut Self::Output { - &mut self.0[i] + fn set_nth_unchecked(&mut self, n: usize, value: f64) { + self.0[n] = value; } } @@ -24,15 +27,14 @@ impl IndexMut for Coor2D { /// Constructors impl Coor2D { - /// A `Coor2D` from latitude/longitude/height/time, with the angular input in degrees, - /// and height and time ignored. + /// A `Coor2D` from latitude/longitude in degrees #[must_use] pub fn geo(latitude: f64, longitude: f64) -> Coor2D { Coor2D([longitude.to_radians(), latitude.to_radians()]) } - /// A `Coor2D` from longitude/latitude/height/time, with the angular input in seconds - /// of arc. Mostly for handling grid shift elements. + /// A `Coor2D` from longitude/latitude in seconds of arc. + /// Mostly for handling grid shift elements. #[must_use] pub fn arcsec(longitude: f64, latitude: f64) -> Coor2D { Coor2D([ @@ -41,23 +43,19 @@ impl Coor2D { ]) } - /// A `Coor2D` from longitude/latitude/height/time, with the angular input in degrees. - /// and height and time ignored. + /// A `Coor2D` from longitude/latitude in degrees. #[must_use] pub fn gis(longitude: f64, latitude: f64) -> Coor2D { Coor2D([longitude.to_radians(), latitude.to_radians()]) } - /// A `Coor2D` from longitude/latitude/height/time, with the angular input in radians, - /// and third and fourth arguments ignored. + /// A `Coor2D` from e.g. longitude/latitude in radians, #[must_use] pub fn raw(first: f64, second: f64) -> Coor2D { Coor2D([first, second]) } - /// A `Coor2D` from latitude/longitude/height/time, with - /// the angular input in the ISO-6709 DDDMM.mmmmm format, - /// and height and time ignored. + /// A `Coor2D` from latitude/longitude in the ISO-6709 DDDMM.mmmmm format. #[must_use] pub fn iso_dm(latitude: f64, longitude: f64) -> Coor2D { let longitude = angular::iso_dm_to_dd(longitude); @@ -65,9 +63,7 @@ impl Coor2D { Coor2D([longitude.to_radians(), latitude.to_radians()]) } - /// A `Coor2D` from latitude/longitude/height/time, with the - /// angular input in the ISO-6709 DDDMMSS.sssss format, - /// and height and time ignored. + /// A `Coor2D` from latitude/longitude in the ISO-6709 DDDMMSS.sssss format. #[must_use] pub fn iso_dms(latitude: f64, longitude: f64) -> Coor2D { let longitude = angular::iso_dms_to_dd(longitude); diff --git a/src/coordinate/coor32.rs b/src/coordinate/coor32.rs index 749c5c2..c52899a 100644 --- a/src/coordinate/coor32.rs +++ b/src/coordinate/coor32.rs @@ -1,5 +1,4 @@ -use crate::math::angular; -use std::ops::{Index, IndexMut}; +use super::*; /// Generic 2D Coordinate tuple, with no fixed interpretation of the elements. /// A tiny coordinate type: Just one fourth the weight of a [`Coor4D`](crate::Coor4D). @@ -7,18 +6,21 @@ use std::ops::{Index, IndexMut}; #[derive(Debug, Default, PartialEq, Copy, Clone)] pub struct Coor32(pub [f32; 2]); -// ----- O P E R A T O R T R A I T S ------------------------------------------------- +impl CoordinateTuple for Coor32 { + fn new(fill: f64) -> Self { + Coor32([fill as f32; 2]) + } -impl Index for Coor32 { - type Output = f32; - fn index(&self, i: usize) -> &Self::Output { - &self.0[i] + fn dim(&self) -> usize { + 2 + } + + fn nth_unchecked(&self, n: usize) -> f64 { + self.0[n] as f64 } -} -impl IndexMut for Coor32 { - fn index_mut(&mut self, i: usize) -> &mut Self::Output { - &mut self.0[i] + fn set_nth_unchecked(&mut self, n: usize, value: f64) { + self.0[n] = value as f32; } } diff --git a/src/coordinate/coor3d.rs b/src/coordinate/coor3d.rs index 4084357..fdb411d 100644 --- a/src/coordinate/coor3d.rs +++ b/src/coordinate/coor3d.rs @@ -1,25 +1,30 @@ -use crate::math::angular; -use std::ops::{Add, Div, Index, IndexMut, Mul, Sub}; +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)] pub struct Coor3D(pub [f64; 3]); -// ----- O P E R A T O R T R A I T S ------------------------------------------------- +impl CoordinateTuple for Coor3D { + fn new(fill: f64) -> Self { + Coor3D([fill; 3]) + } -impl Index for Coor3D { - type Output = f64; - fn index(&self, i: usize) -> &Self::Output { - &self.0[i] + fn dim(&self) -> usize { + 3 } -} -impl IndexMut for Coor3D { - fn index_mut(&mut self, i: usize) -> &mut Self::Output { - &mut self.0[i] + fn nth_unchecked(&self, n: usize) -> f64 { + self.0[n] + } + + fn set_nth_unchecked(&mut self, n: usize, value: f64) { + self.0[n] = value; } } +// ----- 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 { @@ -79,13 +84,13 @@ impl Div for Coor3D { /// Constructors impl Coor3D { - /// A `Coor3D` from latitude/longitude/height/time, with the angular input in degrees + /// A `Coor3D` from latitude/longitude/height, with the angular input in degrees #[must_use] pub fn geo(latitude: f64, longitude: f64, height: f64) -> Coor3D { Coor3D([longitude.to_radians(), latitude.to_radians(), height]) } - /// A `Coor3D` from longitude/latitude/height/time, with the angular input in seconds + /// A `Coor3D` from longitude/latitude/height, with the angular input in seconds /// of arc. Mostly for handling grid shift elements. #[must_use] pub fn arcsec(longitude: f64, latitude: f64, height: f64) -> Coor3D { @@ -96,7 +101,7 @@ impl Coor3D { ]) } - /// A `Coor3D` from longitude/latitude/height/time, with the angular input in degrees + /// A `Coor3D` from longitude/latitude/height, with the angular input in degrees #[must_use] pub fn gis(longitude: f64, latitude: f64, height: f64) -> Coor3D { Coor3D([longitude.to_radians(), latitude.to_radians(), height]) @@ -108,7 +113,7 @@ impl Coor3D { Coor3D([first, second, third]) } - /// A `Coor3D` from latitude/longitude/height/time, + /// A `Coor3D` from latitude/longitude/height, /// with the angular input in the ISO-6709 DDDMM.mmmmm format #[must_use] pub fn iso_dm(latitude: f64, longitude: f64, height: f64) -> Coor3D { @@ -117,7 +122,7 @@ impl Coor3D { Coor3D([longitude.to_radians(), latitude.to_radians(), height]) } - /// A `Coor3D` from latitude/longitude/height/time, with + /// A `Coor3D` from latitude/longitude/height, with /// the angular input in the ISO-6709 DDDMMSS.sssss format #[must_use] pub fn iso_dms(latitude: f64, longitude: f64, height: f64) -> Coor3D { @@ -129,7 +134,7 @@ impl Coor3D { /// A `Coor3D` consisting of 3 `NaN`s #[must_use] pub fn nan() -> Coor3D { - Coor3D([f64::NAN, f64::NAN, f64::NAN]) + Coor3D::new(f64::NAN) //([f64::NAN, f64::NAN, f64::NAN]) } /// A `Coor3D` consisting of 3 `0`s @@ -172,7 +177,6 @@ impl Coor3D { #[cfg(test)] mod tests { use super::*; - use crate::prelude::*; #[test] fn distances() { diff --git a/src/coordinate/coor4d.rs b/src/coordinate/coor4d.rs index 94a1154..4c2231d 100644 --- a/src/coordinate/coor4d.rs +++ b/src/coordinate/coor4d.rs @@ -1,27 +1,30 @@ -use crate::math::angular; +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)] pub struct Coor4D(pub [f64; 4]); -// ----- O P E R A T O R T R A I T S ------------------------------------------------- +impl CoordinateTuple for Coor4D { + fn new(fill: f64) -> Self { + Coor4D([fill; 4]) + } -use std::ops::{Index, IndexMut}; + fn dim(&self) -> usize { + 4 + } -impl Index for Coor4D { - type Output = f64; - fn index(&self, i: usize) -> &Self::Output { - &self.0[i] + fn nth_unchecked(&self, n: usize) -> f64 { + self.0[n] } -} -impl IndexMut for Coor4D { - fn index_mut(&mut self, i: usize) -> &mut Self::Output { - &mut self.0[i] + fn set_nth_unchecked(&mut self, n: usize, value: f64) { + self.0[n] = value; } } +// ----- 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 { @@ -158,7 +161,6 @@ impl Coor4D { #[cfg(test)] mod tests { use super::*; - use crate::prelude::*; #[test] fn distances() { diff --git a/src/coordinate/mod.rs b/src/coordinate/mod.rs index 925df25..5b9960a 100644 --- a/src/coordinate/mod.rs +++ b/src/coordinate/mod.rs @@ -5,6 +5,31 @@ 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 /// dimensions only. @@ -31,7 +56,7 @@ where fn to_degrees(&self) -> Self { let (x, y) = self.xy(); let mut res = *self; - res.update(&[x.to_degrees(), y.to_degrees()]); + res.set_xy(x.to_degrees(), y.to_degrees()); res } @@ -39,7 +64,7 @@ where fn to_arcsec(&self) -> Self { let (x, y) = self.xy(); let mut res = *self; - res.update(&[x.to_degrees() * 3600., y.to_degrees() * 3600.]); + res.set_xy(x.to_degrees() * 3600., y.to_degrees() * 3600.); res } @@ -47,7 +72,7 @@ where fn to_radians(&self) -> Self { let (x, y) = self.xy(); let mut res = *self; - res.update(&[x.to_radians(), y.to_radians()]); + res.set_xy(x.to_radians(), y.to_radians()); res } @@ -55,7 +80,7 @@ where fn to_geo(&self) -> Self { let (x, y) = self.xy(); let mut res = *self; - res.update(&[y.to_degrees(), x.to_degrees()]); + res.set_xy(y.to_degrees(), x.to_degrees()); res } } @@ -118,94 +143,6 @@ pub trait CoordinateMetadata { // Preliminary empty blanket implementation: Defaults for all items, for all types impl CoordinateMetadata for T where T: ?Sized {} -/// CoordinateSet is the fundamental coordinate access interface in ISO-19111. -/// Strictly speaking, it is not a set, but (in abstract terms) rather an -/// indexed list, or (in more concrete terms): An array. -/// -/// Here it is implemented simply as an accessor trait, that allows us to -/// access any user provided data model by iterating over its elements, -/// represented as a `Coor4D` -pub trait CoordinateSet: CoordinateMetadata { - /// Number of coordinate tuples in the set - fn len(&self) -> usize; - - /// Native dimension of the underlying coordinates (they will always be - /// returned by [`Self::get_coord()`] as converted to [`Coor4D`](super::Coor4D)) - fn dim(&self) -> usize; - - /// Access the `index`th coordinate tuple - fn get_coord(&self, index: usize) -> Coor4D; - - /// Overwrite the `index`th coordinate tuple - fn set_coord(&mut self, index: usize, value: &Coor4D); - - /// Companion to `len()` - fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Replace the two first elements of the `index`th `CoordinateTuple` - /// with `x` and `y`. - /// Consider providing a type specific version, when implementing - /// the CoordinateSet trait for a concrete data type: The default - /// version is straightforward, but not necessarily efficient - fn set_xy(&mut self, index: usize, x: f64, y: f64) { - let mut coord = self.get_coord(index); - coord[0] = x; - coord[1] = y; - self.set_coord(index, &coord); - } - - /// Access the two first elements of the `index`th `CoordinateTuple`. - /// Consider providing a type specific version, when implementing - /// the CoordinateSet trait for a concrete data type: The default - /// version is straightforward, but not necessarily efficient - fn xy(&self, index: usize) -> (f64, f64) { - self.get_coord(index).xy() - } - - /// Replace the three first elements of the `index`th `CoordinateTuple` - /// with `x`, `y` and `z`. - /// Consider providing a type specific version, when implementing - /// the CoordinateSet trait for a concrete data type: The default - /// version is straightforward, but not necessarily efficient - fn set_xyz(&mut self, index: usize, x: f64, y: f64, z: f64) { - let mut coord = self.get_coord(index); - coord[0] = x; - coord[1] = y; - coord[2] = z; - self.set_coord(index, &coord); - } - - /// Access the three first elements of the `index`th `CoordinateTuple`. - /// Consider providing a type specific version, when implementing - /// the CoordinateSet trait for a concrete data type: The default - /// version is straightforward, but not necessarily efficient - fn xyz(&self, index: usize) -> (f64, f64, f64) { - self.get_coord(index).xyz() - } - - /// Replace the four elements of the `index`th `CoordinateTuple` - /// with `x`, `y`, `z` and `t`. Syntactic sugar for [`Self::set_coord`] - fn set_xyzt(&mut self, index: usize, x: f64, y: f64, z: f64, t: f64) { - self.set_coord(index, &Coor4D([x, y, z, t])); - } - - /// Access the four elements of the `index`th `CoordinateTuple`. - /// Syntactic sugar for [`Self::get_coord`] - fn xyzt(&self, index: usize) -> (f64, f64, f64, f64) { - self.get_coord(index).xyzt() - } - - /// Set all coordinate tuples in the set to NaN - fn stomp(&mut self) { - let nanny = Coor4D::nan(); - for i in 0..self.len() { - self.set_coord(i, &nanny); - } - } -} - /// CoordinateTuple is the ISO-19111 atomic spatial/spatiotemporal /// referencing element. So loosely speaking, a CoordinateSet is a /// collection of CoordinateTuples. @@ -229,8 +166,10 @@ pub trait CoordinateSet: CoordinateMetadata { /// /// When accessing dimensions outside of the domain of the CoordinateTuple, /// [NaN](f64::NAN) will be returned. -#[rustfmt::skip] 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). @@ -248,7 +187,11 @@ pub trait 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} + if n < self.dim() { + self.nth_unchecked(n) + } else { + f64::NAN + } } // Note: We use nth_unchecked and explicitly check for dimension in @@ -263,17 +206,29 @@ pub trait CoordinateTuple { /// 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} + 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} + 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} + if self.dim() > 3 { + self.nth_unchecked(3) + } else { + f64::NAN + } } /// A tuple containing the first two components of the CoordinateTuple. @@ -306,25 +261,39 @@ pub trait CoordinateTuple { /// 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()) + ( + 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.) + (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()) + ( + 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()) + ( + self.x().to_degrees() * 3600., + self.y().to_degrees() * 3600., + self.z(), + self.t(), + ) } /// A tuple containing the first two components of the CoordinateTuple, @@ -342,7 +311,12 @@ pub trait CoordinateTuple { /// 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()) + ( + self.x().to_radians(), + self.y().to_radians(), + self.z(), + self.t(), + ) } /// Fill all elements of `self` with `value` @@ -432,7 +406,9 @@ pub trait CoordinateTuple { /// ``` #[must_use] fn hypot2(&self, other: &Self) -> f64 - where Self: Sized { + where + Self: Sized, + { let (u, v) = self.xy(); let (x, y) = other.xy(); (u - x).hypot(v - y) @@ -464,7 +440,9 @@ pub trait CoordinateTuple { /// ``` #[must_use] fn hypot3(&self, other: &Self) -> f64 - where Self: Sized { + where + Self: Sized, + { if self.dim() < 3 { return f64::NAN; } @@ -473,9 +451,10 @@ pub trait CoordinateTuple { (u - x).hypot(v - y).hypot(w - z) } - fn scale(&self, factor: f64) -> Self - where Self: Sized+Copy { + where + Self: Sized + Copy, + { let mut res = *self; for i in 0..self.dim() { res.set_nth(i, self.nth(i) * factor); @@ -484,79 +463,28 @@ pub trait CoordinateTuple { } fn dot(&self, other: Self) -> f64 - where Self: Sized { + where + Self: Sized, + { let mut res = 0.; for i in 0..self.dim() { res += self.nth(i) * other.nth(i); } res } - -} - -// CoordinateTuple implementations for the Geodesy data types, -// Coor2D, Coor32, Coor3D, Coor4D - -impl CoordinateTuple for Coor2D { - fn dim(&self) -> usize { - 2 - } - - fn nth_unchecked(&self, n: usize) -> f64 { - self.0[n] - } - - fn set_nth_unchecked(&mut self, n: usize, value: f64) { - self.0[n] = value; - } -} - -impl CoordinateTuple for Coor3D { - fn dim(&self) -> usize { - 3 - } - - fn nth_unchecked(&self, n: usize) -> f64 { - self.0[n] - } - - fn set_nth_unchecked(&mut self, n: usize, value: f64) { - self.0[n] = value; - } } -impl CoordinateTuple for Coor4D { - fn dim(&self) -> usize { - 4 - } - - fn nth_unchecked(&self, n: usize) -> f64 { - self.0[n] - } - - fn set_nth_unchecked(&mut self, n: usize, value: f64) { - self.0[n] = value; - } -} - -impl CoordinateTuple for Coor32 { - fn dim(&self) -> usize { - 2 - } - - fn nth_unchecked(&self, n: usize) -> f64 { - self.0[n] as f64 - } - - fn set_nth_unchecked(&mut self, n: usize, value: f64) { - self.0[n] = value as f32; - } -} - -// And let's also implement it for a plain 2D f64 tuple +// 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 { diff --git a/src/coordinate/set.rs b/src/coordinate/set.rs index 66e40b5..8e302fa 100644 --- a/src/coordinate/set.rs +++ b/src/coordinate/set.rs @@ -1,3 +1,91 @@ +/// CoordinateSet is the fundamental coordinate access interface in ISO-19111. +/// Strictly speaking, it is not a set, but (in abstract terms) rather an +/// indexed list, or (in more concrete terms): An array. +/// +/// Here it is implemented simply as an accessor trait, that allows us to +/// access any user provided data model by iterating over its elements, +/// represented as a `Coor4D` +pub trait CoordinateSet: CoordinateMetadata { + /// Number of coordinate tuples in the set + fn len(&self) -> usize; + + /// Native dimension of the underlying coordinates (they will always be + /// returned by [`Self::get_coord()`] as converted to [`Coor4D`](super::Coor4D)) + fn dim(&self) -> usize; + + /// Access the `index`th coordinate tuple + fn get_coord(&self, index: usize) -> Coor4D; + + /// Overwrite the `index`th coordinate tuple + fn set_coord(&mut self, index: usize, value: &Coor4D); + + /// Companion to `len()` + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Replace the two first elements of the `index`th `CoordinateTuple` + /// with `x` and `y`. + /// Consider providing a type specific version, when implementing + /// the CoordinateSet trait for a concrete data type: The default + /// version is straightforward, but not necessarily efficient + fn set_xy(&mut self, index: usize, x: f64, y: f64) { + let mut coord = self.get_coord(index); + coord[0] = x; + coord[1] = y; + self.set_coord(index, &coord); + } + + /// Access the two first elements of the `index`th `CoordinateTuple`. + /// Consider providing a type specific version, when implementing + /// the CoordinateSet trait for a concrete data type: The default + /// version is straightforward, but not necessarily efficient + fn xy(&self, index: usize) -> (f64, f64) { + self.get_coord(index).xy() + } + + /// Replace the three first elements of the `index`th `CoordinateTuple` + /// with `x`, `y` and `z`. + /// Consider providing a type specific version, when implementing + /// the CoordinateSet trait for a concrete data type: The default + /// version is straightforward, but not necessarily efficient + fn set_xyz(&mut self, index: usize, x: f64, y: f64, z: f64) { + let mut coord = self.get_coord(index); + coord[0] = x; + coord[1] = y; + coord[2] = z; + self.set_coord(index, &coord); + } + + /// Access the three first elements of the `index`th `CoordinateTuple`. + /// Consider providing a type specific version, when implementing + /// the CoordinateSet trait for a concrete data type: The default + /// version is straightforward, but not necessarily efficient + fn xyz(&self, index: usize) -> (f64, f64, f64) { + self.get_coord(index).xyz() + } + + /// Replace the four elements of the `index`th `CoordinateTuple` + /// with `x`, `y`, `z` and `t`. Syntactic sugar for [`Self::set_coord`] + fn set_xyzt(&mut self, index: usize, x: f64, y: f64, z: f64, t: f64) { + self.set_coord(index, &Coor4D([x, y, z, t])); + } + + /// Access the four elements of the `index`th `CoordinateTuple`. + /// Syntactic sugar for [`Self::get_coord`] + fn xyzt(&self, index: usize) -> (f64, f64, f64, f64) { + self.get_coord(index).xyzt() + } + + /// Set all coordinate tuples in the set to NaN + fn stomp(&mut self) { + let nanny = Coor4D::nan(); + for i in 0..self.len() { + self.set_coord(i, &nanny); + } + } +} + use super::*; // Some helper macros, simplifying the macros for the actual data types @@ -20,7 +108,7 @@ macro_rules! length { fn len(&self) -> usize { self.len() } - } + }; } macro_rules! coordinate_set_impl_2d_subset { @@ -54,7 +142,6 @@ macro_rules! coordinate_set_impl_3d_subset { }; } - // ----- CoordinateSet implementations for some Coor2D containers ------------ /// By default, the CoordinateSet implementations for Coor2D return `0` and `f64::NAN` diff --git a/src/lib.rs b/src/lib.rs index 31924a5..aebaacc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,9 +178,9 @@ pub use crate::coordinate::coor32::Coor32; pub use crate::coordinate::coor3d::Coor3D; pub use crate::coordinate::coor4d::Coor4D; // Coordinate traits +pub use crate::coordinate::set::CoordinateSet; pub use crate::coordinate::AngularUnits; pub use crate::coordinate::CoordinateMetadata; -pub use crate::coordinate::CoordinateSet; pub use crate::coordinate::CoordinateTuple; // ---- Et cetera ---- From 5095449ab19e3e5253eaa919c69b463274620df9 Mon Sep 17 00:00:00 2001 From: Thomas Knudsen Date: Thu, 11 Apr 2024 14:54:09 +0200 Subject: [PATCH 3/3] Add/Sub/Mul/Div for all builtin CoordinateTuple types --- CHANGELOG.md | 2 + src/coordinate/coor3d.rs | 59 +---- src/coordinate/coor4d.rs | 64 +---- src/coordinate/mod.rs | 390 +---------------------------- src/coordinate/tuple.rs | 497 +++++++++++++++++++++++++++++++++++++ src/ellipsoid/geodesics.rs | 2 +- src/lib.rs | 2 +- 7 files changed, 506 insertions(+), 510 deletions(-) create mode 100644 src/coordinate/tuple.rs 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 ----