gistools/proj/project/
vandg.rs

1use crate::proj::{CoordinateStep, Proj, ProjectCoordinates, TransformCoordinates};
2use alloc::rc::Rc;
3use core::{
4    cell::RefCell,
5    f64::consts::{FRAC_PI_2, PI, TAU},
6};
7use libm::{acos, asin, cos, fabs, sqrt, tan};
8
9// Changes to handle +over are: Copyright 2011-2014 Morelli Informatik
10const TOL: f64 = 1e-10;
11const THIRD: f64 = 0.333_333_333_333_333_3;
12const C2_27: f64 = 0.074_074_074_074_074_07; // 2/27
13const PI4_3: f64 = 4.188_790_204_786_391; // 4*pi/3
14const PISQ: f64 = 9.869_604_401_089_358; // pi^2
15const TPISQ: f64 = 19.739_208_802_178_716; // 2*pi^2
16const HPISQ: f64 = 4.934_802_200_544_679; // pi^2/2
17
18/// # van der Grinten (I)
19///
20/// **Classification**: Miscellaneous
21///
22/// **Available forms**: Forward and inverse, spherical projection
23///
24/// **Defined area**: Global
25///
26/// **Alias**: vandg
27///
28/// **Domain**: 2D
29///
30/// **Input type**: Geodetic coordinates
31///
32/// **Output type**: Projected coordinates
33///
34/// ## Projection String
35/// ```ini
36/// +proj=vandg
37/// ```
38///
39/// ## Parameters
40///
41/// All parameters are optional.
42///
43/// - `+lon_0=<value>`: Central meridian.
44/// - `+R=<value>`: Radius of the sphere.
45/// - `+x_0=<value>`: False easting.
46/// - `+y_0=<value>`: False northing.
47///
48/// ![van der Grinten (I)](https://github.com/Open-S2/gis-tools/blob/master/assets/proj4/projections/images/vandg.png?raw=true)
49#[derive(Debug, Clone, PartialEq)]
50pub struct VanDerGrintenIProjection {
51    proj: Rc<RefCell<Proj>>,
52}
53impl ProjectCoordinates for VanDerGrintenIProjection {
54    fn code(&self) -> i64 {
55        -1
56    }
57    fn name(&self) -> &'static str {
58        "van der Grinten (I)"
59    }
60    fn names() -> &'static [&'static str] {
61        &["van der Grinten", "VanDerGrinten", "Van_der_Grinten_I", "van der Grinten (I)", "vandg"]
62    }
63}
64impl CoordinateStep for VanDerGrintenIProjection {
65    fn new(proj: Rc<RefCell<Proj>>) -> Self {
66        {
67            let proj = &mut proj.borrow_mut();
68            proj.es = 0.;
69        }
70        VanDerGrintenIProjection { proj }
71    }
72    fn forward<P: TransformCoordinates>(&self, p: &mut P) {
73        vandg_s_forward(&self.proj.borrow(), p);
74    }
75    fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
76        vandg_s_inverse(p);
77    }
78}
79
80/// Van der Grinten (I) Spheroidal forward project
81pub fn vandg_s_forward<P: TransformCoordinates>(proj: &Proj, p: &mut P) {
82    // Comments tie this formulation to Snyder (1987), p. 241.
83    let mut x;
84    let mut y;
85    let mut p2 = fabs(p.phi() / FRAC_PI_2); // sin(theta) from (29-6)
86    if (p2 - TOL) > 1. {
87        panic!("Coordinate outside projection domain");
88    }
89    let mut sign = 1;
90    if proj.over && fabs(p.lam()) > PI {
91        sign = -1;
92    }
93    if p2 > 1. {
94        p2 = 1.;
95    }
96    if fabs(p.phi()) <= TOL {
97        x = p.lam();
98        y = 0.;
99    } else if fabs(p.lam()) <= TOL || fabs(p2 - 1.) < TOL {
100        x = 0.;
101        y = PI * tan(0.5 * asin(p2));
102        if p.phi() < 0. {
103            y = -y;
104        }
105    } else {
106        let al = 0.5 * (sign as f64) * fabs(PI / p.lam() - p.lam() / PI); // A from (29-3)
107        let al2 = al * al; // A^2
108        let mut g = sqrt(1. - p2 * p2); // cos(theta)
109        g = g / (p2 + g - 1.); // G from (29-4)
110        let g2 = g * g; // G^2
111        p2 = g * (2. / p2 - 1.); // P from (29-5)
112        p2 = p2 * p2; // P^2
113        x = g - p2; // G - P^2
114        g = p2 + al2; // P^2 + A^2
115        // (29-1)
116        x = PI * fabs(al * x + sqrt(al2 * x * x - g * (g2 - p2))) / g;
117        if p.lam() < 0. {
118            x = -x;
119        }
120        y = fabs(x / PI);
121        // y from (29-2) has been expressed in terms of x here
122        y = 1. - y * (y + 2. * al);
123        if y < -TOL {
124            panic!("Coordinate outside projection domain");
125        }
126        if y < 0. {
127            y = 0.;
128        } else {
129            y = sqrt(y) * (if p.phi() < 0. { -PI } else { PI });
130        }
131    }
132
133    p.set_x(x);
134    p.set_y(y);
135}
136
137/// Van der Grinten (I) Spheroidal forward project
138pub fn vandg_s_inverse<P: TransformCoordinates>(p: &mut P) {
139    // static PJ_LP vandg_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */
140    //     PJ_LP lp = {0.0, 0.0};
141    //     double t, c0, c1, c2, c3, al, r2, r, m, d, ay, x2, y2;
142    // Comments tie this formulation to Snyder (1987), p. 242.
143    let x2 = p.x() * p.x(); // pi^2 * X^2
144    let ay = fabs(p.y());
145    if ay < TOL {
146        p.set_phi(0.);
147        let t = x2 * x2 + TPISQ * (x2 + HPISQ);
148        p.set_lam(if fabs(p.x()) <= TOL { 0. } else { 0.5 * (x2 - PISQ + sqrt(t)) / p.x() });
149        return;
150    }
151    let y2 = p.y() * p.y(); // pi^2 * Y^2
152    let r = x2 + y2; // pi^2 * (X^2+Y^2)
153    let r2 = r * r; // pi^4 * (X^2+Y^2)^2
154    let c1 = -PI * ay * (r + PISQ); // pi^4 * c1 (29-11)
155    // pi^4 * c3 (29-13)
156    let c3 = r2 + TAU * (ay * r + PI * (y2 + PI * (ay + FRAC_PI_2)));
157    let mut c2 = c1 + PISQ * (r - 3. * y2); // pi^4 * c2 (29-12)
158    let c0 = PI * ay; // pi^2 * Y
159    c2 /= c3; // c2/c3
160    let al = c1 / c3 - THIRD * c2 * c2; // a1 (29-15)
161    let m = 2. * sqrt(-THIRD * al); // m1 (29-16)
162    let d = C2_27 * c2 * c2 * c2 + (c0 * c0 - THIRD * c2 * c1) / c3; // d (29-14)
163    let al_mul_m = al * m; // a1*m1
164    if fabs(al_mul_m) < 1e-16 {
165        panic!("Coordinate outside projection domain");
166    }
167    let mut d = 3. * d / al_mul_m; // cos(3*theta1) (29-17)
168    let mut t = fabs(d);
169    if (t - TOL) <= 1. {
170        d = if t > 1. { if d > 0. { 0. } else { PI } } else { acos(d) }; // 3*theta1 (29-17)
171        if r > PISQ {
172            // This code path is triggered for coordinates generated in the
173            // forward path when |long|>180deg and +over
174            d = TAU - d;
175        }
176        // (29-18) but change pi/3 to 4*pi/3 to flip sign of cos
177        p.set_phi(PI * (m * cos(d * THIRD + PI4_3) - THIRD * c2));
178        if p.y() < 0. {
179            p.set_phi(-p.phi());
180        }
181        t = r2 + TPISQ * (x2 - y2 + HPISQ);
182        p.set_lam(if fabs(p.x()) <= TOL {
183            0.
184        } else {
185            0.5 * (r - PISQ + (if t <= 0. { 0. } else { sqrt(t) })) / p.x()
186        });
187    } else {
188        panic!("Coordinate outside projection domain");
189    }
190}