gistools/proj/project/merc.rs
1use crate::proj::{
2 _msfn, CoordinateStep, LATITUDE_OF_FIRST_STANDARD_PARALLEL, MERCATOR, MERCATOR_VARIANT_A,
3 MERCATOR_VARIANT_B, Proj, ProjMethod, ProjectCoordinates, TransformCoordinates, WEB_MERCATOR,
4 sinhpsi2tanphi,
5};
6use alloc::rc::Rc;
7use core::cell::RefCell;
8use libm::{asinh, atan, atanh, cos, fabs, sin, sinh, tan};
9
10/// Mercator projection
11pub type MercatorProjection = MercatorBaseProjection<MERCATOR>;
12/// Mercator (variant A) projection
13pub type MercatorVariantAProjection = MercatorBaseProjection<MERCATOR_VARIANT_A>;
14/// Mercator (variant B) projection
15pub type MercatorVariantBProjection = MercatorBaseProjection<MERCATOR_VARIANT_B>;
16
17/// # Mercator Projection
18///
19/// The Mercator projection is a cylindrical map projection originating from the 16th century.
20/// It is widely recognized as the first regularly used map projection. It is a conformal projection
21/// where the equator projects to a straight line at constant scale. A rhumb line, or course of
22/// constant heading, projects to a straight line, making it suitable for navigational purposes.
23///
24/// **Classification**: Conformal cylindrical
25///
26/// **Available forms**: Forward and Inverse, spherical and ellipsoidal
27///
28/// **Defined area**: Global, but best used near the equator
29///
30/// **Alias**: `merc`
31///
32/// **Domain**: 2D
33///
34/// **Input type**: Geodetic coordinates
35///
36/// **Output type**: Projected coordinates
37///
38/// ## Projection String
39/// ```ini
40/// +proj=merc
41/// ```
42///
43/// ## Usage
44/// The Mercator projection is often used for equatorial regions and navigational charts. It is not
45/// suitable for world maps due to significant area distortions. For example, Greenland appears
46/// larger than South America in the projection, despite Greenland's actual area being approximately
47/// one-eighth of South America's.
48///
49/// **Examples:**
50///
51/// - Using latitude of true scale:
52/// ```bash
53/// $ echo 56.35 12.32 | proj +proj=merc +lat_ts=56.5
54/// 3470306.37 759599.90
55/// ```
56/// - Using scaling factor:
57/// ```bash
58/// $ echo 56.35 12.32 | proj +proj=merc +k_0=2
59/// 12545706.61 2746073.80
60/// ```
61///
62/// **Note**: `+lat_ts` and `+k_0` are mutually exclusive. If both are used, `+lat_ts` takes
63/// precedence over `+k_0`.
64///
65/// ## Parameters
66/// - `lat_ts`: Latitude of true scale
67/// - `k_0`: Scaling factor
68/// - `lon_0`: Longitude of origin
69/// - `x_0`: False easting
70/// - `y_0`: False northing
71/// - `ellps`: Ellipsoid
72/// - `R`: Radius of the sphere
73///
74/// ## Mathematical Definition
75///
76/// **Spheroidal Form**
77/// - **Forward Projection**:
78/// $$x = k_0 \cdot R \cdot \lambda$$
79/// $$y = k_0 \cdot R \cdot \psi$$
80/// where
81/// $$\psi = \ln\left(\tan\left(\frac{\pi}{4} + \frac{\phi}{2}\right)\right)$$
82/// - **Inverse Projection**:
83/// $$\lambda = x / (k_0 \cdot R)$$
84/// $$\psi = y / (k_0 \cdot R)$$
85/// $$\phi = \frac{\pi}{2} - 2 \cdot \arctan\left(\exp(-\psi)\right)$$
86///
87/// **Ellipsoidal Form**
88/// - **Forward Projection**:
89/// $$x = k_0 \cdot a \cdot \lambda$$
90/// $$y = k_0 \cdot a \cdot \psi$$
91/// where
92/// $$\psi = \ln\left(\tan\left(\frac{\pi}{4} + \frac{\phi}{2}\right)\right) - 0.5 \cdot e \cdot \ln\left(\frac{1 + e \cdot \sin(\phi)}{1 - e \cdot \sin(\phi)}\right)$$
93/// - **Inverse Projection**:
94/// $$\lambda = x / (k_0 \cdot a)$$
95/// $$\psi = y / (k_0 \cdot a)$$
96/// $$\phi = \arctan(\tau)$$
97/// where
98/// $$\tau = \tan(\phi)$$
99///
100/// ## Further Reading
101/// - [Wikipedia: Mercator Projection](https://en.wikipedia.org/wiki/Mercator_projection)
102/// - [Wolfram Mathworld: Mercator Projection](http://mathworld.wolfram.com/MercatorProjection.html)
103///
104/// 
105#[derive(Debug, Clone, PartialEq)]
106pub struct MercatorBaseProjection<const C: i64> {
107 proj: Rc<RefCell<Proj>>,
108 conv_case: ProjMethod,
109}
110impl<const C: i64> ProjectCoordinates for MercatorBaseProjection<C> {
111 fn code(&self) -> i64 {
112 C
113 }
114 fn name(&self) -> &'static str {
115 "Mercator"
116 }
117 fn names() -> &'static [&'static str] {
118 &[
119 "Mercator",
120 "Popular Visualisation Pseudo Mercator",
121 "Mercator_1SP",
122 "Mercator_2SP",
123 "Mercator (variant A)",
124 "Mercator (variant B)",
125 "Mercator_Auxiliary_Sphere",
126 "merc",
127 ]
128 }
129}
130impl<const C: i64> CoordinateStep for MercatorBaseProjection<C> {
131 fn new(proj: Rc<RefCell<Proj>>) -> Self {
132 let mut phits = 0.0;
133 let mut is_phits: bool = false;
134 let conv_case: ProjMethod;
135 {
136 let proj = &mut proj.borrow_mut();
137 if let Some(lat_ts) = proj.params.get(&LATITUDE_OF_FIRST_STANDARD_PARALLEL) {
138 phits = fabs(lat_ts.f64());
139 is_phits = true;
140 }
141 if phits >= core::f64::consts::FRAC_PI_2 {
142 panic!("Invalid value for lat_ts: |lat_ts| should be <= 90°");
143 }
144
145 if proj.es != 0.0 {
146 // ellipsoid case
147 conv_case = ProjMethod::Ellipsoidal;
148 if is_phits {
149 proj.k0 = _msfn(sin(phits), cos(phits), proj.es);
150 }
151 } else {
152 // sphere case
153 conv_case = ProjMethod::Spheroidal;
154 if is_phits {
155 proj.k0 = cos(phits);
156 }
157 }
158 }
159 MercatorBaseProjection { proj, conv_case }
160 }
161 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
162 if self.conv_case == ProjMethod::Spheroidal {
163 merc_s_forward(&self.proj.borrow(), p);
164 } else {
165 merc_e_forward(&self.proj.borrow(), p);
166 }
167 }
168 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
169 if self.conv_case == ProjMethod::Spheroidal {
170 merc_s_inverse(&self.proj.borrow(), p);
171 } else {
172 merc_e_inverse(&self.proj.borrow(), p);
173 }
174 }
175}
176
177/// # Web Mercator / Pseudo Mercator Projection
178///
179/// The Web Mercator / Pseudo Mercator projection is a cylindrical map projection.
180/// This is a variant of the regular [Mercator](crate::proj::MercatorBaseProjection) projection,
181/// except that the computation is done on a sphere, using the semi-major axis of the ellipsoid.
182///
183/// From [Wikipedia](https://en.wikipedia.org/wiki/Web_Mercator):
184///
185/// > This projection is widely used by the `Web Mercator`, `Google Web Mercator`,
186/// > `Spheroidal Mercator`, `WGS 84 Web Mercator[1]` or `WGS 84/Pseudo-Mercator` is a
187/// > variant of the Mercator projection and is the de facto standard for Web
188/// > mapping applications. [...]
189/// > It is used by virtually all major online map providers [...]
190/// > Its official EPSG identifier is EPSG:3857, although others have been used
191/// > historically.
192///
193/// **Classification**: Cylindrical (non-conformal if used with an ellipsoid)
194///
195/// **Available forms**: Forward and Inverse
196///
197/// **Defined area**: Global
198///
199/// **Alias**: `webmerc`
200///
201/// **Domain**: 2D
202///
203/// **Input type**: Geodetic coordinates
204///
205/// **Output type**: Projected coordinates
206///
207/// ## Usage
208///
209/// ```bash
210/// $ echo 2 49 | proj +proj=webmerc +datum=WGS84
211/// 222638.98 6274861.39
212/// ```
213///
214/// ## Parameters
215///
216/// **Note**: All parameters for the projection are optional, except the ellipsoid
217/// definition, which is WGS84 for the typical use case of EPSG:3857.
218/// In which case, the other parameters are set to their default 0 value.
219///
220/// - `ellps`: Ellipsoid
221/// - `lon_0`: Longitude of origin
222/// - `x_0`: False easting
223/// - `y_0`: False northing
224///
225/// ## Mathematical Definition
226///
227/// The formulas describing the Mercator projection are adapted from G. Evenden's libproj manuals.
228///
229/// **Forward Projection**:
230///
231/// $$ x = λ $$
232/// $$ y = ln(tan(π/4 + φ/2)) $$
233///
234///
235/// **Inverse Projection**:
236///
237/// $$ λ = x $$
238/// $$ φ = π/2 - 2 * atan(exp(-y)) $$
239///
240///
241/// ## Further Reading
242///
243/// - [Wikipedia: Web Mercator](https://en.wikipedia.org/wiki/Web_Mercator)
244///
245/// 
246#[derive(Debug, Clone, PartialEq)]
247pub struct WebMercatorProjection {
248 proj: Rc<RefCell<Proj>>,
249}
250impl ProjectCoordinates for WebMercatorProjection {
251 fn code(&self) -> i64 {
252 WEB_MERCATOR
253 }
254 fn name(&self) -> &'static str {
255 "Web Mercator"
256 }
257 fn names() -> &'static [&'static str] {
258 &["Web Mercator", "Pseudo Mercator", "webmerc"]
259 }
260}
261impl CoordinateStep for WebMercatorProjection {
262 fn new(proj: Rc<RefCell<Proj>>) -> Self {
263 WebMercatorProjection { proj }
264 }
265 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
266 merc_s_forward(&self.proj.borrow(), p);
267 }
268 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
269 merc_s_inverse(&self.proj.borrow(), p);
270 }
271}
272
273/// Ellipsoidal, mercator forward projection
274pub fn merc_e_forward<P: TransformCoordinates>(proj: &Proj, p: &mut P) {
275 p.set_x(proj.k0 * p.lam());
276 // Instead of calling tan and sin, call sin and cos which the compiler
277 // optimizes to a single call to sincos.
278 let phi = p.phi();
279 let sphi = phi.sin();
280 let cphi = phi.cos();
281 p.set_y(proj.k0 * (asinh(sphi / cphi) - proj.e * atanh(proj.e * sphi)));
282}
283
284/// Spheroidal, mercator forward
285pub fn merc_s_forward<P: TransformCoordinates>(proj: &Proj, p: &mut P) {
286 p.set_x(proj.k0 * p.lam());
287 p.set_y(proj.k0 * asinh(tan(p.phi())));
288}
289
290/// Ellipsoidal, mercator inverse
291pub fn merc_e_inverse<P: TransformCoordinates>(proj: &Proj, p: &mut P) {
292 p.set_phi(atan(sinhpsi2tanphi(sinh(p.y() / proj.k0), proj.e)));
293}
294
295/// Spheroidal, mercator inverse
296pub fn merc_s_inverse<P: TransformCoordinates>(proj: &Proj, p: &mut P) {
297 p.set_phi(atan(sinh(p.y() / proj.k0)));
298 p.set_lam(p.x() / proj.k0);
299}