GeographicLib 2.5
TransverseMercatorExact.cpp
Go to the documentation of this file.
1/**
2 * \file TransverseMercatorExact.cpp
3 * \brief Implementation for GeographicLib::TransverseMercatorExact class
4 *
5 * Copyright (c) Charles Karney (2008-2022) <karney@alum.mit.edu> and licensed
6 * under the MIT/X11 License. For more information, see
7 * https://geographiclib.sourceforge.io/
8 *
9 * The relevant section of Lee's paper is part V, pp 67--101,
10 * <a href="https://doi.org/10.3138/X687-1574-4325-WM62">Conformal
11 * Projections Based On Jacobian Elliptic Functions</a>;
12 * <a href="https://archive.org/details/conformalproject0000leel/page/92">
13 * borrow from archive.org</a>.
14 *
15 * The method entails using the Thompson Transverse Mercator as an
16 * intermediate projection. The projections from the intermediate
17 * coordinates to [\e phi, \e lam] and [\e x, \e y] are given by elliptic
18 * functions. The inverse of these projections are found by Newton's method
19 * with a suitable starting guess.
20 *
21 * This implementation and notation closely follows Lee, with the following
22 * exceptions:
23 * <center><table>
24 * <tr><th>Lee <th>here <th>Description
25 * <tr><td>x/a <td>xi <td>Northing (unit Earth)
26 * <tr><td>y/a <td>eta <td>Easting (unit Earth)
27 * <tr><td>s/a <td>sigma <td>xi + i * eta
28 * <tr><td>y <td>x <td>Easting
29 * <tr><td>x <td>y <td>Northing
30 * <tr><td>k <td>e <td>eccentricity
31 * <tr><td>k^2 <td>mu <td>elliptic function parameter
32 * <tr><td>k'^2 <td>mv <td>elliptic function complementary parameter
33 * <tr><td>m <td>k <td>scale
34 * <tr><td>zeta <td>zeta <td>complex longitude = Mercator = chi in paper
35 * <tr><td>s <td>sigma <td>complex GK = zeta in paper
36 * </table></center>
37 *
38 * Minor alterations have been made in some of Lee's expressions in an
39 * attempt to control round-off. For example atanh(sin(phi)) is replaced by
40 * asinh(tan(phi)) which maintains accuracy near phi = pi/2. Such changes
41 * are noted in the code.
42 **********************************************************************/
43
45
46namespace GeographicLib {
47
48 using namespace std;
49
50 TransverseMercatorExact::TransverseMercatorExact(real a, real f, real k0,
51 bool extendp)
52 : tol_(numeric_limits<real>::epsilon())
53 , tol2_(real(0.1) * tol_)
54 , taytol_(pow(tol_, real(0.6)))
55 , _a(a)
56 , _f(f)
57 , _k0(k0)
58 , _mu(_f * (2 - _f)) // e^2
59 , _mv(1 - _mu) // 1 - e^2
60 , _e(sqrt(_mu))
61 , _extendp(extendp)
62 , _eEu(_mu)
63 , _eEv(_mv)
64 {
65 if (!(isfinite(_a) && _a > 0))
66 throw GeographicErr("Equatorial radius is not positive");
67 if (!(_f > 0))
68 throw GeographicErr("Flattening is not positive");
69 if (!(_f < 1))
70 throw GeographicErr("Polar semi-axis is not positive");
71 if (!(isfinite(_k0) && _k0 > 0))
72 throw GeographicErr("Scale is not positive");
73 }
74
81
82 void TransverseMercatorExact::zeta(real /*u*/, real snu, real cnu, real dnu,
83 real /*v*/, real snv, real cnv, real dnv,
84 real& taup, real& lam) const {
85 // Lee 54.17 but write
86 // atanh(snu * dnv) = asinh(snu * dnv / sqrt(cnu^2 + _mv * snu^2 * snv^2))
87 // atanh(_e * snu / dnv) =
88 // asinh(_e * snu / sqrt(_mu * cnu^2 + _mv * cnv^2))
89 // Overflow value s.t. atan(overflow) = pi/2
90 static const real
91 overflow = 1 / Math::sq(numeric_limits<real>::epsilon());
92 real
93 d1 = sqrt(Math::sq(cnu) + _mv * Math::sq(snu * snv)),
94 d2 = sqrt(_mu * Math::sq(cnu) + _mv * Math::sq(cnv)),
95 t1 = (d1 != 0 ? snu * dnv / d1 : (signbit(snu) ? -overflow : overflow)),
96 t2 = (d2 != 0 ? sinh( _e * asinh(_e * snu / d2) ) :
97 (signbit(snu) ? -overflow : overflow));
98 // psi = asinh(t1) - asinh(t2)
99 // taup = sinh(psi)
100 taup = t1 * hypot(real(1), t2) - t2 * hypot(real(1), t1);
101 lam = (d1 != 0 && d2 != 0) ?
102 atan2(dnu * snv, cnu * cnv) - _e * atan2(_e * cnu * snv, dnu * cnv) :
103 0;
104 }
105
106 void TransverseMercatorExact::dwdzeta(real /*u*/,
107 real snu, real cnu, real dnu,
108 real /*v*/,
109 real snv, real cnv, real dnv,
110 real& du, real& dv) const {
111 // Lee 54.21 but write (1 - dnu^2 * snv^2) = (cnv^2 + _mu * snu^2 * snv^2)
112 // (see A+S 16.21.4)
113 real d = _mv * Math::sq(Math::sq(cnv) + _mu * Math::sq(snu * snv));
114 du = cnu * dnu * dnv * (Math::sq(cnv) - _mu * Math::sq(snu * snv)) / d;
115 dv = -snu * snv * cnv * (Math::sq(dnu * dnv) + _mu * Math::sq(cnu)) / d;
116 }
117
118 // Starting point for zetainv
119 bool TransverseMercatorExact::zetainv0(real psi, real lam,
120 real& u, real& v) const {
121 bool retval = false;
122 if (psi < -_e * Math::pi()/4 &&
123 lam > (1 - 2 * _e) * Math::pi()/2 &&
124 psi < lam - (1 - _e) * Math::pi()/2) {
125 // N.B. this branch is normally not taken because psi < 0 is converted
126 // psi > 0 by Forward.
127 //
128 // There's a log singularity at w = w0 = Eu.K() + i * Ev.K(),
129 // corresponding to the south pole, where we have, approximately
130 //
131 // psi = _e + i * pi/2 - _e * atanh(cos(i * (w - w0)/(1 + _mu/2)))
132 //
133 // Inverting this gives:
134 real
135 psix = 1 - psi / _e,
136 lamx = (Math::pi()/2 - lam) / _e;
137 u = asinh(sin(lamx) / hypot(cos(lamx), sinh(psix))) *
138 (1 + _mu/2);
139 v = atan2(cos(lamx), sinh(psix)) * (1 + _mu/2);
140 u = _eEu.K() - u;
141 v = _eEv.K() - v;
142 } else if (psi < _e * Math::pi()/2 &&
143 lam > (1 - 2 * _e) * Math::pi()/2) {
144 // At w = w0 = i * Ev.K(), we have
145 //
146 // zeta = zeta0 = i * (1 - _e) * pi/2
147 // zeta' = zeta'' = 0
148 //
149 // including the next term in the Taylor series gives:
150 //
151 // zeta = zeta0 - (_mv * _e) / 3 * (w - w0)^3
152 //
153 // When inverting this, we map arg(w - w0) = [-90, 0] to
154 // arg(zeta - zeta0) = [-90, 180]
155 real
156 dlam = lam - (1 - _e) * Math::pi()/2,
157 rad = hypot(psi, dlam),
158 // atan2(dlam-psi, psi+dlam) + 45d gives arg(zeta - zeta0) in range
159 // [-135, 225). Subtracting 180 (since multiplier is negative) makes
160 // range [-315, 45). Multiplying by 1/3 (for cube root) gives range
161 // [-105, 15). In particular the range [-90, 180] in zeta space maps
162 // to [-90, 0] in w space as required.
163 ang = atan2(dlam-psi, psi+dlam) - real(0.75) * Math::pi();
164 // Error using this guess is about 0.21 * (rad/e)^(5/3)
165 retval = rad < _e * taytol_;
166 rad = cbrt(3 / (_mv * _e) * rad);
167 ang /= 3;
168 u = rad * cos(ang);
169 v = rad * sin(ang) + _eEv.K();
170 } else {
171 // Use spherical TM, Lee 12.6 -- writing atanh(sin(lam) / cosh(psi)) =
172 // asinh(sin(lam) / hypot(cos(lam), sinh(psi))). This takes care of the
173 // log singularity at zeta = Eu.K() (corresponding to the north pole)
174 v = asinh(sin(lam) / hypot(cos(lam), sinh(psi)));
175 u = atan2(sinh(psi), cos(lam));
176 // But scale to put 90,0 on the right place
177 u *= _eEu.K() / (Math::pi()/2);
178 v *= _eEu.K() / (Math::pi()/2);
179 }
180 return retval;
181 }
182
183 // Invert zeta using Newton's method
184 void TransverseMercatorExact::zetainv(real taup, real lam,
185 real& u, real& v) const {
186 real
187 psi = asinh(taup),
188 scal = 1/hypot(real(1), taup);
189 if (zetainv0(psi, lam, u, v))
190 return;
191 real stol2 = tol2_ / Math::sq(fmax(psi, real(1)));
192 // min iterations = 2, max iterations = 6; mean = 4.0
193 for (int i = 0, trip = 0;
194 i < numit_ ||
196 ("Convergence failure in TransverseMercatorExact");
197 ++i) {
198 real snu, cnu, dnu, snv, cnv, dnv;
199 _eEu.am(u, snu, cnu, dnu);
200 _eEv.am(v, snv, cnv, dnv);
201 real tau1, lam1, du1, dv1;
202 zeta(u, snu, cnu, dnu, v, snv, cnv, dnv, tau1, lam1);
203 dwdzeta(u, snu, cnu, dnu, v, snv, cnv, dnv, du1, dv1);
204 tau1 -= taup;
205 lam1 -= lam;
206 tau1 *= scal;
207 real
208 delu = tau1 * du1 - lam1 * dv1,
209 delv = tau1 * dv1 + lam1 * du1;
210 u -= delu;
211 v -= delv;
212 if (trip)
213 break;
214 real delw2 = Math::sq(delu) + Math::sq(delv);
215 if (!(delw2 >= stol2))
216 ++trip;
217 }
218 }
219
220 void TransverseMercatorExact::sigma(real /*u*/, real snu, real cnu, real dnu,
221 real v, real snv, real cnv, real dnv,
222 real& xi, real& eta) const {
223 // Lee 55.4 writing
224 // dnu^2 + dnv^2 - 1 = _mu * cnu^2 + _mv * cnv^2
225 real d = _mu * Math::sq(cnu) + _mv * Math::sq(cnv);
226 xi = _eEu.E(snu, cnu, dnu) - _mu * snu * cnu * dnu / d;
227 eta = v - _eEv.E(snv, cnv, dnv) + _mv * snv * cnv * dnv / d;
228 }
229
230 void TransverseMercatorExact::dwdsigma(real /*u*/,
231 real snu, real cnu, real dnu,
232 real /*v*/,
233 real snv, real cnv, real dnv,
234 real& du, real& dv) const {
235 // Reciprocal of 55.9: dw/ds = dn(w)^2/_mv, expanding complex dn(w) using
236 // A+S 16.21.4
237 real d = _mv * Math::sq(Math::sq(cnv) + _mu * Math::sq(snu * snv));
238 real
239 dnr = dnu * cnv * dnv,
240 dni = - _mu * snu * cnu * snv;
241 du = (Math::sq(dnr) - Math::sq(dni)) / d;
242 dv = 2 * dnr * dni / d;
243 }
244
245 // Starting point for sigmainv
246 bool TransverseMercatorExact::sigmainv0(real xi, real eta,
247 real& u, real& v) const {
248 bool retval = false;
249 if (eta > real(1.25) * _eEv.KE() ||
250 (xi < -real(0.25) * _eEu.E() && xi < eta - _eEv.KE())) {
251 // sigma as a simple pole at w = w0 = Eu.K() + i * Ev.K() and sigma is
252 // approximated by
253 //
254 // sigma = (Eu.E() + i * Ev.KE()) + 1/(w - w0)
255 real
256 x = xi - _eEu.E(),
257 y = eta - _eEv.KE(),
258 r2 = Math::sq(x) + Math::sq(y);
259 u = _eEu.K() + x/r2;
260 v = _eEv.K() - y/r2;
261 } else if ((eta > real(0.75) * _eEv.KE() && xi < real(0.25) * _eEu.E())
262 || eta > _eEv.KE()) {
263 // At w = w0 = i * Ev.K(), we have
264 //
265 // sigma = sigma0 = i * Ev.KE()
266 // sigma' = sigma'' = 0
267 //
268 // including the next term in the Taylor series gives:
269 //
270 // sigma = sigma0 - _mv / 3 * (w - w0)^3
271 //
272 // When inverting this, we map arg(w - w0) = [-pi/2, -pi/6] to
273 // arg(sigma - sigma0) = [-pi/2, pi/2]
274 // mapping arg = [-pi/2, -pi/6] to [-pi/2, pi/2]
275 real
276 deta = eta - _eEv.KE(),
277 rad = hypot(xi, deta),
278 // Map the range [-90, 180] in sigma space to [-90, 0] in w space. See
279 // discussion in zetainv0 on the cut for ang.
280 ang = atan2(deta-xi, xi+deta) - real(0.75) * Math::pi();
281 // Error using this guess is about 0.068 * rad^(5/3)
282 retval = rad < 2 * taytol_;
283 rad = cbrt(3 / _mv * rad);
284 ang /= 3;
285 u = rad * cos(ang);
286 v = rad * sin(ang) + _eEv.K();
287 } else {
288 // Else use w = sigma * Eu.K/Eu.E (which is correct in the limit _e -> 0)
289 u = xi * _eEu.K()/_eEu.E();
290 v = eta * _eEu.K()/_eEu.E();
291 }
292 return retval;
293 }
294
295 // Invert sigma using Newton's method
296 void TransverseMercatorExact::sigmainv(real xi, real eta,
297 real& u, real& v) const {
298 if (sigmainv0(xi, eta, u, v))
299 return;
300 // min iterations = 2, max iterations = 7; mean = 3.9
301 for (int i = 0, trip = 0;
302 i < numit_ ||
304 ("Convergence failure in TransverseMercatorExact");
305 ++i) {
306 real snu, cnu, dnu, snv, cnv, dnv;
307 _eEu.am(u, snu, cnu, dnu);
308 _eEv.am(v, snv, cnv, dnv);
309 real xi1, eta1, du1, dv1;
310 sigma(u, snu, cnu, dnu, v, snv, cnv, dnv, xi1, eta1);
311 dwdsigma(u, snu, cnu, dnu, v, snv, cnv, dnv, du1, dv1);
312 xi1 -= xi;
313 eta1 -= eta;
314 real
315 delu = xi1 * du1 - eta1 * dv1,
316 delv = xi1 * dv1 + eta1 * du1;
317 u -= delu;
318 v -= delv;
319 if (trip)
320 break;
321 real delw2 = Math::sq(delu) + Math::sq(delv);
322 if (!(delw2 >= tol2_))
323 ++trip;
324 }
325 }
326
327 void TransverseMercatorExact::Scale(real tau, real /*lam*/,
328 real snu, real cnu, real dnu,
329 real snv, real cnv, real dnv,
330 real& gamma, real& k) const {
331 real sec2 = 1 + Math::sq(tau); // sec(phi)^2
332 // Lee 55.12 -- negated for our sign convention. gamma gives the bearing
333 // (clockwise from true north) of grid north
334 gamma = atan2(_mv * snu * snv * cnv, cnu * dnu * dnv);
335 // Lee 55.13 with nu given by Lee 9.1 -- in sqrt change the numerator
336 // from
337 //
338 // (1 - snu^2 * dnv^2) to (_mv * snv^2 + cnu^2 * dnv^2)
339 //
340 // to maintain accuracy near phi = 90 and change the denomintor from
341 //
342 // (dnu^2 + dnv^2 - 1) to (_mu * cnu^2 + _mv * cnv^2)
343 //
344 // to maintain accuracy near phi = 0, lam = 90 * (1 - e). Similarly
345 // rewrite sqrt term in 9.1 as
346 //
347 // _mv + _mu * c^2 instead of 1 - _mu * sin(phi)^2
348 k = sqrt(_mv + _mu / sec2) * sqrt(sec2) *
349 sqrt( (_mv * Math::sq(snv) + Math::sq(cnu * dnv)) /
350 (_mu * Math::sq(cnu) + _mv * Math::sq(cnv)) );
351 }
352
353 void TransverseMercatorExact::Forward(real lon0, real lat, real lon,
354 real& x, real& y,
355 real& gamma, real& k) const {
356 lat = Math::LatFix(lat);
357 lon = Math::AngDiff(lon0, lon);
358 // Explicitly enforce the parity
359 int
360 latsign = (!_extendp && signbit(lat)) ? -1 : 1,
361 lonsign = (!_extendp && signbit(lon)) ? -1 : 1;
362 lon *= lonsign;
363 lat *= latsign;
364 bool backside = !_extendp && lon > Math::qd;
365 if (backside) {
366 if (lat == 0)
367 latsign = -1;
368 lon = Math::hd - lon;
369 }
370 real
371 lam = lon * Math::degree(),
372 tau = Math::tand(lat);
373
374 // u,v = coordinates for the Thompson TM, Lee 54
375 real u, v;
376 if (lat == Math::qd) {
377 u = _eEu.K();
378 v = 0;
379 } else if (lat == 0 && lon == Math::qd * (1 - _e)) {
380 u = 0;
381 v = _eEv.K();
382 } else
383 // tau = tan(phi), taup = sinh(psi)
384 zetainv(Math::taupf(tau, _e), lam, u, v);
385
386 real snu, cnu, dnu, snv, cnv, dnv;
387 _eEu.am(u, snu, cnu, dnu);
388 _eEv.am(v, snv, cnv, dnv);
389
390 real xi, eta;
391 sigma(u, snu, cnu, dnu, v, snv, cnv, dnv, xi, eta);
392 if (backside)
393 xi = 2 * _eEu.E() - xi;
394 y = xi * _a * _k0 * latsign;
395 x = eta * _a * _k0 * lonsign;
396
397 if (lat == Math::qd) {
398 gamma = lon;
399 k = 1;
400 } else {
401 // Recompute (tau, lam) from (u, v) to improve accuracy of Scale
402 zeta(u, snu, cnu, dnu, v, snv, cnv, dnv, tau, lam);
403 tau = Math::tauf(tau, _e);
404 Scale(tau, lam, snu, cnu, dnu, snv, cnv, dnv, gamma, k);
405 gamma /= Math::degree();
406 }
407 if (backside)
408 gamma = Math::hd - gamma;
409 gamma *= latsign * lonsign;
410 k *= _k0;
411 }
412
413 void TransverseMercatorExact::Reverse(real lon0, real x, real y,
414 real& lat, real& lon,
415 real& gamma, real& k) const {
416 // This undoes the steps in Forward.
417 real
418 xi = y / (_a * _k0),
419 eta = x / (_a * _k0);
420 // Explicitly enforce the parity
421 int
422 xisign = (!_extendp && signbit(xi)) ? -1 : 1,
423 etasign = (!_extendp && signbit(eta)) ? -1 : 1;
424 xi *= xisign;
425 eta *= etasign;
426 bool backside = !_extendp && xi > _eEu.E();
427 if (backside)
428 xi = 2 * _eEu.E()- xi;
429
430 // u,v = coordinates for the Thompson TM, Lee 54
431 real u, v;
432 if (xi == 0 && eta == _eEv.KE()) {
433 u = 0;
434 v = _eEv.K();
435 } else
436 sigmainv(xi, eta, u, v);
437
438 real snu, cnu, dnu, snv, cnv, dnv;
439 _eEu.am(u, snu, cnu, dnu);
440 _eEv.am(v, snv, cnv, dnv);
441 real phi, lam, tau;
442 if (v != 0 || u != _eEu.K()) {
443 zeta(u, snu, cnu, dnu, v, snv, cnv, dnv, tau, lam);
444 tau = Math::tauf(tau, _e);
445 phi = atan(tau);
446 lat = phi / Math::degree();
447 lon = lam / Math::degree();
448 Scale(tau, lam, snu, cnu, dnu, snv, cnv, dnv, gamma, k);
449 gamma /= Math::degree();
450 } else {
451 lat = Math::qd;
452 lon = lam = gamma = 0;
453 k = 1;
454 }
455
456 if (backside)
457 lon = Math::hd - lon;
458 lon *= etasign;
459 lon = Math::AngNormalize(lon + Math::AngNormalize(lon0));
460 lat *= xisign;
461 if (backside)
462 gamma = Math::hd - gamma;
463 gamma *= xisign * etasign;
464 k *= _k0;
465 }
466
467} // namespace GeographicLib
GeographicLib::Math::real real
Definition GeodSolve.cpp:28
#define GEOGRAPHICLIB_PANIC(msg)
Definition Math.hpp:62
Header for GeographicLib::TransverseMercatorExact class.
Exception handling for GeographicLib.
static T degree()
Definition Math.hpp:209
static T tand(T x)
Definition Math.cpp:188
static T LatFix(T x)
Definition Math.hpp:309
static T sq(T x)
Definition Math.hpp:221
static constexpr int qd
degrees per quarter turn
Definition Math.hpp:145
static T tauf(T taup, T es)
Definition Math.cpp:235
static T AngNormalize(T x)
Definition Math.cpp:66
static T taupf(T tau, T es)
Definition Math.cpp:225
static T pi()
Definition Math.hpp:199
static T AngDiff(T x, T y, T &e)
Definition Math.cpp:77
static constexpr int hd
degrees per half turn
Definition Math.hpp:148
An exact implementation of the transverse Mercator projection.
void Forward(real lon0, real lat, real lon, real &x, real &y, real &gamma, real &k) const
static const TransverseMercatorExact & UTM()
void Reverse(real lon0, real x, real y, real &lat, real &lon, real &gamma, real &k) const
Namespace for GeographicLib.