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}