1use crate::authoring::*;
3
4fn 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
21fn cart_inv(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
24 let ellps = op.params.ellps(0);
25
26 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 let ar = b * ra;
35 let ce4 = 1.5 * es * es;
37 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 let lam = Y.atan2(X);
52
53 let p = X.hypot(Y);
55
56 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 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 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#[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#[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 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 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}