From 7401ca7dbe0a70af9c3e84457cab9765fd25195a Mon Sep 17 00:00:00 2001 From: Thomas Knudsen Date: Thu, 4 Apr 2024 23:11:32 +0200 Subject: [PATCH] Extend and use CoordinateTuple trait. dim in CoordinateSet --- examples/00-transformations.rs | 7 +- examples/02-user_defined_macros.rs | 3 +- ...defined_coordinate_types_and_containers.rs | 3 + examples/07-examples_from_ruminations.rs | 3 +- src/context/minimal.rs | 16 +- src/context/plain.rs | 24 +- src/coordinate/coor2d.rs | 84 +---- src/coordinate/coor32.rs | 72 +---- src/coordinate/coor3d.rs | 98 +----- src/coordinate/coor4d.rs | 139 +-------- src/coordinate/mod.rs | 286 ++++++++++++++---- src/coordinate/set.rs | 133 ++++---- src/ellipsoid/geodesics.rs | 13 +- src/grid/mod.rs | 1 + src/inner_op/gridshift.rs | 1 + src/op/mod.rs | 8 +- 16 files changed, 350 insertions(+), 541 deletions(-) diff --git a/examples/00-transformations.rs b/examples/00-transformations.rs index 69ebfbe7..e61a83cb 100644 --- a/examples/00-transformations.rs +++ b/examples/00-transformations.rs @@ -37,8 +37,7 @@ fn main() -> anyhow::Result<()> { // But since a coordinate tuple is really just an array of double // precision numbers, you may also generate it directly using plain // Rust syntax. Note that Coor2D, like f64, provides the to_radians - // method. So compared to cph_raw above, we can use a slightly more - // compact notation. + // method, but it operates in place, so we need two steps in this case. let cph_direct = Coor2D([12., 55.]).to_radians(); // The three versions of Copenhagen coordinates should be identical. assert_eq!(cph, cph_raw); @@ -79,8 +78,8 @@ fn main() -> anyhow::Result<()> { // Note the use of `to_geo`, which transforms lon/lat in radians // to lat/lon in degrees. It is defined for Coor2D as well as for // arrays, vectors and slices of Coor2D - for coord in data.to_geo() { - println!(" {:?}", coord); + for coord in data { + println!(" {:?}", coord.to_geo()); } // To geo again, but using slices - in two different ways diff --git a/examples/02-user_defined_macros.rs b/examples/02-user_defined_macros.rs index f09c1f2a..02b03237 100644 --- a/examples/02-user_defined_macros.rs +++ b/examples/02-user_defined_macros.rs @@ -46,10 +46,9 @@ fn main() -> anyhow::Result<()> { // data.to_geo() transforms all elements in data from the internal GIS // format (lon/lat in radians) to lat/lon in degrees. - data.to_geo(); println!("Back to ed50:"); for coord in data { - println!(" {:?}", coord); + println!(" {:?}", coord.to_geo()); } Ok(()) diff --git a/examples/06-user_defined_coordinate_types_and_containers.rs b/examples/06-user_defined_coordinate_types_and_containers.rs index 2babc604..8c02160a 100644 --- a/examples/06-user_defined_coordinate_types_and_containers.rs +++ b/examples/06-user_defined_coordinate_types_and_containers.rs @@ -75,6 +75,9 @@ impl CoordinateSet for AbscissaCollection { fn len(&self) -> usize { 4 } + fn dim(&self) -> usize { + 1 + } } fn main() -> Result<(), anyhow::Error> { diff --git a/examples/07-examples_from_ruminations.rs b/examples/07-examples_from_ruminations.rs index 4bac6b35..9af8c2d6 100644 --- a/examples/07-examples_from_ruminations.rs +++ b/examples/07-examples_from_ruminations.rs @@ -61,9 +61,8 @@ fn rumination_000() -> Result<(), anyhow::Error> { // [6] And go back, i.e. utm -> geo ctx.apply(utm32, Inv, &mut data)?; - data.to_geo(); for coord in data { - println!("{:?}", coord); + println!("{:?}", coord.to_geo()); } Ok(()) diff --git a/src/context/minimal.rs b/src/context/minimal.rs index 2f37a238..0ad8f615 100644 --- a/src/context/minimal.rs +++ b/src/context/minimal.rs @@ -143,16 +143,16 @@ mod tests { assert_eq!(steps[2], "addone inv"); let mut data = some_basic_coor2dinates(); - assert_eq!(data[0][0], 55.); - assert_eq!(data[1][0], 59.); + assert_eq!(data[0].x(), 55.); + assert_eq!(data[1].x(), 59.); assert_eq!(2, ctx.apply(op, Fwd, &mut data)?); - assert_eq!(data[0][0], 56.); - assert_eq!(data[1][0], 60.); + assert_eq!(data[0].x(), 56.); + assert_eq!(data[1].x(), 60.); ctx.apply(op, Inv, &mut data)?; - assert_eq!(data[0][0], 55.); - assert_eq!(data[1][0], 59.); + assert_eq!(data[0].x(), 55.); + assert_eq!(data[1].x(), 59.); let params = ctx.params(op, 1)?; let ellps = params.ellps(0); @@ -168,8 +168,8 @@ mod tests { let op = ctx.op("geo:in | utm zone=32 | neu:out")?; let mut data = some_basic_coor2dinates(); - assert_eq!(data[0][0], 55.); - assert_eq!(data[1][0], 59.); + assert_eq!(data[0].x(), 55.); + assert_eq!(data[1].x(), 59.); ctx.apply(op, Fwd, &mut data)?; let expected = [6098907.825005002, 691875.6321396609]; diff --git a/src/context/plain.rs b/src/context/plain.rs index e9e02a26..291183f3 100644 --- a/src/context/plain.rs +++ b/src/context/plain.rs @@ -312,16 +312,16 @@ mod tests { // ...and it works as expected? let mut data = some_basic_coor2dinates(); - assert_eq!(data[0][0], 55.); - assert_eq!(data[1][0], 59.); + assert_eq!(data[0].x(), 55.); + assert_eq!(data[1].x(), 59.); ctx.apply(op, Fwd, &mut data)?; - assert_eq!(data[0][0], 56.); - assert_eq!(data[1][0], 60.); + assert_eq!(data[0].x(), 56.); + assert_eq!(data[1].x(), 60.); ctx.apply(op, Inv, &mut data)?; - assert_eq!(data[0][0], 55.); - assert_eq!(data[1][0], 59.); + assert_eq!(data[0].x(), 55.); + assert_eq!(data[1].x(), 59.); // Now test that the look-up functionality works in general @@ -344,8 +344,8 @@ mod tests { let mut data = some_basic_coor2dinates(); ctx.apply(op, Fwd, &mut data)?; - assert_eq!(data[0][0], 57.); - assert_eq!(data[1][0], 61.); + assert_eq!(data[0].x(), 57.); + assert_eq!(data[1].x(), 61.); // 3 Console tests from stupid.md let op = ctx.op("stupid:bad"); @@ -354,14 +354,14 @@ mod tests { let op = ctx.op("stupid:addthree")?; let mut data = some_basic_coor2dinates(); ctx.apply(op, Fwd, &mut data)?; - assert_eq!(data[0][0], 58.); - assert_eq!(data[1][0], 62.); + assert_eq!(data[0].x(), 58.); + assert_eq!(data[1].x(), 62.); let op = ctx.op("stupid:addthree_one_by_one")?; let mut data = some_basic_coor2dinates(); ctx.apply(op, Fwd, &mut data)?; - assert_eq!(data[0][0], 58.); - assert_eq!(data[1][0], 62.); + assert_eq!(data[0].x(), 58.); + assert_eq!(data[1].x(), 62.); // Make sure we can access "sigil-less runtime defined resources" ctx.register_resource("foo", "bar"); diff --git a/src/coordinate/coor2d.rs b/src/coordinate/coor2d.rs index 255b5039..a63ce688 100644 --- a/src/coordinate/coor2d.rs +++ b/src/coordinate/coor2d.rs @@ -1,12 +1,11 @@ use super::*; use crate::math::angular; -use std::ops::{Index, IndexMut}; /// Generic 2D Coordinate tuple, with no fixed interpretation of the elements #[derive(Debug, Default, PartialEq, Copy, Clone)] pub struct Coor2D(pub [f64; 2]); -// ----- O P E R A T O R T R A I T S ------------------------------------------------- +use std::ops::{Index, IndexMut}; impl Index for Coor2D { type Output = f64; @@ -21,34 +20,6 @@ impl IndexMut for Coor2D { } } -// ----- A N G U L A R U N I T S ------------------------------------------- - -impl AngularUnits for Coor2D { - /// Transform the elements of a `Coor2D` from degrees to radians - #[must_use] - fn to_radians(self) -> Self { - Coor2D([self[0].to_radians(), self[1].to_radians()]) - } - - /// Transform the elements of a `Coor2D` from radians to degrees - #[must_use] - fn to_degrees(self) -> Self { - Coor2D([self[0].to_degrees(), self[1].to_degrees()]) - } - - /// Transform the elements of a `Coor2D` from radians to seconds of arc. - #[must_use] - fn to_arcsec(self) -> Self { - Coor2D([self[0].to_degrees() * 3600., self[1].to_degrees() * 3600.]) - } - - /// Transform the internal lon/lat-in-radians to lat/lon-in-degrees - #[must_use] - fn to_geo(self) -> Self { - Coor2D([self[1].to_degrees(), self[0].to_degrees()]) - } -} - // ----- C O N S T R U C T O R S --------------------------------------------- /// Constructors @@ -127,53 +98,13 @@ impl Coor2D { /// Multiply by a scalar #[must_use] pub fn scale(&self, factor: f64) -> Coor2D { - Coor2D([self[0] * factor, self[1] * factor]) + Coor2D([self.x() * factor, self.y() * factor]) } /// Scalar product #[must_use] pub fn dot(&self, other: Coor2D) -> f64 { - self[0] * other[0] + self[1] * other[1] - } -} - -// ----- D I S T A N C E S --------------------------------------------------- - -impl Coor2D { - /// 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: - /// - /// [`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] - pub fn hypot2(&self, other: &Self) -> f64 { - (self[0] - other[0]).hypot(self[1] - other[1]) - } -} - -impl From for Coor4D { - fn from(c: Coor2D) -> Self { - Coor4D([c[0], c[1], 0.0, 0.0]) - } -} - -impl From for Coor2D { - fn from(xyzt: Coor4D) -> Self { - Coor2D([xyzt[0], xyzt[1]]) + self.x() * other.x() + self.y() * other.y() } } @@ -197,16 +128,15 @@ mod tests { let c = Coor2D::raw(12., 55.).to_radians(); let d = Coor2D::gis(12., 55.); assert_eq!(c, d); - assert_eq!(d[0], 12f64.to_radians()); - let e = d.to_degrees(); - assert_eq!(e[0], c.to_degrees()[0]); + assert_eq!(d.x(), 12f64.to_radians()); + assert_eq!(d.x().to_degrees(), c.x().to_degrees()); } #[test] fn array() { let b = Coor2D::raw(7., 8.); - let c = [b[0], b[1], f64::NAN, f64::NAN]; - assert_eq!(b[0], c[0]); + let c = [b.x(), b.y(), f64::NAN, f64::NAN]; + assert_eq!(b.x(), c[0]); } #[test] diff --git a/src/coordinate/coor32.rs b/src/coordinate/coor32.rs index bc735667..6177fe8c 100644 --- a/src/coordinate/coor32.rs +++ b/src/coordinate/coor32.rs @@ -1,6 +1,5 @@ /// Tiny coordinate type: 2D, 32 bits, only one fourth the weight of a Coord. /// Probably only useful for small scale world maps, without too much zoom. -use super::*; use crate::math::angular; use std::ops::{Index, IndexMut}; @@ -23,34 +22,6 @@ impl IndexMut for Coor32 { } } -// ----- A N G U L A R U N I T S ------------------------------------------- - -impl AngularUnits for Coor32 { - /// Transform the first two elements of a `Coor32` from degrees to radians - #[must_use] - fn to_radians(self) -> Self { - Coor32([self[0].to_radians(), self[1].to_radians()]) - } - - /// Transform the elements of a `Coor32` from radians to degrees - #[must_use] - fn to_degrees(self) -> Self { - Coor32([self[0].to_degrees(), self[1].to_degrees()]) - } - - /// Transform the elements of a `Coor32` from radians to seconds of arc. - #[must_use] - fn to_arcsec(self) -> Self { - Coor32([self[0].to_degrees() * 3600., self[1].to_degrees() * 3600.]) - } - - /// Transform the internal lon/lat-in-radians to lat/lon-in-degrees - #[must_use] - fn to_geo(self) -> Self { - Coor32([self[1].to_degrees(), self[0].to_degrees()]) - } -} - // ----- C O N S T R U C T O R S --------------------------------------------- /// Constructors @@ -139,51 +110,12 @@ impl Coor32 { } } -impl From for Coor4D { - fn from(c: Coor32) -> Self { - Coor4D([c[0] as f64, c[1] as f64, 0.0, f64::NAN]) - } -} - -impl From for Coor32 { - fn from(xyzt: Coor4D) -> Self { - Coor32::raw(xyzt[0], xyzt[1]) - } -} - -// ----- D I S T A N C E S --------------------------------------------------- - -impl Coor32 { - /// 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: - /// - /// [`distance`](crate::ellipsoid::Ellipsoid::distance) - /// - /// # Examples - /// - /// ``` - /// use geodesy::prelude::*; - /// let t = 1000.; - /// let p0 = Coor32::origin(); - /// let p1 = Coor32::raw(t, t); - /// assert_eq!(p0.hypot2(&p1), t.hypot(t)); - /// ``` - #[must_use] - pub fn hypot2(&self, other: &Self) -> f64 { - (self[0] as f64 - other[0] as f64).hypot(self[1] as f64 - other[1] as f64) - } -} - // ----- T E S T S --------------------------------------------------- #[cfg(test)] mod tests { - use super::*; + use crate::prelude::*; + #[test] fn distances() { let lat = angular::dms_to_dd(55, 30, 36.); diff --git a/src/coordinate/coor3d.rs b/src/coordinate/coor3d.rs index f18feca2..40843572 100644 --- a/src/coordinate/coor3d.rs +++ b/src/coordinate/coor3d.rs @@ -1,4 +1,3 @@ -use super::*; use crate::math::angular; use std::ops::{Add, Div, Index, IndexMut, Mul, Sub}; @@ -76,39 +75,6 @@ impl Div for Coor3D { } } -// ----- A N G U L A R U N I T S ------------------------------------------- - -impl AngularUnits for Coor3D { - /// Transform the first two elements of a `Coor3D` from degrees to radians - #[must_use] - fn to_radians(self) -> Self { - Coor3D::raw(self[0].to_radians(), self[1].to_radians(), self[2]) - } - - /// Transform the first two elements of a `Coor3D` from radians to degrees - #[must_use] - fn to_degrees(self) -> Self { - Coor3D::raw(self[0].to_degrees(), self[1].to_degrees(), self[2]) - } - - /// Transform the first two elements of a `Coor3D` from radians to seconds - /// of arc. - #[must_use] - fn to_arcsec(self) -> Self { - Coor3D::raw( - self[0].to_degrees() * 3600., - self[1].to_degrees() * 3600., - self[2], - ) - } - - /// Transform the internal lon/lat/h/t-in-radians to lat/lon/h/t-in-degrees - #[must_use] - fn to_geo(self) -> Self { - Coor3D::raw(self[1].to_degrees(), self[0].to_degrees(), self[2]) - } -} - // ----- C O N S T R U C T O R S --------------------------------------------- /// Constructors @@ -201,74 +167,12 @@ impl Coor3D { } } -// ----- D I S T A N C E S --------------------------------------------------- - -impl Coor3D { - /// 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. - /// - /// The distance is computed in the subspace spanned by the first and - /// second coordinate of the `Coor3D`s - /// - /// # See also: - /// - /// [`hypot3`](Coor3D::hypot3), - /// [`distance`](crate::ellipsoid::Ellipsoid::distance) - /// - /// # Examples - /// - /// ``` - /// use geodesy::prelude::*; - /// let t = 1000 as f64; - /// let p0 = Coor3D::origin(); - /// let p1 = Coor3D::raw(t, t, 0.); - /// assert_eq!(p0.hypot2(&p1), t.hypot(t)); - /// ``` - #[must_use] - pub fn hypot2(&self, other: &Self) -> f64 { - (self[0] - other[0]).hypot(self[1] - other[1]) - } - - /// 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`](Coor3D::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] - pub fn hypot3(&self, other: &Self) -> f64 { - (self[0] - other[0]) - .hypot(self[1] - other[1]) - .hypot(self[2] - other[2]) - } -} - // ----- T E S T S --------------------------------------------------- #[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 2661a6eb..4b88b7c2 100644 --- a/src/coordinate/coor4d.rs +++ b/src/coordinate/coor4d.rs @@ -1,6 +1,5 @@ -use super::*; use crate::math::angular; -use std::ops::{Add, Div, Index, IndexMut, Mul, Sub}; +use std::ops::{Add, Div, Mul, Sub}; /// Generic 4D coordinate tuple, with no fixed interpretation of the elements #[derive(Debug, Default, PartialEq, Copy, Clone)] @@ -8,6 +7,8 @@ pub struct Coor4D(pub [f64; 4]); // ----- O P E R A T O R T R A I T S ------------------------------------------------- +use std::ops::{Index, IndexMut}; + impl Index for Coor4D { type Output = f64; fn index(&self, i: usize) -> &Self::Output { @@ -81,40 +82,6 @@ impl Div for Coor4D { } } -// ----- A N G U L A R U N I T S ------------------------------------------- - -impl AngularUnits for Coor4D { - /// Transform the first two elements of a `Coor4D` from degrees to radians - #[must_use] - fn to_radians(self) -> Self { - Coor4D::raw(self[0].to_radians(), self[1].to_radians(), self[2], self[3]) - } - - /// Transform the first two elements of a `Coor4D` from radians to degrees - #[must_use] - fn to_degrees(self) -> Self { - Coor4D::raw(self[0].to_degrees(), self[1].to_degrees(), self[2], self[3]) - } - - /// Transform the first two elements of a `Coor4D` from radians to seconds - /// of arc. - #[must_use] - fn to_arcsec(self) -> Self { - Coor4D::raw( - self[0].to_degrees() * 3600., - self[1].to_degrees() * 3600., - self[2], - self[3], - ) - } - - /// Transform the internal lon/lat/h/t-in-radians to lat/lon/h/t-in-degrees - #[must_use] - fn to_geo(self) -> Self { - Coor4D::raw(self[1].to_degrees(), self[0].to_degrees(), self[2], self[3]) - } -} - // ----- C O N S T R U C T O R S --------------------------------------------- /// Constructors @@ -128,7 +95,7 @@ impl Coor4D { /// A `Coor4D` from longitude/latitude/height/time, 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, time: f64) -> Coor4D { + pub fn parcsec(longitude: f64, latitude: f64, height: f64, time: f64) -> Coor4D { Coor4D([ longitude.to_radians() / 3600., latitude.to_radians() / 3600., @@ -184,91 +151,6 @@ impl Coor4D { pub fn ones() -> Coor4D { Coor4D([1., 1., 1., 1.]) } - - /// Arithmetic (also see the operator trait implementations `add, sub, mul, div`) - - /// Multiply by a scalar - #[must_use] - pub fn scale(&self, factor: f64) -> Coor4D { - let mut result = Coor4D::nan(); - for i in 0..4 { - result[i] = self[i] * factor; - } - result - } - - /// Scalar product - #[must_use] - pub fn dot(&self, other: Coor4D) -> f64 { - let mut result = 0_f64; - for i in 0..4 { - result += self[i] * other[i]; - } - result - } -} - -// ----- D I S T A N C E S --------------------------------------------------- - -impl Coor4D { - /// 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. - /// - /// The distance is computed in the subspace spanned by the first and - /// second coordinate of the `Coor4D`s - /// - /// # See also: - /// - /// [`hypot3`](Coor4D::hypot3), - /// [`distance`](crate::ellipsoid::Ellipsoid::distance) - /// - /// # Examples - /// - /// ``` - /// use geodesy::prelude::*; - /// let t = 1000 as f64; - /// let p0 = Coor4D::origin(); - /// let p1 = Coor4D::raw(t, t, 0., 0.); - /// assert_eq!(p0.hypot2(&p1), t.hypot(t)); - /// ``` - #[must_use] - pub fn hypot2(&self, other: &Self) -> f64 { - (self[0] - other[0]).hypot(self[1] - other[1]) - } - - /// 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 `Coor4D`s - /// - /// # See also: - /// - /// [`hypot2`](Coor4D::hypot2), - /// [`distance`](crate::ellipsoid::Ellipsoid::distance) - /// - /// # Examples - /// - /// ``` - /// use geodesy::prelude::*; - /// let t = 1000 as f64; - /// let p0 = Coor4D::origin(); - /// let p1 = Coor4D::raw(t, t, t, 0.); - /// assert_eq!(p0.hypot3(&p1), t.hypot(t).hypot(t)); - /// ``` - #[must_use] - pub fn hypot3(&self, other: &Self) -> f64 { - (self[0] - other[0]) - .hypot(self[1] - other[1]) - .hypot(self[2] - other[2]) - } } // ----- T E S T S --------------------------------------------------- @@ -276,6 +158,7 @@ impl Coor4D { #[cfg(test)] mod tests { use super::*; + use crate::prelude::*; #[test] fn distances() { @@ -292,16 +175,16 @@ mod tests { let c = Coor4D::raw(12., 55., 100., 0.).to_radians(); let d = Coor4D::gis(12., 55., 100., 0.); assert_eq!(c, d); - assert_eq!(d[0], 12f64.to_radians()); + assert_eq!(d.x(), 12f64.to_radians()); let e = d.to_degrees(); - assert_eq!(e[0], c.to_degrees()[0]); + assert_eq!(e.x(), c.to_degrees().x()); } #[test] fn array() { let b = Coor4D::raw(7., 8., 9., 10.); - let c = [b[0], b[1], b[2], b[3], f64::NAN, f64::NAN]; - assert_eq!(b[0], c[0]); + let c = [b.x(), b.y(), b.z(), b.t(), f64::NAN, f64::NAN]; + assert_eq!(b.x(), c[0]); } #[test] @@ -313,13 +196,9 @@ mod tests { let c = a.add(b); assert_eq!(c, Coor4D([5., 5., 5., 5.])); - let d = c.scale(2.); - assert_eq!(d, Coor4D([10., 10., 10., 10.])); - let e = t.div(b); assert_eq!(e, Coor4D([3., 4., 6., 12.])); assert_eq!(e.mul(b), t); - assert_eq!(a.dot(b), 20.) } } diff --git a/src/coordinate/mod.rs b/src/coordinate/mod.rs index 857312a7..f299d049 100644 --- a/src/coordinate/mod.rs +++ b/src/coordinate/mod.rs @@ -10,17 +10,75 @@ pub mod set; /// dimensions only. pub trait AngularUnits { /// Transform the first two elements of a coordinate tuple from degrees to radians - fn to_radians(self) -> Self; + fn to_radians(&self) -> Self; /// Transform the first two elements of a coordinate tuple from radians to degrees - fn to_degrees(self) -> Self; + fn to_degrees(&self) -> Self; /// Transform the first two elements of a coordinate tuple from radians to seconds /// of arc. - fn to_arcsec(self) -> Self; + fn to_arcsec(&self) -> Self; /// Transform the internal lon/lat(/h/t)-in-radians to lat/lon(/h/t)-in-degrees - fn to_geo(self) -> Self; + fn to_geo(&self) -> Self; + + fn scale(&self, factor: f64) -> Self; + fn dot(&self, other: Self) -> f64; +} + +impl AngularUnits for T +where + T: CoordinateTuple + Copy, +{ + /// Convert the first two elements of `self` from radians to degrees + fn to_degrees(&self) -> Self { + let (x, y) = self.xy(); + let mut res = *self; + res.update(&[x.to_degrees(), y.to_degrees()]); + res + } + + /// Convert the first two elements of `self` from radians to degrees + fn to_arcsec(&self) -> Self { + let (x, y) = self.xy(); + dbg!((x, y)); + dbg!(x.to_degrees()); + let mut res = *self; + res.update(&[x.to_degrees() * 3600., y.to_degrees() * 3600.]); + res + } + + /// Convert the first two elements of `self` from degrees to radians + fn to_radians(&self) -> Self { + let (x, y) = self.xy(); + let mut res = *self; + res.update(&[x.to_radians(), y.to_radians()]); + res + } + + /// Convert-and-swap the first two elements of `self` from radians to degrees + fn to_geo(&self) -> Self { + let (x, y) = self.xy(); + let mut res = *self; + res.update(&[y.to_degrees(), x.to_degrees()]); + res + } + + fn scale(&self, factor: f64) -> Self { + 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 { + let mut res = 0.; + for i in 0..self.dim() { + res += self.nth(i) * other.nth(i); + } + res + } } /// For Rust Geodesy, the ISO-19111 concept of `DirectPosition` is represented @@ -92,6 +150,9 @@ 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 as converted to [`Coor4D`](super::Coor4D)) + fn dim(&self) -> usize; + /// Access the `index`th coordinate tuple fn get_coord(&self, index: usize) -> Coor4D; @@ -113,8 +174,8 @@ pub trait CoordinateSet: CoordinateMetadata { } /// The CoordinateTuple is the ISO-19111 atomic spatial/spatiotemporal -/// referencing element. Loosely speaking, a CoordinateSet consists of -/// CoordinateTuples. +/// referencing element. 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 @@ -127,39 +188,40 @@ pub trait CoordinateSet: CoordinateMetadata { /// aligns well with the internal coordinate order convention of most /// Geodesy operators. /// -/// All accessors have default implementations, except the -/// [`unchecked_nth()`](crate::coordinate::CoordinateTuple::unchecked_nth) function, +/// All accessors have default implementations, except the 3 methods +/// [`unchecked_nth()`](Self::unchecked_nth()), +/// [`unchecked_set_nth()`](Self::unchecked_set_nth) 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. #[rustfmt::skip] pub trait CoordinateTuple { - const DIMENSION: usize; - /// Access the n'th (0-based) element of the CoordinateTuple. /// May panic if n >= DIMENSION. - /// See also [`nth()`](crate::coordinate::CoordinateTuple::nth). + /// See also [`nth()`](Self::nth). fn unchecked_nth(&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 unchecked_set_nth(&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 [`unchecked_nth()`](crate::coordinate::CoordinateTuple::unchecked_nth). + /// See also [`unchecked_nth()`](Self::unchecked_nth). fn nth(&self, n: usize) -> f64 { - if Self::DIMENSION < n { self.nth(n) } else {f64::NAN} - } - - /// Alternative to the DIMENSION associated const. May take over in order to - /// make the trait object safe. - fn dim(&self) -> usize { - Self::DIMENSION + if n < self.dim() { self.unchecked_nth(n) } else {f64::NAN} } // Note: We use unchecked_nth 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 cases - // of implementation. + // 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 { @@ -168,17 +230,17 @@ pub trait CoordinateTuple { /// Pragmatically named accessor for the second element of the CoordinateTuple. fn y(&self) -> f64 { - if Self::DIMENSION > 1 { self.unchecked_nth(1) } else {f64::NAN} + if self.dim() > 1 { self.unchecked_nth(1) } else {f64::NAN} } /// Pragmatically named accessor for the third element of the CoordinateTuple. fn z(&self) -> f64 { - if Self::DIMENSION > 2 { self.unchecked_nth(2) } else {f64::NAN} + if self.dim() > 2 { self.unchecked_nth(2) } else {f64::NAN} } /// Pragmatically named accessor for the fourth element of the CoordinateTuple. fn t(&self) -> f64 { - if Self::DIMENSION > 3 { self.unchecked_nth(3) } else {f64::NAN} + if self.dim() > 3 { self.unchecked_nth(3) } else {f64::NAN} } /// A tuple containing the first two components of the CoordinateTuple. @@ -196,87 +258,197 @@ pub trait CoordinateTuple { (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_in_degrees(&self) -> (f64, f64) { + 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_in_degrees(&self) -> (f64, f64, f64) { + 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_in_degrees(&self) -> (f64, f64, f64, f64) { + 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_in_arcsec(&self) -> (f64, f64) { + 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_in_arcsec(&self) -> (f64, f64, f64) { + 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_in_arcsec(&self) -> (f64, f64, f64, f64) { + 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_in_radians(&self) -> (f64, f64) { + 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_in_radians(&self) -> (f64, f64, f64) { + 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_in_radians(&self) -> (f64, f64, f64, f64) { + 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.unchecked_set_nth(n, value); + } + } + /// Replace the n'th (0-based) element of the `CoordinateTuple` with `value`. - /// May panic if n >= [DIMENSION]. - /// See also [`set_nth()`](crate::coordinate::CoordinateTuple::nth). - fn unchecked_set_nth(&mut self, n: usize, value: f64); + /// If `n >=` [`dim()`](Self::dim()) fill the coordinate with `f64::NAN`. + /// See also [`unchecked_set_nth()`](Self::unchecked_set_nth). + fn set_nth(&mut self, n: usize, value: f64) { + if n < self.dim() { + self.unchecked_set_nth(n, value) + } else { + self.fill(f64::NAN); + } + } - /// Replace the `N` first (up to [DIMENSION]) elements of `self` with the - /// elements of `value` - fn set(&mut self, value: [f64; N]) { - for i in 0..N.min(Self::DIMENSION) { - self.unchecked_set_nth(i, value[i]) + /// Replace the two first elements of the `CoordinateTuple` with `x` and `y`. + /// If the dimension in less than 2, fill the coordinate with `f64::NAN`. + /// See also [`unchecked_set_nth()`](Self::unchecked_set_nth). + fn set_xy(&mut self, x: f64, y: f64) { + if self.dim() > 1 { + self.unchecked_set_nth(0, x); + self.unchecked_set_nth(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.unchecked_set_nth(0, x); + self.unchecked_set_nth(1, y); + self.unchecked_set_nth(2, z); + } else { + self.fill(f64::NAN); + } + } + /// Replace the four first elements of the `CoordinateTuple` with `x`, `y` `z` and `t`. + /// If the dimension in 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.unchecked_set_nth(0, x); + self.unchecked_set_nth(1, y); + self.unchecked_set_nth(2, z); + self.unchecked_set_nth(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.unchecked_set_nth(i, value[i]) + } + } -// We must still implement the CoordinateTuple trait for -// the Geodesy data types Coor2D, Coor32, Coor3D, Coor4D + /// 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) + } +} // We must still implement the CoordinateTuple trait for // the Geodesy data types Coor2D, Coor32, Coor3D, Coor4D impl CoordinateTuple for Coor2D { - const DIMENSION: usize = 2; + fn dim(&self) -> usize { + 2 + } + fn unchecked_nth(&self, n: usize) -> f64 { self.0[n] } @@ -287,7 +459,10 @@ impl CoordinateTuple for Coor2D { } impl CoordinateTuple for Coor3D { - const DIMENSION: usize = 3; + fn dim(&self) -> usize { + 3 + } + fn unchecked_nth(&self, n: usize) -> f64 { self.0[n] } @@ -298,7 +473,10 @@ impl CoordinateTuple for Coor3D { } impl CoordinateTuple for Coor4D { - const DIMENSION: usize = 4; + fn dim(&self) -> usize { + 4 + } + fn unchecked_nth(&self, n: usize) -> f64 { self.0[n] } @@ -309,7 +487,10 @@ impl CoordinateTuple for Coor4D { } impl CoordinateTuple for Coor32 { - const DIMENSION: usize = 2; + fn dim(&self) -> usize { + 2 + } + fn unchecked_nth(&self, n: usize) -> f64 { self.0[n] as f64 } @@ -322,7 +503,8 @@ impl CoordinateTuple for Coor32 { // And let's also implement it for a plain 2D f64 tuple #[rustfmt::skip] impl CoordinateTuple for (f64, f64) { - const DIMENSION: usize = 2; + fn dim(&self) -> usize { 2 } + fn unchecked_nth(&self, n: usize) -> f64 { match n { 0 => self.0, diff --git a/src/coordinate/set.rs b/src/coordinate/set.rs index 69d99f7d..1ac08111 100644 --- a/src/coordinate/set.rs +++ b/src/coordinate/set.rs @@ -1,52 +1,13 @@ use super::*; -impl AngularUnits for &mut T -where - T: CoordinateSet, -{ - /// Transform the first two elements of all elements in a - /// coordinate set from degrees to radians - fn to_radians(self) -> Self { - for i in 0..self.len() { - self.set_coord(i, &self.get_coord(i).to_radians()); - } - self - } - - /// Transform the first two elements of a all elements in a - /// coordinate set from radians to degrees - #[must_use] - fn to_degrees(self) -> Self { - for i in 0..self.len() { - self.set_coord(i, &self.get_coord(i).to_degrees()); - } - self - } - - /// Transform the first two elements of a all elements in a - /// coordinate set from radians to seconds of arc. - #[must_use] - fn to_arcsec(self) -> Self { - for i in 0..self.len() { - self.set_coord(i, &self.get_coord(i).to_arcsec()); - } - self - } - - /// Transform all elements in a coordinate set from the internal - /// lon/lat/h/t-in-radians to lat/lon/h/t-in-degrees - fn to_geo(self) -> Self { - for i in 0..self.len() { - self.set_coord(i, &self.get_coord(i).to_geo()); - } - self - } -} - // ----- CoordinateSet implementations for some Coor4D containers ------------ -impl CoordinateSet for [Coor4D; N] { + +impl CoordinateSet for &mut [Coor4D] { fn len(&self) -> usize { - N + (**self).len() + } + fn dim(&self) -> usize { + 4 } fn get_coord(&self, index: usize) -> Coor4D { self[index] @@ -56,9 +17,12 @@ impl CoordinateSet for [Coor4D; N] { } } -impl CoordinateSet for &mut [Coor4D] { +impl CoordinateSet for [Coor4D; N] { fn len(&self) -> usize { - (**self).len() + N + } + fn dim(&self) -> usize { + 4 } fn get_coord(&self, index: usize) -> Coor4D { self[index] @@ -72,6 +36,9 @@ impl CoordinateSet for Vec { fn len(&self) -> usize { self.len() } + fn dim(&self) -> usize { + 4 + } fn get_coord(&self, index: usize) -> Coor4D { self[index] } @@ -81,10 +48,14 @@ impl CoordinateSet for Vec { } // ----- CoordinateSet implementations for some Coor3D containers ------------ + impl CoordinateSet for [Coor3D; N] { fn len(&self) -> usize { N } + fn dim(&self) -> usize { + 3 + } fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0], self[index][1], self[index][2], f64::NAN]) } @@ -97,6 +68,9 @@ impl CoordinateSet for &mut [Coor3D] { fn len(&self) -> usize { (**self).len() } + fn dim(&self) -> usize { + 3 + } fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0], self[index][1], self[index][2], f64::NAN]) } @@ -109,6 +83,9 @@ impl CoordinateSet for Vec { fn len(&self) -> usize { self.len() } + fn dim(&self) -> usize { + 3 + } fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0], self[index][1], self[index][2], f64::NAN]) } @@ -141,6 +118,9 @@ impl CoordinateSet for [Coor2D; N] { fn len(&self) -> usize { N } + fn dim(&self) -> usize { + 2 + } fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0], self[index][1], 0., f64::NAN]) } @@ -153,6 +133,9 @@ impl CoordinateSet for &mut [Coor2D] { fn len(&self) -> usize { (**self).len() } + fn dim(&self) -> usize { + 2 + } fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0], self[index][1], 0.0, f64::NAN]) } @@ -165,6 +148,9 @@ impl CoordinateSet for Vec { fn len(&self) -> usize { self.len() } + fn dim(&self) -> usize { + 2 + } fn get_coord(&self, index: usize) -> Coor4D { Coor4D([self[index][0], self[index][1], 0., f64::NAN]) } @@ -183,6 +169,9 @@ where fn len(&self) -> usize { self.0.len() } + fn dim(&self) -> usize { + 4 + } fn get_coord(&self, index: usize) -> Coor4D { let c = self.0.get_coord(index); Coor4D([c[0], c[1], self.1, self.2]) @@ -202,6 +191,9 @@ where fn len(&self) -> usize { self.0.len() } + fn dim(&self) -> usize { + 4 + } fn get_coord(&self, index: usize) -> Coor4D { let c = self.0.get_coord(index); Coor4D([c[0], c[1], c[2], self.1]) @@ -213,36 +205,15 @@ where // ----- CoordinateSet implementations for some Coor32 containers ------------ -impl CoordinateSet for [Coor32; N] { - fn len(&self) -> usize { - N - } - fn get_coord(&self, index: usize) -> Coor4D { - Coor4D([self[index][0] as f64, self[index][1] as f64, 0., f64::NAN]) - } - fn set_coord(&mut self, index: usize, value: &Coor4D) { - self[index] = Coor32::raw(value[0], value[1]); - } -} - impl CoordinateSet for &mut [Coor32] { fn len(&self) -> usize { (**self).len() } - fn get_coord(&self, index: usize) -> Coor4D { - Coor4D([self[index][0] as f64, self[index][1] as f64, 0.0, f64::NAN]) - } - fn set_coord(&mut self, index: usize, value: &Coor4D) { - self[index] = Coor32::raw(value[0], value[1]); - } -} - -impl CoordinateSet for Vec { - fn len(&self) -> usize { - self.len() + fn dim(&self) -> usize { + 2 } fn get_coord(&self, index: usize) -> Coor4D { - Coor4D([self[index][0] as f64, self[index][1] as f64, 0., f64::NAN]) + Coor4D([self[index][0] as f64, self[index][1] as f64, 0.0, f64::NAN]) } fn set_coord(&mut self, index: usize, value: &Coor4D) { self[index] = Coor32::raw(value[0], value[1]); @@ -321,20 +292,24 @@ mod tests { // Test the "AngularUnits" conversion trait #[test] fn angular() { - let mut operands = some_basic_coor2dinates(); + let operands = some_basic_coor2dinates(); let cph = operands.get_coord(0); // Note the different usage patterns when using the AngularUnits trait with // a Coor4D and a CoordinateSet: For the latter, the blanket implementation // is for an `&mut T where T: CoordinateSet`, and we just mutate the contents // in situ. For the former, we return a newly computed `Coor4D`. - operands.to_radians(); let cph = cph.to_radians(); - assert_eq!(cph[0], operands.get_coord(0)[0]); - assert_eq!(cph[1], operands.get_coord(0)[1]); - - operands.to_arcsec(); - assert_eq!(cph[0].to_degrees() * 3600., operands.get_coord(0)[0]); - assert_eq!(cph[1].to_degrees() * 3600., operands.get_coord(0)[1]); + assert_eq!(cph[0], operands.get_coord(0).to_radians()[0]); + assert_eq!(cph[1], operands.get_coord(0).to_radians()[1]); + + assert_eq!( + cph[0].to_degrees() * 3600., + operands.get_coord(0).to_radians().to_arcsec()[0] + ); + assert_eq!( + cph[1].to_degrees() * 3600., + operands.get_coord(0).to_radians().to_arcsec()[1] + ); } } diff --git a/src/ellipsoid/geodesics.rs b/src/ellipsoid/geodesics.rs index 558472a3..e0b3b1f7 100644 --- a/src/ellipsoid/geodesics.rs +++ b/src/ellipsoid/geodesics.rs @@ -16,7 +16,12 @@ impl Ellipsoid { /// Federico Dolce and Michael Kirk, provides a Rust implementation of Karney's algorithm. #[must_use] #[allow(non_snake_case)] - pub fn geodesic_fwd(&self, from: &C, azimuth: f64, distance: f64) -> Coor4D { + pub fn geodesic_fwd( + &self, + from: &C, + azimuth: f64, + distance: f64, + ) -> Coor4D { // Coordinates of the point of origin, P1 let (L1, B1) = from.xy(); @@ -238,9 +243,9 @@ mod tests { assert!((d[2] - 2365723.367715).abs() < 1e-4); // And the other way round... - let b = ellps.geodesic_fwd(&p1, d[0], d[2]).to_degrees(); - assert!((b[0] - p2[0].to_degrees()).abs() < 1e-9); - assert!((b[1] - p2[1].to_degrees()).abs() < 1e-9); + let b = ellps.geodesic_fwd(&p1, d[0], d[2]); + assert!((b[0].to_degrees() - p2[0].to_degrees()).abs() < 1e-9); + assert!((b[1].to_degrees() - p2[1].to_degrees()).abs() < 1e-9); Ok(()) } } diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 728c3bbc..fea9756c 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -340,6 +340,7 @@ pub fn grids_at(grids: &[Arc], coord: &Coor4D, use_null_grid: bool) -> #[cfg(test)] mod tests { use super::*; + use crate::coordinate::AngularUnits; // lat_n, lat_s, lon_w, lon_e, dlat, dlon const HEADER: [f64; 6] = [58., 54., 8., 16., -1., 1.]; diff --git a/src/inner_op/gridshift.rs b/src/inner_op/gridshift.rs index 9f67d2cc..ba8efc71 100644 --- a/src/inner_op/gridshift.rs +++ b/src/inner_op/gridshift.rs @@ -148,6 +148,7 @@ pub fn new(parameters: &RawParameters, ctx: &dyn Context) -> Result { #[cfg(test)] mod tests { use super::*; + use crate::coordinate::AngularUnits; #[test] fn gridshift() -> Result<(), Error> { diff --git a/src/op/mod.rs b/src/op/mod.rs index 5dfbe9ba..b08af4d4 100644 --- a/src/op/mod.rs +++ b/src/op/mod.rs @@ -381,11 +381,11 @@ mod tests { // Now instantiating the macro with ham = 2 let op = ctx.op("helmert:ham ham=2")?; ctx.apply(op, Fwd, &mut data)?; - assert_eq!(data[0][0], 57.); - assert_eq!(data[1][0], 61.); + assert_eq!(data[0].x(), 57.); + assert_eq!(data[1].x(), 61.); ctx.apply(op, Inv, &mut data)?; - assert_eq!(data[0][0], 55.); - assert_eq!(data[1][0], 59.); + assert_eq!(data[0].x(), 55.); + assert_eq!(data[1].x(), 59.); Ok(()) }