1use crate::proj::{CoordinateStep, Proj, ProjectCoordinates, TransformCoordinates};
2use alloc::rc::Rc;
3use core::{
4 cell::RefCell,
5 f64::consts::{FRAC_PI_2, PI},
6};
7use libm::{fabs, floor, round};
8
9fn v(c: &RobinCoefs, z: f64) -> f64 {
10 c.c0 + z * (c.c1 + z * (c.c2 + z * c.c3))
11}
12fn dv(c: &RobinCoefs, z: f64) -> f64 {
13 c.c1 + 2.0 * z * c.c2 + z * z * 3.0 * c.c3
14}
15
16#[derive(Debug, Default, Clone, PartialEq)]
25pub struct RobinCoefs {
26 pub c0: f64,
28 pub c1: f64,
30 pub c2: f64,
32 pub c3: f64,
34}
35const X_COEFS: [RobinCoefs; 19] = [
36 RobinCoefs { c0: 1.0, c1: 2.2199e-17, c2: -7.15515e-05, c3: 3.1103e-06 },
37 RobinCoefs { c0: 0.9986, c1: -0.000482243, c2: -2.4897e-05, c3: -1.3309e-06 },
38 RobinCoefs { c0: 0.9954, c1: -0.00083103, c2: -4.48605e-05, c3: -9.86701e-07 },
39 RobinCoefs { c0: 0.99, c1: -0.00135364, c2: -5.9661e-05, c3: 3.6777e-06 },
40 RobinCoefs { c0: 0.9822, c1: -0.00167442, c2: -4.49547e-06, c3: -5.72411e-06 },
41 RobinCoefs { c0: 0.973, c1: -0.00214868, c2: -9.03571e-05, c3: 1.8736e-08 },
42 RobinCoefs { c0: 0.96, c1: -0.00305085, c2: -9.00761e-05, c3: 1.64917e-06 },
43 RobinCoefs { c0: 0.9427, c1: -0.00382792, c2: -6.53386e-05, c3: -2.6154e-06 },
44 RobinCoefs { c0: 0.9216, c1: -0.00467746, c2: -0.00010457, c3: 4.81243e-06 },
45 RobinCoefs { c0: 0.8962, c1: -0.00536223, c2: -3.23831e-05, c3: -5.43432e-06 },
46 RobinCoefs { c0: 0.8679, c1: -0.00609363, c2: -0.000113898, c3: 3.32484e-06 },
47 RobinCoefs { c0: 0.835, c1: -0.00698325, c2: -6.40253e-05, c3: 9.34959e-07 },
48 RobinCoefs { c0: 0.7986, c1: -0.00755338, c2: -5.00009e-05, c3: 9.35324e-07 },
49 RobinCoefs { c0: 0.7597, c1: -0.00798324, c2: -3.5971e-05, c3: -2.27626e-06 },
50 RobinCoefs { c0: 0.7186, c1: -0.00851367, c2: -7.01149e-05, c3: -8.6303e-06 },
51 RobinCoefs { c0: 0.6732, c1: -0.00986209, c2: -0.000199569, c3: 1.91974e-05 },
52 RobinCoefs { c0: 0.6213, c1: -0.010418, c2: 8.83923e-05, c3: 6.24051e-06 },
53 RobinCoefs { c0: 0.5722, c1: -0.00906601, c2: 0.000182, c3: 6.24051e-06 },
54 RobinCoefs { c0: 0.5322, c1: -0.00677797, c2: 0.000275608, c3: 6.24051e-06 },
55];
56
57const Y_COEFS: [RobinCoefs; 19] = [
58 RobinCoefs { c0: -5.20417e-18, c1: 0.0124, c2: 1.21431e-18, c3: -8.45284e-11 },
59 RobinCoefs { c0: 0.062, c1: 0.0124, c2: -1.26793e-09, c3: 4.22642e-10 },
60 RobinCoefs { c0: 0.124, c1: 0.0124, c2: 5.07171e-09, c3: -1.60604e-09 },
61 RobinCoefs { c0: 0.186, c1: 0.0123999, c2: -1.90189e-08, c3: 6.00152e-09 },
62 RobinCoefs { c0: 0.248, c1: 0.0124002, c2: 7.10039e-08, c3: -2.24e-08 },
63 RobinCoefs { c0: 0.31, c1: 0.0123992, c2: -2.64997e-07, c3: 8.35986e-08 },
64 RobinCoefs { c0: 0.372, c1: 0.0124029, c2: 9.88983e-07, c3: -3.11994e-07 },
65 RobinCoefs { c0: 0.434, c1: 0.0123893, c2: -3.69093e-06, c3: -4.35621e-07 },
66 RobinCoefs { c0: 0.4958, c1: 0.0123198, c2: -1.02252e-05, c3: -3.45523e-07 },
67 RobinCoefs { c0: 0.5571, c1: 0.0121916, c2: -1.54081e-05, c3: -5.82288e-07 },
68 RobinCoefs { c0: 0.6176, c1: 0.0119938, c2: -2.41424e-05, c3: -5.25327e-07 },
69 RobinCoefs { c0: 0.6769, c1: 0.011713, c2: -3.20223e-05, c3: -5.16405e-07 },
70 RobinCoefs { c0: 0.7346, c1: 0.0113541, c2: -3.97684e-05, c3: -6.09052e-07 },
71 RobinCoefs { c0: 0.7903, c1: 0.0109107, c2: -4.89042e-05, c3: -1.04739e-06 },
72 RobinCoefs { c0: 0.8435, c1: 0.0103431, c2: -6.4615e-05, c3: -1.40374e-09 },
73 RobinCoefs { c0: 0.8936, c1: 0.00969686, c2: -6.4636e-05, c3: -8.547e-06 },
74 RobinCoefs { c0: 0.9394, c1: 0.00840947, c2: -0.000192841, c3: -4.2106e-06 },
75 RobinCoefs { c0: 0.9761, c1: 0.00616527, c2: -0.000256, c3: -4.2106e-06 },
76 RobinCoefs { c0: 1.0, c1: 0.00328947, c2: -0.000319159, c3: -4.2106e-06 },
77];
78
79const FXC: f64 = 0.8487;
80const FYC: f64 = 1.3523;
81const C1: f64 = 11.459_155_902_616_464;
82const RC1: f64 = 0.087_266_462_599_716_47;
83const NODES: usize = 18;
84const ONEEPS: f64 = 1.000001;
85const EPS: f64 = 1e-10;
86const MAX_ITER: usize = 100;
88
89#[derive(Debug, Clone, PartialEq)]
121pub struct RobinsonProjection {
122 proj: Rc<RefCell<Proj>>,
123}
124impl ProjectCoordinates for RobinsonProjection {
125 fn code(&self) -> i64 {
126 -1
127 }
128 fn name(&self) -> &'static str {
129 "Robinson"
130 }
131 fn names() -> &'static [&'static str] {
132 &["Robinson", "robin"]
133 }
134}
135impl CoordinateStep for RobinsonProjection {
136 fn new(proj: Rc<RefCell<Proj>>) -> Self {
137 proj.borrow_mut().es = 0.;
138 RobinsonProjection { proj }
139 }
140 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
141 robin_s_forward(p);
142 }
143 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
144 robin_s_inverse(p);
145 }
146}
147
148pub fn robin_s_forward<P: TransformCoordinates>(p: &mut P) {
150 let mut dphi = fabs(p.phi());
151 let mut i = if f64::is_nan(p.phi()) { -1. } else { round(floor(dphi * C1 + 1e-15)) };
152 if i < 0. {
153 panic!("Coordinate outside projection domain");
154 }
155 if i >= NODES as f64 {
156 i = NODES as f64;
157 }
158 dphi = (dphi - RC1 * i).to_degrees();
159 let x = v(&X_COEFS[i as usize], dphi) * FXC * p.lam();
160 let mut y = v(&Y_COEFS[i as usize], dphi) * FYC;
161 if p.phi() < 0. {
162 y = -y;
163 }
164 p.set_x(x);
165 p.set_y(y);
166}
167
168pub fn robin_s_inverse<P: TransformCoordinates>(p: &mut P) {
170 let mut lam = p.x() / FXC;
171 let mut phi = fabs(p.y() / FYC);
172 if phi >= 1. {
173 if phi > ONEEPS {
175 panic!("Coordinate outside projection domain");
176 } else {
177 phi = if p.y() < 0. { -FRAC_PI_2 } else { FRAC_PI_2 };
178 lam /= X_COEFS[NODES].c0;
179 }
180 } else {
181 let i = if f64::is_nan(phi) { -1. } else { round(floor(phi * NODES as f64)) };
184 if i < 0. || i >= NODES as f64 {
185 panic!("Coordinate outside projection domain");
186 }
187 let mut i = i as usize;
188 loop {
189 if Y_COEFS[i].c0 > phi {
190 i -= 1;
191 } else if Y_COEFS[i + 1].c0 <= phi {
192 i += 1;
193 } else {
194 break;
195 }
196 }
197 let t_coef = &Y_COEFS[i];
198 let mut t = 5. * (phi - t_coef.c0) / (Y_COEFS[i + 1].c0 - t_coef.c0);
200 let mut iters = MAX_ITER;
201 while iters > 0 {
202 let t1 = (v(t_coef, t) - phi) / dv(t_coef, t);
204 t -= t1;
205 if fabs(t1) < EPS {
206 break;
207 }
208 iters -= 1;
209 }
210 if iters == 0 {
211 panic!("Coordinate outside projection domain");
212 }
213 phi = (5. * (i as f64) + t).to_radians();
214 if p.y() < 0. {
215 phi = -phi;
216 }
217 lam /= v(&X_COEFS[i], t);
218 if fabs(lam) > PI {
219 panic!("Coordinate outside projection domain");
220 }
221 }
222 p.set_phi(phi);
223 p.set_lam(lam);
224}