Skip to main content

geodesy/inner_op/
cart.rs

1/// Geographical to cartesian (and v.v.) conversion
2use crate::authoring::*;
3
4// ----- F O R W A R D --------------------------------------------------------------
5
6fn cart_fwd(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
7    let n = operands.len();
8    let mut successes = 0;
9    let ellps = op.params.ellps(0);
10    for i in 0..n {
11        let mut coord = operands.get_coord(i);
12        coord = ellps.cartesian(&coord);
13        if !coord.0.iter().any(|c| c.is_nan()) {
14            successes += 1;
15        }
16        operands.set_coord(i, &coord);
17    }
18    successes
19}
20
21// ----- I N V E R S E --------------------------------------------------------------
22
23fn cart_inv(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
24    let ellps = op.params.ellps(0);
25
26    // eccentricity squared, Fukushima's E, Claessens' c3 = 1-c2`
27    let es = ellps.eccentricity_squared();
28
29    let b = ellps.semiminor_axis();
30    let a = ellps.semimajor_axis();
31    let ra = 1. / ellps.semimajor_axis();
32
33    // b/a: Fukushima's ec, Claessens' c4
34    let ar = b * ra;
35    // 1.5 times the fourth power of the eccentricity
36    let ce4 = 1.5 * es * es;
37    // if we're closer than this to the Z axis, we force latitude to one of the poles
38    let cutoff = ellps.semimajor_axis() * 1e-16;
39
40    let n = operands.len();
41    let mut successes = 0;
42    #[allow(non_snake_case)]
43    for i in 0..n {
44        let mut coord = operands.get_coord(i);
45        let X = coord[0];
46        let Y = coord[1];
47        let Z = coord[2];
48        let t = coord[3];
49
50        // The longitude is straightforward
51        let lam = Y.atan2(X);
52
53        // The perpendicular distance from the point coordinate to the Z-axis (HM eq. 5-28)
54        let p = X.hypot(Y);
55
56        // If we're close to the Z-axis, the full algorithm breaks down. But if
57        // we're close to the Z-axis, we also assert that the latitude is close
58        // to one of the poles. So we force the latitude to the relevant pole and
59        // compute the height as |Z| - b
60        if p < cutoff {
61            let phi = std::f64::consts::FRAC_PI_2.copysign(Z);
62            let h = Z.abs() - b;
63            coord = Coor4D::raw(lam, phi, h, t);
64            operands.set_coord(i, &coord);
65            continue;
66        }
67
68        let P = ra * p;
69        let S0 = ra * Z;
70        let C0 = ar * P;
71
72        // There's a lot of common subexpressions in the following which,
73        // in Fukushima's and Claessens' Fortranesque implementations,
74        // were explicitly eliminated (by introducing s02 = S0*S0, etc.).
75        // For clarity, we keep the full expressions here, and leave the
76        // elimination task to the compiler's optimizer step.
77        let A = S0.hypot(C0);
78        let F = P * A * A * A - es * C0 * C0 * C0;
79        let B = ce4 * S0 * S0 * C0 * C0 * P * (A - ar);
80
81        let S1 = (ar * S0 * A * A * A + es * S0 * S0 * S0) * F - B * S0;
82        let C1 = F * F - B * C0;
83        let CC = ar * C1;
84
85        let phi = S1.atan2(CC);
86        let h = (p * CC.abs() + Z.abs() * S1.abs() - a * CC.hypot(ar * S1)) / CC.hypot(S1);
87        // Bowring's height formula works better close to the ellipsoid, but requires a (sin, cos)-pair
88        coord = Coor4D::raw(lam, phi, h, t);
89        operands.set_coord(i, &coord);
90
91        if ![lam, phi, h, t].iter().any(|c| c.is_nan()) {
92            successes += 1;
93        }
94    }
95    successes
96}
97
98// ----- C O N S T R U C T O R ------------------------------------------------------
99
100#[rustfmt::skip]
101pub const GAMUT: [OpParameter; 2] = [
102    OpParameter::Flag { key: "inv" },
103    OpParameter::Text { key: "ellps", default: Some("GRS80") },
104];
105
106pub fn new(parameters: &RawParameters, ctx: &dyn Context) -> Result<Op, Error> {
107    Op::plain(
108        parameters,
109        InnerOp(cart_fwd),
110        Some(InnerOp(cart_inv)),
111        &GAMUT,
112        ctx,
113    )
114}
115
116// ----- T E S T S ------------------------------------------------------------------
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn roundtrip() -> Result<(), Error> {
124        let mut ctx = Minimal::default();
125        let op = ctx.op("cart")?;
126
127        let geo = [
128            Coor4D::geo(85., 0., 100000., 0.),
129            Coor4D::geo(55., 10., -100000., 0.),
130            Coor4D::geo(25., 20., 0., 0.),
131            Coor4D::geo(0., -20., 0., 0.),
132            Coor4D::geo(-25., 20., 10., 0.),
133            Coor4D::geo(-25., -20., 10., 0.),
134            Coor4D::geo(25., -20., 10., 0.),
135        ];
136
137        let cart = [
138            Coor4D::raw(566_462.633_537_476_8, 0.0, 6_432_020.333_690_127, 0.0),
139            Coor4D::raw(
140                3_554_403.475_871_930_4,
141                626_737.233_120_170_7,
142                5_119_468.318_659_256,
143                0.,
144            ),
145            Coor4D::raw(
146                5_435_195.382_145_216,
147                1_978_249.336_521_975_5,
148                2_679_074.462_877_277_8,
149                0.,
150            ),
151            Coor4D::raw(5_993_488.273_261_571, -2_181_451.330_890_750_5, 0., 0.),
152            Coor4D::raw(
153                5_435_203.898_652_612,
154                1_978_252.436_277_167_4,
155                -2_679_078.689_059_895,
156                0.,
157            ),
158            Coor4D::raw(
159                5_435_203.898_652_612,
160                -1_978_252.436_277_167_4,
161                -2_679_078.689_059_895,
162                0.,
163            ),
164            Coor4D::raw(
165                5_435_203.898_652_612,
166                -1_978_252.436_277_167_4,
167                2_679_078.689_059_895,
168                0.,
169            ),
170        ];
171
172        let e = Ellipsoid::default();
173        // Forward
174        let mut operands = geo;
175        ctx.apply(op, Fwd, &mut operands)?;
176        for i in 0..4 {
177            assert!(operands[i].hypot3(&cart[i]) < 20e-9);
178        }
179
180        // Inverse
181        ctx.apply(op, Inv, &mut operands)?;
182        for i in 0..5 {
183            assert!(e.distance(&operands[i], &geo[i]) < 1e-8);
184        }
185
186        Ok(())
187    }
188}