Skip to content

Commit 09c2140

Browse files
committed
Trait impl of ISO-19111 coordinate tuple
1 parent 80120f5 commit 09c2140

File tree

4 files changed

+124
-113
lines changed

4 files changed

+124
-113
lines changed

src/coordinate/mod.rs

+105-98
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,27 @@ pub trait CoordinateMetadata {
8282
impl<T> CoordinateMetadata for T where T: ?Sized {}
8383

8484
/// CoordinateSet is the fundamental coordinate access interface in ISO-19111.
85+
/// Strictly speaking, it is not a set, but (in abstract terms) rather an
86+
/// indexed list, or (in more concrete terms): An array.
8587
///
8688
/// Here it is implemented simply as an accessor trait, that allows us to
8789
/// access any user provided data model by iterating over its elements,
8890
/// represented as a `Coor4D`
8991
pub trait CoordinateSet: CoordinateMetadata {
9092
/// Number of coordinate tuples in the set
9193
fn len(&self) -> usize;
94+
9295
/// Access the `index`th coordinate tuple
9396
fn get_coord(&self, index: usize) -> Coor4D;
97+
9498
/// Overwrite the `index`th coordinate tuple
9599
fn set_coord(&mut self, index: usize, value: &Coor4D);
100+
101+
/// Companion to `len()`
96102
fn is_empty(&self) -> bool {
97103
self.len() == 0
98104
}
105+
99106
/// Set all coordinate tuples in the set to NaN
100107
fn stomp(&mut self) {
101108
let nanny = Coor4D::nan();
@@ -105,130 +112,130 @@ pub trait CoordinateSet: CoordinateMetadata {
105112
}
106113
}
107114

108-
//-----------------------------------------------------------------------
109-
// An experiment with an extended version of Kyle Barron's CoordTrait PR
110-
// over at https://github.com/georust/geo/pull/1157
111-
//-----------------------------------------------------------------------
112-
113-
// The next 8 lines are mostly copied from georust/geo/geo-types/src/lib.rs
114-
// Although I added ToPrimitive in the first line
115-
use core::fmt::Debug;
116-
use num_traits::{Float, Num, NumCast, ToPrimitive};
117-
pub trait CoordinateType: Num + Copy + NumCast + PartialOrd + Debug {}
118-
impl<T: Num + Copy + NumCast + PartialOrd + Debug> CoordinateType for T {}
119-
pub trait CoordNum: CoordinateType + Debug {}
120-
impl<T: CoordinateType + Debug> CoordNum for T {}
121-
pub trait CoordFloat: CoordNum + Float {}
122-
impl<T: CoordNum + Float> CoordFloat for T {}
123-
124-
// And here is Kyle Barron's CoordTrait from https://github.com/georust/geo/pull/1157
125-
// extended with z(), t(), m(), and associated consts DIMENSION and MEASURE
126-
pub trait CoordTrait {
127-
type T: CoordNum;
115+
/// The CoordinateTuple is the ISO-19111 atomic spatial/spatiotemporal
116+
/// referencing element. Loosely speaking, a CoordinateSet consists of
117+
/// CoordinateTuples.
118+
///
119+
/// Note that (despite the formal name) the underlying data structure
120+
/// need not be a tuple: It can be any item, for which it makes sense
121+
/// to implement the CoordinateTuple trait.
122+
///
123+
/// The CoordinateTuple trait provides a number of convenience accessors
124+
/// for accessing single coordinate elements or tuples of subsets.
125+
/// These accessors are pragmatically named (x, y, xy, etc.). While these
126+
/// names may be geodetically naive, they are suggestive and practical, and
127+
/// aligns well with the internal coordinate order convention of most
128+
/// Geodesy operators.
129+
///
130+
/// All accessors have default implementations, except the
131+
/// [`unchecked_nth()`](crate::coordinate::CoordinateTuple::unchecked_nth) function,
132+
/// which must be provided by the implementer.
133+
///
134+
/// When accessing dimensions outside of the domain of the CoordinateTuple,
135+
/// [NaN](f64::NAN) will be returned.
136+
#[rustfmt::skip]
137+
pub trait CoordinateTuple {
128138
const DIMENSION: usize;
129-
const MEASURE: bool;
130139

131-
/// Accessors for the coordinate tuple components
132-
fn x(&self) -> Self::T;
133-
fn y(&self) -> Self::T;
134-
fn z(&self) -> Self::T;
135-
fn t(&self) -> Self::T;
136-
fn m(&self) -> Self::T;
140+
/// Access the n'th (0-based) element of the CoordinateTuple.
141+
/// May panic if n >= DIMENSION.
142+
/// See also [`nth()`](crate::coordinate::CoordinateTuple::nth).
143+
fn unchecked_nth(&self, n: usize) -> f64;
137144

138-
/// Returns a tuple that contains the two first components of the coord.
139-
fn x_y(&self) -> (Self::T, Self::T) {
140-
(self.x(), self.y())
145+
/// Access the n'th (0-based) element of the CoordinateTuple.
146+
/// Returns NaN if `n >= DIMENSION`.
147+
/// See also [`unchecked_nth()`](crate::coordinate::CoordinateTuple::unchecked_nth).
148+
fn nth(&self, n: usize) -> f64 {
149+
if Self::DIMENSION < n { self.nth(n) } else {f64::NAN}
141150
}
142-
}
143151

144-
// The CoordTuples trait is blanket-implemented for anything that
145-
// CoordTrait is implemented for
146-
impl<C: CoordTrait> CoordTuples for C {}
152+
/// Alternative to the DIMENSION associated const. May take over in order to
153+
/// make the trait object safe.
154+
fn dim(&self) -> usize {
155+
Self::DIMENSION
156+
}
147157

148-
// And here the actual implementation, which takes any CoordTrait implementing
149-
// data type, and lets us access the contents as geodesy-compatible f64 tuples
150-
#[rustfmt::skip]
151-
pub trait CoordTuples: CoordTrait {
152-
/// Accessors for the coordinate tuple components converted to f64
153-
fn x_as_f64(&self) -> f64 { self.x().to_f64().unwrap_or(f64::NAN) }
154-
fn y_as_f64(&self) -> f64 { self.y().to_f64().unwrap_or(f64::NAN) }
155-
fn z_as_f64(&self) -> f64 { self.z().to_f64().unwrap_or(f64::NAN) }
156-
fn t_as_f64(&self) -> f64 { self.t().to_f64().unwrap_or(f64::NAN) }
157-
fn m_as_f64(&self) -> f64 { self.m().to_f64().unwrap_or(f64::NAN) }
158+
// Note: We use unchecked_nth and explicitly check for dimension in
159+
// y(), z() and t(), rather than leaving the check to nth(...).
160+
// This is because the checks in these cases are constant expressions,
161+
// and hence can be eliminated by the compiler in the concrete cases
162+
// of implementation.
158163

159-
fn xy_as_f64(&self) -> (f64, f64) {
160-
(self.x_as_f64(), self.y_as_f64())
164+
/// Pragmatically named accessor for the first element of the CoordinateTuple.
165+
fn x(&self) -> f64 {
166+
self.unchecked_nth(0)
161167
}
162168

163-
fn xyz_as_f64(&self) -> (f64, f64, f64) {
164-
(self.x_as_f64(), self.y_as_f64(), self.z_as_f64())
169+
/// Pragmatically named accessor for the second element of the CoordinateTuple.
170+
fn y(&self) -> f64 {
171+
if Self::DIMENSION > 1 { self.unchecked_nth(1) } else {f64::NAN}
172+
}
173+
174+
/// Pragmatically named accessor for the third element of the CoordinateTuple.
175+
fn z(&self) -> f64 {
176+
if Self::DIMENSION > 2 { self.unchecked_nth(2) } else {f64::NAN}
177+
}
178+
179+
/// Pragmatically named accessor for the fourth element of the CoordinateTuple.
180+
fn t(&self) -> f64 {
181+
if Self::DIMENSION > 3 { self.unchecked_nth(3) } else {f64::NAN}
165182
}
166183

167-
fn xyzt_as_f64(&self) -> (f64, f64, f64, f64) {
168-
(self.x_as_f64(), self.y_as_f64(), self.z_as_f64(), self.t_as_f64())
184+
/// A tuple containing the first two components of the CoordinateTuple.
185+
fn xy(&self) -> (f64, f64) {
186+
(self.x(), self.y())
169187
}
170-
}
171188

172-
// We must still implement the foundational CoordTrait trait for
173-
// the Geodesy data types Coor2D, Coor32, Coor3D, Coor4D
189+
/// A tuple containing the first three components of the CoordinateTuple.
190+
fn xyz(&self) -> (f64, f64, f64) {
191+
(self.x(), self.y(), self.z())
192+
}
174193

175-
#[rustfmt::skip]
176-
impl CoordTrait for Coor2D {
177-
type T = f64;
178-
const DIMENSION: usize = 2;
179-
const MEASURE: bool = false;
180-
fn x(&self) -> Self::T { self.0[0] }
181-
fn y(&self) -> Self::T { self.0[1] }
182-
fn z(&self) -> Self::T { f64::NAN }
183-
fn t(&self) -> Self::T { f64::NAN }
184-
fn m(&self) -> Self::T { f64::NAN }
194+
/// A tuple containing the first four components of the CoordinateTuple.
195+
fn xyzt(&self) -> (f64, f64, f64, f64) {
196+
(self.x(), self.y(), self.z(), self.t())
197+
}
185198
}
186199

187-
#[rustfmt::skip]
188-
impl CoordTrait for Coor32 {
189-
type T = f32;
200+
// We must still implement the CoordinateTuple trait for
201+
// the Geodesy data types Coor2D, Coor32, Coor3D, Coor4D
202+
impl CoordinateTuple for Coor2D {
190203
const DIMENSION: usize = 2;
191-
const MEASURE: bool = false;
192-
fn x(&self) -> Self::T { self.0[0] }
193-
fn y(&self) -> Self::T { self.0[1] }
194-
fn z(&self) -> Self::T { f32::NAN }
195-
fn t(&self) -> Self::T { f32::NAN }
196-
fn m(&self) -> Self::T { f32::NAN }
204+
fn unchecked_nth(&self, n: usize) -> f64 {
205+
self.0[n]
206+
}
197207
}
198208

199-
#[rustfmt::skip]
200-
impl CoordTrait for Coor3D {
201-
type T = f64;
209+
impl CoordinateTuple for Coor3D {
202210
const DIMENSION: usize = 3;
203-
const MEASURE: bool = false;
204-
fn x(&self) -> Self::T { self.0[0] }
205-
fn y(&self) -> Self::T { self.0[1] }
206-
fn z(&self) -> Self::T { self.0[2] }
207-
fn t(&self) -> Self::T { f64::NAN }
208-
fn m(&self) -> Self::T { f64::NAN }
211+
fn unchecked_nth(&self, n: usize) -> f64 {
212+
self.0[n]
213+
}
209214
}
210215

211-
#[rustfmt::skip]
212-
impl CoordTrait for Coor4D {
213-
type T = f64;
216+
impl CoordinateTuple for Coor4D {
214217
const DIMENSION: usize = 4;
215-
const MEASURE: bool = false;
216-
fn x(&self) -> Self::T { self.0[0] }
217-
fn y(&self) -> Self::T { self.0[1] }
218-
fn z(&self) -> Self::T { self.0[2] }
219-
fn t(&self) -> Self::T { self.0[3] }
220-
fn m(&self) -> Self::T { f64::NAN }
218+
fn unchecked_nth(&self, n: usize) -> f64 {
219+
self.0[n]
220+
}
221+
}
222+
223+
impl CoordinateTuple for Coor32 {
224+
const DIMENSION: usize = 2;
225+
fn unchecked_nth(&self, n: usize) -> f64 {
226+
self.0[n] as f64
227+
}
221228
}
222229

223230
// And let's also implement it for a plain 2D f64 tuple
224231
#[rustfmt::skip]
225-
impl CoordTrait for (f64, f64) {
226-
type T = f64;
232+
impl CoordinateTuple for (f64, f64) {
227233
const DIMENSION: usize = 2;
228-
const MEASURE: bool = false;
229-
fn x(&self) -> Self::T { self.0 }
230-
fn y(&self) -> Self::T { self.1 }
231-
fn z(&self) -> Self::T { f64::NAN }
232-
fn t(&self) -> Self::T { f64::NAN }
233-
fn m(&self) -> Self::T { f64::NAN }
234+
fn unchecked_nth(&self, n: usize) -> f64 {
235+
match n {
236+
0 => self.0,
237+
1 => self.1,
238+
_ => panic!()
239+
}
240+
}
234241
}

src/ellipsoid/geodesics.rs

+13-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
use num_traits::AsPrimitive;
2-
3-
use crate::coordinate::{CoordTrait, CoordTuples};
4-
5-
// Now using an extended version of Kyle Barron's CoordTrait, cf. src/coordinate/mod.rs
1+
use crate::coordinate::CoordinateTuple;
62

73
use super::*;
84

@@ -20,9 +16,14 @@ impl Ellipsoid {
2016
/// Federico Dolce and Michael Kirk, provides a Rust implementation of Karney's algorithm.
2117
#[must_use]
2218
#[allow(non_snake_case)]
23-
pub fn geodesic_fwd<G: CoordTuples>(&self, from: &G, azimuth: f64, distance: f64) -> Coor4D {
19+
pub fn geodesic_fwd<G: CoordinateTuple>(
20+
&self,
21+
from: &G,
22+
azimuth: f64,
23+
distance: f64,
24+
) -> Coor4D {
2425
// Coordinates of the point of origin, P1
25-
let (L1, B1) = from.xy_as_f64();
26+
let (L1, B1) = from.xy();
2627

2728
// The latitude of P1 projected onto the auxiliary sphere
2829
let U1 = self.latitude_geographic_to_reduced(B1);
@@ -98,9 +99,9 @@ impl Ellipsoid {
9899
/// See [`geodesic_fwd`](crate::Ellipsoid::geodesic_fwd)
99100
#[must_use]
100101
#[allow(non_snake_case)] // So we can use the mathematical notation from the original text
101-
pub fn geodesic_inv<G: CoordTrait>(&self, from: &G, to: &G) -> Coor4D {
102-
let (L1, B1) = from.xy_as_f64();
103-
let (L2, B2) = to.xy_as_f64();
102+
pub fn geodesic_inv<G: CoordinateTuple>(&self, from: &G, to: &G) -> Coor4D {
103+
let (L1, B1) = from.xy();
104+
let (L2, B2) = to.xy();
104105
let B = B2 - B1;
105106
let L = L2 - L1;
106107

@@ -200,11 +201,8 @@ impl Ellipsoid {
200201
/// }
201202
/// ```
202203
#[must_use]
203-
pub fn distance<G: CoordTrait>(&self, from: &G, to: &G) -> f64
204-
where
205-
G::T: AsPrimitive<f64>,
206-
{
207-
self.geodesic_inv::<G>(from, to)[2]
204+
pub fn distance<G: CoordinateTuple>(&self, from: &G, to: &G) -> f64 {
205+
self.geodesic_inv(from, to)[2]
208206
}
209207
}
210208

src/inner_op/units.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
/// Units are taken from PROJ https://github.com/OSGeo/PROJ/blob/master/src/units.c,
22
3+
// the factor and description elements are not used for now, but
4+
// we keep them and allow(dead_code) to maintain alignment with
5+
// the PROJ implementation
6+
#[allow(dead_code)]
37
pub struct Unit(&'static str, &'static str, &'static str, f64);
48
impl Unit {
59
pub fn name(&self) -> &'static str {

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod prelude {
2121
pub use crate::Coor4D;
2222
pub use crate::CoordinateMetadata;
2323
pub use crate::CoordinateSet;
24+
pub use crate::CoordinateTuple;
2425

2526
// Et cetera
2627
pub use crate::Ellipsoid;
@@ -180,6 +181,7 @@ pub use crate::coordinate::coor4d::Coor4D;
180181
pub use crate::coordinate::AngularUnits;
181182
pub use crate::coordinate::CoordinateMetadata;
182183
pub use crate::coordinate::CoordinateSet;
184+
pub use crate::coordinate::CoordinateTuple;
183185

184186
// ---- Et cetera ----
185187

0 commit comments

Comments
 (0)