Skip to content

Commit cf94cf6

Browse files
committed
Trait based refactor of Ellipsoid
The Ellipsoid impl was huge and unmaintainable. Now it has been split into an EllipsoidBase trait, defining the fundamental shape and size parameters, and a number of more specialized traits for latitudes, meridian-geometry, geodesics, cartesians (geocart), and gravity. Also lib.rs was cleaned up to reflect the new structure, and the prelude has been rebuilt using thematical modules: coord, ctx, and ellps, representing coordinate, context, and ellipsoidal material, respectively. Likewise, the "authoring" extended prelude has been restructured through the introduction of the grd, ops, and parse modules. Finally, a number of documentation improvements were introduced while refactoring the existing material to reflect the new structure. For usage based on the "use geodesy::prelude::*" idiom, no user visible changes are expected.
1 parent 7f1ec6a commit cf94cf6

21 files changed

+501
-358
lines changed

CHANGELOG.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- CoordinateSet: xyz(), set_xyz(), xyzt(), set_xyzt() methods
12+
- `CoordinateSet`: xyz(), set_xyz(), xyzt(), set_xyzt() methods
1313
- Vector space operators (Add, Sub, Mul, Div) for all built
1414
in coordinate tuple types (Coor4D, Coor3D, Coor2D, Coor32)
15+
- `TriaxialEllpisoid`, mostly as a placeholder
1516

1617
### Changed
1718

1819
- `CoordinateTuple` trait now requires implementation of the constructor
1920
method `new(fill: f64)`, returning an object of `dim()` copies of `fill`.
21+
- The huge `Ellipsoid`-implementation switched to a new trait `EllipsoidBase`,
22+
and a number of associated traits requiring `EllipsoidBase`.
23+
- Meaning of `EllipsoidBase::aspect_ratio()` switched from *b / a* to the
24+
apparently more common *a / b*
25+
- Major restructuring and clean up of `lib.rs`. Only marignally visible externally,
26+
if using `use geodesy::prelude::*`
2027

2128
### Removed
2229

examples/03-user_defined_operators.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub fn add42_constructor(parameters: &RawParameters, ctx: &dyn Context) -> Resul
5151
}
5252

5353
fn main() -> anyhow::Result<()> {
54-
let mut prv = geodesy::Minimal::new();
54+
let mut prv = geodesy::prelude::Minimal::new();
5555
prv.register_op("add42", OpConstructor(add42_constructor));
5656
let add42 = prv.op("add42")?;
5757

examples/05-pq.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fn main() -> Result<(), anyhow::Error> {
6666

6767
// We use ::new() instead of ::default() in order to gain access to the
6868
// BUILTIN_ADAPTORS
69-
let mut ctx = geodesy::Minimal::new();
69+
let mut ctx = geodesy::prelude::Minimal::new();
7070
trace!("trace message 2");
7171
debug!("debug message 2");
7272

examples/06-user_defined_coordinate_types_and_containers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fn main() -> Result<(), anyhow::Error> {
8787

8888
// We use ::new() instead of ::default() in order to gain access to the
8989
// BUILTIN_ADAPTORS (geo:in, geo:out etc.)
90-
let mut ctx = geodesy::Minimal::new();
90+
let mut ctx = geodesy::prelude::Minimal::new();
9191
trace!("have context");
9292

9393
let copenhagen = Coor4D([55., 12., 0., 0.]);

examples/08-user_defined_operators_using_proj.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ pub fn proj_constructor(parameters: &RawParameters, _ctx: &dyn Context) -> Resul
171171
}
172172

173173
fn main() -> anyhow::Result<()> {
174-
let mut prv = geodesy::Minimal::new();
174+
let mut prv = geodesy::prelude::Minimal::new();
175175
prv.register_op("proj", OpConstructor(proj_constructor));
176176
let e = Ellipsoid::default();
177177

src/coordinate/coor32.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::*;
22

33
/// Generic 2D Coordinate tuple, with no fixed interpretation of the elements.
4-
/// A tiny coordinate type: Just one fourth the weight of a [`Coor4D`](crate::Coor4D).
4+
/// A tiny coordinate type: Just one fourth the weight of a [`Coor4D`](super::Coor4D).
55
/// Probably only useful for small scale world maps, without too much zoom.
66
#[derive(Debug, Default, PartialEq, Copy, Clone)]
77
pub struct Coor32(pub [f32; 2]);

src/coordinate/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ where
6363
}
6464

6565
/// For Rust Geodesy, the ISO-19111 concept of `DirectPosition` is represented
66-
/// as a `geodesy::Coo4D`.
66+
/// as a `geodesy::Coor4D`.
6767
///
6868
/// The strict connection between the ISO19107 "DirectPosition" datatype
6969
/// and the ISO19111/OGC Topic 2 "CoordinateSet" interface (i.e. trait)

src/coordinate/tuple.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ all_coord_operators!(Coor32, Coor32, coor32);
109109

110110
/// CoordinateTuple is the ISO-19111 atomic spatial/spatiotemporal
111111
/// referencing element. So loosely speaking, a CoordinateSet is a
112-
/// collection of CoordinateTuples.
112+
/// collection of CoordinateTuples.
113113
///
114114
/// Note that (despite the formal name) the underlying data structure
115115
/// need not be a tuple: It can be any item, for which it makes sense
@@ -357,7 +357,7 @@ pub trait CoordinateTuple {
357357
/// # See also:
358358
///
359359
/// [`hypot3`](Self::hypot3),
360-
/// [`distance`](crate::ellipsoid::Ellipsoid::distance)
360+
/// [`distance`](crate::ellps::Geodesics::distance)
361361
///
362362
/// # Examples
363363
///
@@ -391,7 +391,7 @@ pub trait CoordinateTuple {
391391
/// # See also:
392392
///
393393
/// [`hypot2()`](Self::hypot2),
394-
/// [`distance`](crate::ellipsoid::Ellipsoid::distance)
394+
/// [`distance`](crate::ellps::Geodesics::distance)
395395
///
396396
/// # Examples
397397
///

src/ellipsoid/biaxial.rs

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use crate::prelude::*;
2+
3+
/// An ellipsoid of revolution.
4+
#[derive(Clone, Copy, Debug, PartialEq)]
5+
pub struct Ellipsoid {
6+
a: f64,
7+
f: f64,
8+
}
9+
10+
/// GRS80 is the default ellipsoid.
11+
impl Default for Ellipsoid {
12+
fn default() -> Ellipsoid {
13+
Ellipsoid::new(6_378_137.0, 1. / 298.257_222_100_882_7)
14+
}
15+
}
16+
17+
impl EllipsoidBase for Ellipsoid {
18+
fn semimajor_axis(&self) -> f64 {
19+
self.a
20+
}
21+
22+
fn flattening(&self) -> f64 {
23+
self.f
24+
}
25+
}
26+
27+
/// Constructors for `Ellipsoid`
28+
impl Ellipsoid {
29+
/// User defined ellipsoid
30+
#[must_use]
31+
pub fn new(semimajor_axis: f64, flattening: f64) -> Ellipsoid {
32+
Ellipsoid {
33+
a: semimajor_axis,
34+
f: flattening,
35+
}
36+
}
37+
38+
/// Predefined ellipsoid; built-in, defined in asset collections, or given as a
39+
/// string formatted (a, rf) tuple, e.g. "6378137, 298.25"
40+
pub fn named(name: &str) -> Result<Ellipsoid, Error> {
41+
// Is it one of the few builtins?
42+
if let Some(index) = super::constants::ELLIPSOID_LIST
43+
.iter()
44+
.position(|&ellps| ellps.0 == name)
45+
{
46+
let e = super::constants::ELLIPSOID_LIST[index];
47+
let ax: f64 = e.1.parse().unwrap();
48+
let rf: f64 = e.3.parse().unwrap();
49+
// EPSG convention: zero reciproque flattening indicates zero flattening
50+
let f = if rf != 0.0 { 1.0 / rf } else { rf };
51+
return Ok(Ellipsoid::new(ax, f));
52+
}
53+
54+
// The "semimajor, reciproque-flattening" form, e.g. "6378137, 298.3"
55+
let a_and_rf = name.split(',').collect::<Vec<_>>();
56+
if a_and_rf.len() == 2_usize {
57+
if let Ok(a) = a_and_rf[0].trim().parse::<f64>() {
58+
if let Ok(rf) = a_and_rf[1].trim().parse::<f64>() {
59+
return Ok(Ellipsoid::new(a, 1. / rf));
60+
}
61+
}
62+
}
63+
64+
// TODO: Search asset collection
65+
Err(Error::NotFound(
66+
String::from(name),
67+
String::from("Ellipsoid::named()"),
68+
))
69+
}
70+
}
71+
72+
// ----- Tests ---------------------------------------------------------------------
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use crate::ellps::Ellipsoid;
77+
use crate::ellps::EllipsoidBase;
78+
use crate::ellps::Meridians;
79+
use crate::Error;
80+
81+
#[test]
82+
fn test_ellipsoid() -> Result<(), Error> {
83+
// Constructors
84+
let ellps = Ellipsoid::named("intl")?;
85+
assert_eq!(ellps.flattening(), 1. / 297.);
86+
87+
let ellps = Ellipsoid::named("6378137, 298.25")?;
88+
assert_eq!(ellps.semimajor_axis(), 6378137.0);
89+
assert_eq!(ellps.flattening(), 1. / 298.25);
90+
91+
let ellps = Ellipsoid::named("GRS80")?;
92+
assert_eq!(ellps.semimajor_axis(), 6378137.0);
93+
assert_eq!(ellps.flattening(), 1. / 298.257_222_100_882_7);
94+
95+
assert!((ellps.normalized_meridian_arc_unit() - 0.998_324_298_423_041_5).abs() < 1e-13);
96+
assert!((4.0 * ellps.meridian_quadrant() - 40_007_862.916_921_8).abs() < 1e-7);
97+
Ok(())
98+
}
99+
}

src/ellipsoid/cartesians.rs src/ellipsoid/geocart.rs

+15-19
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ use super::*;
22

33
use std::f64::consts::FRAC_PI_2;
44

5-
impl Ellipsoid {
6-
// ----- Cartesian <--> Geographic conversion ----------------------------------
7-
5+
/// Geographic <--> Cartesian conversion
6+
pub trait GeoCart: EllipsoidBase {
87
/// Geographic to cartesian conversion.
98
///
109
/// Follows the the derivation given by
@@ -13,11 +12,8 @@ impl Ellipsoid {
1312
#[must_use]
1413
#[allow(non_snake_case)] // make it possible to mimic math notation from original paper
1514
#[allow(clippy::many_single_char_names)] // ditto
16-
pub fn cartesian(&self, geographic: &Coor4D) -> Coor4D {
17-
let lam = geographic[0];
18-
let phi = geographic[1];
19-
let h = geographic[2];
20-
let t = geographic[3];
15+
fn cartesian<C: CoordinateTuple>(&self, geographic: &C) -> Coor4D {
16+
let (lam, phi, h, t) = geographic.xyzt();
2117

2218
let N = self.prime_vertical_radius_of_curvature(phi);
2319
let cosphi = phi.cos();
@@ -32,26 +28,23 @@ impl Ellipsoid {
3228
Coor4D::raw(X, Y, Z, t)
3329
}
3430

35-
/// Cartesian to geogaphic conversion.
31+
/// Cartesian to geographic conversion.
3632
///
3733
/// Follows the the derivation given by
3834
/// Bowring ([1976](crate::Bibliography::Bow76) and
3935
/// [1985](crate::Bibliography::Bow85))
4036
#[must_use]
4137
#[allow(non_snake_case)] // make it possible to mimic math notation from original paper
4238
#[allow(clippy::many_single_char_names)] // ditto
43-
pub fn geographic(&self, cartesian: &Coor4D) -> Coor4D {
44-
let X = cartesian[0];
45-
let Y = cartesian[1];
46-
let Z = cartesian[2];
47-
let t = cartesian[3];
39+
fn geographic<C: CoordinateTuple>(&self, cartesian: &C) -> Coor4D {
40+
let (X, Y, Z, t) = cartesian.xyzt();
4841

4942
// We need a few additional ellipsoidal parameters
5043
let b = self.semiminor_axis();
5144
let eps = self.second_eccentricity_squared();
5245
let es = self.eccentricity_squared();
5346

54-
// The longitude is straightforward
47+
// The longitude is straightforward: Plain geometry in the equatoreal plane
5548
let lam = Y.atan2(X);
5649

5750
// The perpendicular distance from the point coordinate to the Z-axis
@@ -79,26 +72,29 @@ impl Ellipsoid {
7972
// Fukushima (1999), Appendix B: Equivalent to Even Rouault's, implementation,
8073
// but not as clear - although a bit faster due to the substitution of sqrt
8174
// for hypot.
82-
let T = (Z * self.a) / (p * b);
75+
let a = self.semimajor_axis();
76+
let T = (Z * a) / (p * b);
8377
let c = 1.0 / (1.0 + T * T).sqrt();
8478
let s = c * T;
8579

8680
let phi_num = Z + eps * b * s.powi(3);
87-
let phi_denom = p - es * self.a * c.powi(3);
81+
let phi_denom = p - es * a * c.powi(3);
8882
let phi = phi_num.atan2(phi_denom);
8983

9084
let lenphi = phi_num.hypot(phi_denom);
9185
let sinphi = phi_num / lenphi;
9286
let cosphi = phi_denom / lenphi;
9387

88+
let a = self.semimajor_axis();
89+
9490
// We already have sinphi and es, so we can compute the radius
9591
// of curvature faster by inlining, rather than calling the
9692
// prime_vertical_radius_of_curvature() method.
97-
let N = self.a / (1.0 - sinphi.powi(2) * es).sqrt();
93+
let N = a / (1.0 - sinphi.powi(2) * es).sqrt();
9894

9995
// Bowring (1985), as quoted by Burtch (2006), suggests this expression
10096
// as more accurate than the commonly used h = p / cosphi - N;
101-
let h = p * cosphi + Z * sinphi - self.a * self.a / N;
97+
let h = p * cosphi + Z * sinphi - a * a / N;
10298

10399
Coor4D::raw(lam, phi, h, t)
104100
}

0 commit comments

Comments
 (0)