geodesy/coordinate/
mod.rs

1use crate::prelude::*;
2pub mod coor2d;
3pub mod coor32;
4pub mod coor3d;
5pub mod coor4d;
6pub mod set;
7
8/// Methods for changing the coordinate representation of angles.
9/// Dimensionality untold, the methods operate on the first two
10/// dimensions only.
11pub trait AngularUnits {
12    /// Transform the first two elements of a coordinate tuple from degrees to radians
13    fn to_radians(&self) -> Self;
14
15    /// Transform the first two elements of a coordinate tuple from radians to degrees
16    fn to_degrees(&self) -> Self;
17
18    /// Transform the first two elements of a coordinate tuple from radians to seconds
19    /// of arc.
20    fn to_arcsec(&self) -> Self;
21
22    /// Transform the internal lon/lat(/h/t)-in-radians to lat/lon(/h/t)-in-degrees
23    fn to_geo(&self) -> Self;
24}
25
26impl<T> AngularUnits for T
27where
28    T: CoordinateTuple + Copy,
29{
30    /// Convert the first two elements of `self` from radians to degrees
31    fn to_degrees(&self) -> Self {
32        let (x, y) = self.xy();
33        let mut res = *self;
34        res.update(&[x.to_degrees(), y.to_degrees()]);
35        res
36    }
37
38    /// Convert the first two elements of `self` from radians to degrees
39    fn to_arcsec(&self) -> Self {
40        let (x, y) = self.xy();
41        let mut res = *self;
42        res.update(&[x.to_degrees() * 3600., y.to_degrees() * 3600.]);
43        res
44    }
45
46    /// Convert the first two elements of `self` from degrees to radians
47    fn to_radians(&self) -> Self {
48        let (x, y) = self.xy();
49        let mut res = *self;
50        res.update(&[x.to_radians(), y.to_radians()]);
51        res
52    }
53
54    /// Convert-and-swap the first two elements of `self` from radians to degrees
55    fn to_geo(&self) -> Self {
56        let (x, y) = self.xy();
57        let mut res = *self;
58        res.update(&[y.to_degrees(), x.to_degrees()]);
59        res
60    }
61}
62
63/// For Rust Geodesy, the ISO-19111 concept of `DirectPosition` is represented
64/// as a `geodesy::Coo4D`.
65///
66/// The strict connection between the ISO19107 "DirectPosition" datatype
67/// and the ISO19111/OGC Topic 2 "CoordinateSet" interface (i.e. trait)
68/// is unclear to me: The DirectPosition, according to 19107, includes
69/// metadata which in the 19111 CoordinateSet interface is lifted from the
70/// DirectPosition to the CoordinateSet level. Nevertheless, the interface
71/// consists of an array of DirectPositions, and further derives from the
72/// CoordinateMetadata interface...
73#[allow(dead_code)]
74type DirectPosition = Coor4D;
75
76// ----- Coordinate Metadata --------------------------------------------------
77
78/// OGC 18-005r5, section 7.4 https://docs.ogc.org/as/18-005r5/18-005r5.html#12
79#[derive(Debug, Default, PartialEq, PartialOrd, Copy, Clone)]
80pub struct DataEpoch(f64);
81
82/// The metadataidentifier (CRS id) is represented by an UUID placeholder
83#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
84pub struct MdIdentifier(uuid::Uuid);
85
86/// CRS given as a register item
87#[derive(Debug, Default, PartialEq, PartialOrd, Clone)]
88pub enum Crs {
89    #[default]
90    Unknown,
91    RegisterItem(String, String),
92}
93
94// ----- Interface: Coordinate Metadata ---------------------------------------
95
96/// The ISO-19111 Coordinate Metadata gamut includes an optional
97///  epoch and one of two possible ways of representing the CRS
98pub trait CoordinateMetadata {
99    fn crs_id(&self) -> Option<MdIdentifier> {
100        None
101    }
102    fn crs(&self) -> Option<Crs> {
103        Some(Crs::Unknown)
104    }
105    fn coordinate_epoch(&self) -> Option<DataEpoch> {
106        None
107    }
108    // constraints
109    fn is_valid(&self) -> bool {
110        if self.crs_id().is_none() && self.crs().is_none() {
111            return false;
112        }
113        true
114        // TODO: check for coordinate_epoch.is_some() for dynamic crs
115    }
116}
117
118// Preliminary empty blanket implementation: Defaults for all items, for all types
119impl<T> CoordinateMetadata for T where T: ?Sized {}
120
121/// CoordinateSet is the fundamental coordinate access interface in ISO-19111.
122/// Strictly speaking, it is not a set, but (in abstract terms) rather an
123/// indexed list, or (in more concrete terms): An array.
124///
125/// Here it is implemented simply as an accessor trait, that allows us to
126/// access any user provided data model by iterating over its elements,
127/// represented as a `Coor4D`
128pub trait CoordinateSet: CoordinateMetadata {
129    /// Number of coordinate tuples in the set
130    fn len(&self) -> usize;
131
132    /// Native dimension of the underlying coordinates (they will always be returned as converted to [`Coor4D`](super::Coor4D))
133    fn dim(&self) -> usize;
134
135    /// Access the `index`th coordinate tuple
136    fn get_coord(&self, index: usize) -> Coor4D;
137
138    /// Overwrite the `index`th coordinate tuple
139    fn set_coord(&mut self, index: usize, value: &Coor4D);
140
141    /// Companion to `len()`
142    fn is_empty(&self) -> bool {
143        self.len() == 0
144    }
145
146    /// Replace the two first elements of the `index`th `CoordinateTuple`
147    /// with `x` and `y`.
148    /// Consider providing a type specific version, when implementing
149    /// the CoordinateSet trait for a concrete data type: The default
150    ///  version is straightforward, but not very efficient.
151    fn set_xy(&mut self, index: usize, x: f64, y: f64) {
152        let mut coord = self.get_coord(index);
153        coord.set_nth_unchecked(0, x);
154        coord.set_nth_unchecked(1, y);
155        self.set_coord(index, &coord);
156    }
157
158    /// Access the two first elements of the `index`th `CoordinateTuple`.
159    /// Consider providing a type specific version, when implementing
160    /// the CoordinateSet trait for a concrete data type: The default
161    /// version is straightforward, but not very efficient.
162    fn xy(&self, index: usize) -> (f64, f64) {
163        self.get_coord(index).xy()
164    }
165
166    /// Set all coordinate tuples in the set to NaN
167    fn stomp(&mut self) {
168        let nanny = Coor4D::nan();
169        for i in 0..self.len() {
170            self.set_coord(i, &nanny);
171        }
172    }
173}
174
175/// CoordinateTuple is the ISO-19111 atomic spatial/spatiotemporal
176/// referencing element. So loosely speaking, a CoordinateSet is a
177///  collection of CoordinateTuples.
178///
179/// Note that (despite the formal name) the underlying data structure
180/// need not be a tuple: It can be any item, for which it makes sense
181/// to implement the CoordinateTuple trait.
182///
183/// The CoordinateTuple trait provides a number of convenience accessors
184/// for accessing single coordinate elements or tuples of subsets.
185/// These accessors are pragmatically named (x, y, xy, etc.). While these
186/// names may be geodetically naïve, they are suggestive, practical, and
187/// aligns well with the internal coordinate order convention of most
188/// Geodesy operators.
189///
190/// All accessors have default implementations, except the 3 methods
191/// [`nth_unchecked()`](Self::nth_unchecked()),
192/// [`set_nth_unchecked()`](Self::set_nth_unchecked) and
193/// [`dim()`](Self::dim()),
194/// which must be provided by the implementer.
195///
196/// When accessing dimensions outside of the domain of the CoordinateTuple,
197/// [NaN](f64::NAN) will be returned.
198#[rustfmt::skip]
199pub trait CoordinateTuple {
200    /// Access the n'th (0-based) element of the CoordinateTuple.
201    /// May panic if n >= DIMENSION.
202    /// See also [`nth()`](Self::nth).
203    fn nth_unchecked(&self, n: usize) -> f64;
204
205    /// Replace the n'th (0-based) element of the `CoordinateTuple` with `value`.
206    /// May panic if `n >=` [`dim()`](Self::dim()).
207    /// See also [`set_nth()`](Self::set_nth).
208    fn set_nth_unchecked(&mut self, n: usize, value: f64);
209
210    /// Native dimension of the coordinate tuple
211    fn dim(&self) -> usize;
212
213    /// Access the n'th (0-based) element of the CoordinateTuple.
214    /// Returns NaN if `n >= DIMENSION`.
215    /// See also [`nth()`](Self::nth_unchecked).
216    fn nth(&self, n: usize) -> f64 {
217        if n < self.dim() { self.nth_unchecked(n) } else {f64::NAN}
218    }
219
220    // Note: We use nth_unchecked and explicitly check for dimension in
221    // y(), z() and t(), rather than leaving the check to nth(...).
222    // This is because the checks in these cases are constant expressions, and
223    // hence can be eliminated by the compiler in the concrete implementations.
224
225    /// Pragmatically named accessor for the first element of the CoordinateTuple.
226    fn x(&self) -> f64 {
227        self.nth_unchecked(0)
228    }
229
230    /// Pragmatically named accessor for the second element of the CoordinateTuple.
231    fn y(&self) -> f64 {
232        if self.dim() > 1 { self.nth_unchecked(1) } else {f64::NAN}
233    }
234
235    /// Pragmatically named accessor for the third element of the CoordinateTuple.
236    fn z(&self) -> f64 {
237        if self.dim() > 2 { self.nth_unchecked(2) } else {f64::NAN}
238    }
239
240    /// Pragmatically named accessor for the fourth element of the CoordinateTuple.
241    fn t(&self) -> f64 {
242        if self.dim() > 3 { self.nth_unchecked(3) } else {f64::NAN}
243    }
244
245    /// A tuple containing the first two components of the CoordinateTuple.
246    fn xy(&self) -> (f64, f64) {
247        (self.x(), self.y())
248    }
249
250    /// A tuple containing the first three components of the CoordinateTuple.
251    fn xyz(&self) -> (f64, f64, f64) {
252        (self.x(), self.y(), self.z())
253    }
254
255    /// A tuple containing the first four components of the CoordinateTuple.
256    fn xyzt(&self) -> (f64, f64, f64, f64) {
257        (self.x(), self.y(), self.z(), self.t())
258    }
259
260    /// A tuple containing the first two components of the CoordinateTuple
261    /// converted from radians to degrees
262    fn xy_to_degrees(&self) -> (f64, f64) {
263        (self.x().to_degrees(), self.y().to_degrees())
264    }
265
266    /// A tuple containing the first three components of the CoordinateTuple,
267    /// with the first two converted from radians to degrees.
268    fn xyz_to_degrees(&self) -> (f64, f64, f64) {
269        (self.x().to_degrees(), self.y().to_degrees(), self.z())
270    }
271
272    /// A tuple containing the first four components of the CoordinateTuple,
273    /// with the first two converted from radians to degrees.
274    fn xyzt_to_degrees(&self) -> (f64, f64, f64, f64) {
275        (self.x().to_degrees(), self.y().to_degrees(), self.z(), self.t())
276    }
277
278    /// A tuple containing the first two components of the CoordinateTuple,
279    /// converted from radians to seconds-of-arc
280    fn xy_to_arcsec(&self) -> (f64, f64) {
281        (self.x().to_degrees()*3600., self.y().to_degrees()*3600.)
282    }
283
284    /// A tuple containing the first three components of the CoordinateTuple,
285    /// with the first two converted to seconds-of-arc
286    fn xyz_to_arcsec(&self) -> (f64, f64, f64) {
287        (self.x().to_degrees()*3600., self.y().to_degrees()*3600., self.z())
288    }
289
290    /// A tuple containing the first four components of the CoordinateTuple,
291    /// with the first two converted to seconds-of-arc
292    fn xyzt_to_arcsec(&self) -> (f64, f64, f64, f64) {
293        (self.x().to_degrees()*3600., self.y().to_degrees()*3600., self.z(), self.t())
294    }
295
296    /// A tuple containing the first two components of the CoordinateTuple,
297    /// converted from degrees to radians
298    fn xy_to_radians(&self) -> (f64, f64) {
299        (self.x().to_radians(), self.y().to_radians())
300    }
301
302    /// A tuple containing the first three components of the CoordinateTuple,
303    /// with the first two converted from degrees to radians
304    fn xyz_to_radians(&self) -> (f64, f64, f64) {
305        (self.x().to_radians(), self.y().to_radians(), self.z())
306    }
307
308    /// A tuple containing the first four components of the CoordinateTuple,
309    /// with the first two converted from degrees to radians
310    fn xyzt_to_radians(&self) -> (f64, f64, f64, f64) {
311        (self.x().to_radians(), self.y().to_radians(), self.z(), self.t())
312    }
313
314    /// Fill all elements of `self` with `value`
315    fn fill(&mut self, value: f64) {
316        for n in 0..self.dim() {
317            self.set_nth_unchecked(n, value);
318        }
319    }
320
321    /// Replace the n'th (0-based) element of the `CoordinateTuple` with `value`.
322    /// If `n >=` [`dim()`](Self::dim()) fill the coordinate with `f64::NAN`.
323    /// See also [`set_nth_unchecked()`](Self::set_nth_unchecked).
324    fn set_nth(&mut self, n: usize, value: f64) {
325        if n < self.dim() {
326            self.set_nth_unchecked(n, value)
327        } else {
328            self.fill(f64::NAN);
329        }
330    }
331
332    /// Replace the two first elements of the `CoordinateTuple` with `x` and `y`.
333    /// If the dimension is less than 2, fill the coordinate with `f64::NAN`.
334    /// See also [`set_nth_unchecked()`](Self::set_nth_unchecked).
335    fn set_xy(&mut self, x: f64, y: f64) {
336        if self.dim() > 1 {
337            self.set_nth_unchecked(0, x);
338            self.set_nth_unchecked(1, y);
339        } else {
340            self.fill(f64::NAN);
341        }
342    }
343
344    /// Replace the three first elements of the `CoordinateTuple` with `x`, `y` and `z`.
345    /// If the dimension is less than 3, fill the coordinate with `f64::NAN`.
346    fn set_xyz(&mut self, x: f64, y: f64, z: f64) {
347        if self.dim() > 2 {
348            self.set_nth_unchecked(0, x);
349            self.set_nth_unchecked(1, y);
350            self.set_nth_unchecked(2, z);
351        } else {
352            self.fill(f64::NAN);
353        }
354    }
355
356    /// Replace the four first elements of the `CoordinateTuple` with `x`, `y` `z` and `t`.
357    /// If the dimension is less than 4, fill the coordinate with `f64::NAN`.
358    fn set_xyzt(&mut self, x: f64, y: f64, z: f64, t: f64) {
359        if self.dim() > 3 {
360            self.set_nth_unchecked(0, x);
361            self.set_nth_unchecked(1, y);
362            self.set_nth_unchecked(2, z);
363            self.set_nth_unchecked(3, t);
364        } else {
365            self.fill(f64::NAN);
366        }
367    }
368
369    /// Replace the `N` first (up to [`dim()`](Self::dim())) elements of `self` with the
370    /// elements of `value`
371    #[allow(clippy::needless_range_loop)]
372    fn update(&mut self, value: &[f64]) {
373        let n = value.len().min(self.dim());
374        for i in 0..n {
375            self.set_nth_unchecked(i, value[i])
376        }
377    }
378
379    /// Euclidean distance between two points in the 2D plane.
380    ///
381    /// Primarily used to compute the distance between two projected points
382    /// in their projected plane. Typically, this distance will differ from
383    /// the actual distance in the real world.
384    ///
385    /// # See also:
386    ///
387    /// [`hypot3`](Self::hypot3),
388    /// [`distance`](crate::ellipsoid::Ellipsoid::distance)
389    ///
390    /// # Examples
391    ///
392    /// ```
393    /// use geodesy::prelude::*;
394    /// let t = 1000 as f64;
395    /// let p0 = Coor2D::origin();
396    /// let p1 = Coor2D::raw(t, t);
397    /// assert_eq!(p0.hypot2(&p1), t.hypot(t));
398    /// ```
399    #[must_use]
400    fn hypot2(&self, other: &Self) -> f64
401    where Self: Sized {
402        let (u, v) = self.xy();
403        let (x, y) = other.xy();
404        (u - x).hypot(v - y)
405    }
406
407    /// Euclidean distance between two points in the 3D space.
408    ///
409    /// Primarily used to compute the distance between two points in the
410    /// 3D cartesian space. The typical case is GNSS-observations, in which
411    /// case, the distance computed will reflect the actual distance
412    /// in the real world.
413    ///
414    /// The distance is computed in the subspace spanned by the first,
415    /// second and third coordinate of the `Coor3D`s
416    ///
417    /// # See also:
418    ///
419    /// [`hypot2()`](Self::hypot2),
420    /// [`distance`](crate::ellipsoid::Ellipsoid::distance)
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// use geodesy::prelude::*;
426    /// let t = 1000 as f64;
427    /// let p0 = Coor3D::origin();
428    /// let p1 = Coor3D::raw(t, t, t);
429    /// assert_eq!(p0.hypot3(&p1), t.hypot(t).hypot(t));
430    /// ```
431    #[must_use]
432    fn hypot3(&self, other: &Self) -> f64
433    where Self: Sized {
434        if self.dim() < 3 {
435            return f64::NAN;
436        }
437        let (u, v, w) = self.xyz();
438        let (x, y, z) = other.xyz();
439        (u - x).hypot(v - y).hypot(w - z)
440    }
441
442
443    fn scale(&self, factor: f64) -> Self
444    where Self: Sized+Copy {
445        let mut res = *self;
446        for i in 0..self.dim() {
447            res.set_nth(i, self.nth(i) * factor);
448        }
449        res
450    }
451
452    fn dot(&self, other: Self) -> f64
453    where Self: Sized {
454        let mut res = 0.;
455        for i in 0..self.dim() {
456            res += self.nth(i) * other.nth(i);
457        }
458        res
459    }
460
461}
462
463// CoordinateTuple implementations for the Geodesy data types,
464// Coor2D, Coor32, Coor3D, Coor4D
465
466impl CoordinateTuple for Coor2D {
467    fn dim(&self) -> usize {
468        2
469    }
470
471    fn nth_unchecked(&self, n: usize) -> f64 {
472        self.0[n]
473    }
474
475    fn set_nth_unchecked(&mut self, n: usize, value: f64) {
476        self.0[n] = value;
477    }
478}
479
480impl CoordinateTuple for Coor3D {
481    fn dim(&self) -> usize {
482        3
483    }
484
485    fn nth_unchecked(&self, n: usize) -> f64 {
486        self.0[n]
487    }
488
489    fn set_nth_unchecked(&mut self, n: usize, value: f64) {
490        self.0[n] = value;
491    }
492}
493
494impl CoordinateTuple for Coor4D {
495    fn dim(&self) -> usize {
496        4
497    }
498
499    fn nth_unchecked(&self, n: usize) -> f64 {
500        self.0[n]
501    }
502
503    fn set_nth_unchecked(&mut self, n: usize, value: f64) {
504        self.0[n] = value;
505    }
506}
507
508impl CoordinateTuple for Coor32 {
509    fn dim(&self) -> usize {
510        2
511    }
512
513    fn nth_unchecked(&self, n: usize) -> f64 {
514        self.0[n] as f64
515    }
516
517    fn set_nth_unchecked(&mut self, n: usize, value: f64) {
518        self.0[n] = value as f32;
519    }
520}
521
522// And let's also implement it for a plain 2D f64 tuple
523
524#[rustfmt::skip]
525impl CoordinateTuple for (f64, f64) {
526    fn dim(&self) -> usize { 2 }
527
528    fn nth_unchecked(&self, n: usize) -> f64 {
529        match n {
530            0 => self.0,
531            1 => self.1,
532            _ => panic!()
533        }
534    }
535
536    fn set_nth_unchecked(&mut self, n: usize, value: f64) {
537        match n {
538            0 => self.0 = value,
539            1 => self.1 = value,
540            _ => ()
541        }
542    }
543}