pygeodesy 24.3.24__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. PyGeodesy-24.3.24.dist-info/METADATA +272 -0
  2. PyGeodesy-24.3.24.dist-info/RECORD +115 -0
  3. PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
  4. PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
  5. pygeodesy/LICENSE +21 -0
  6. pygeodesy/__init__.py +615 -0
  7. pygeodesy/__main__.py +103 -0
  8. pygeodesy/albers.py +867 -0
  9. pygeodesy/auxilats/_CX_4.py +218 -0
  10. pygeodesy/auxilats/_CX_6.py +314 -0
  11. pygeodesy/auxilats/_CX_8.py +475 -0
  12. pygeodesy/auxilats/__init__.py +54 -0
  13. pygeodesy/auxilats/__main__.py +86 -0
  14. pygeodesy/auxilats/auxAngle.py +548 -0
  15. pygeodesy/auxilats/auxDLat.py +302 -0
  16. pygeodesy/auxilats/auxDST.py +296 -0
  17. pygeodesy/auxilats/auxLat.py +848 -0
  18. pygeodesy/auxilats/auxily.py +272 -0
  19. pygeodesy/azimuthal.py +1150 -0
  20. pygeodesy/basics.py +892 -0
  21. pygeodesy/booleans.py +2031 -0
  22. pygeodesy/cartesianBase.py +1062 -0
  23. pygeodesy/clipy.py +704 -0
  24. pygeodesy/constants.py +516 -0
  25. pygeodesy/css.py +660 -0
  26. pygeodesy/datums.py +752 -0
  27. pygeodesy/deprecated/__init__.py +61 -0
  28. pygeodesy/deprecated/bases.py +40 -0
  29. pygeodesy/deprecated/classes.py +262 -0
  30. pygeodesy/deprecated/consterns.py +54 -0
  31. pygeodesy/deprecated/datum.py +40 -0
  32. pygeodesy/deprecated/functions.py +375 -0
  33. pygeodesy/deprecated/nvector.py +48 -0
  34. pygeodesy/deprecated/rhumbBase.py +32 -0
  35. pygeodesy/deprecated/rhumbaux.py +33 -0
  36. pygeodesy/deprecated/rhumbsolve.py +33 -0
  37. pygeodesy/deprecated/rhumbx.py +33 -0
  38. pygeodesy/dms.py +986 -0
  39. pygeodesy/ecef.py +1348 -0
  40. pygeodesy/elevations.py +279 -0
  41. pygeodesy/ellipsoidalBase.py +1224 -0
  42. pygeodesy/ellipsoidalBaseDI.py +913 -0
  43. pygeodesy/ellipsoidalExact.py +343 -0
  44. pygeodesy/ellipsoidalGeodSolve.py +343 -0
  45. pygeodesy/ellipsoidalKarney.py +403 -0
  46. pygeodesy/ellipsoidalNvector.py +685 -0
  47. pygeodesy/ellipsoidalVincenty.py +590 -0
  48. pygeodesy/ellipsoids.py +2476 -0
  49. pygeodesy/elliptic.py +1198 -0
  50. pygeodesy/epsg.py +243 -0
  51. pygeodesy/errors.py +804 -0
  52. pygeodesy/etm.py +1190 -0
  53. pygeodesy/fmath.py +1013 -0
  54. pygeodesy/formy.py +1818 -0
  55. pygeodesy/frechet.py +865 -0
  56. pygeodesy/fstats.py +760 -0
  57. pygeodesy/fsums.py +1898 -0
  58. pygeodesy/gars.py +358 -0
  59. pygeodesy/geodesicw.py +581 -0
  60. pygeodesy/geodesicx/_C4_24.py +1699 -0
  61. pygeodesy/geodesicx/_C4_27.py +2395 -0
  62. pygeodesy/geodesicx/_C4_30.py +3301 -0
  63. pygeodesy/geodesicx/__init__.py +48 -0
  64. pygeodesy/geodesicx/__main__.py +91 -0
  65. pygeodesy/geodesicx/gx.py +1382 -0
  66. pygeodesy/geodesicx/gxarea.py +535 -0
  67. pygeodesy/geodesicx/gxbases.py +154 -0
  68. pygeodesy/geodesicx/gxline.py +669 -0
  69. pygeodesy/geodsolve.py +426 -0
  70. pygeodesy/geohash.py +914 -0
  71. pygeodesy/geoids.py +1884 -0
  72. pygeodesy/hausdorff.py +892 -0
  73. pygeodesy/heights.py +1155 -0
  74. pygeodesy/interns.py +687 -0
  75. pygeodesy/iters.py +545 -0
  76. pygeodesy/karney.py +919 -0
  77. pygeodesy/ktm.py +633 -0
  78. pygeodesy/latlonBase.py +1766 -0
  79. pygeodesy/lazily.py +960 -0
  80. pygeodesy/lcc.py +684 -0
  81. pygeodesy/ltp.py +1107 -0
  82. pygeodesy/ltpTuples.py +1563 -0
  83. pygeodesy/mgrs.py +721 -0
  84. pygeodesy/named.py +1324 -0
  85. pygeodesy/namedTuples.py +683 -0
  86. pygeodesy/nvectorBase.py +695 -0
  87. pygeodesy/osgr.py +781 -0
  88. pygeodesy/points.py +1686 -0
  89. pygeodesy/props.py +628 -0
  90. pygeodesy/resections.py +1048 -0
  91. pygeodesy/rhumb/__init__.py +46 -0
  92. pygeodesy/rhumb/aux_.py +397 -0
  93. pygeodesy/rhumb/bases.py +1148 -0
  94. pygeodesy/rhumb/ekx.py +563 -0
  95. pygeodesy/rhumb/solve.py +572 -0
  96. pygeodesy/simplify.py +647 -0
  97. pygeodesy/solveBase.py +472 -0
  98. pygeodesy/sphericalBase.py +724 -0
  99. pygeodesy/sphericalNvector.py +1264 -0
  100. pygeodesy/sphericalTrigonometry.py +1447 -0
  101. pygeodesy/streprs.py +627 -0
  102. pygeodesy/trf.py +2079 -0
  103. pygeodesy/triaxials.py +1484 -0
  104. pygeodesy/units.py +969 -0
  105. pygeodesy/unitsBase.py +349 -0
  106. pygeodesy/ups.py +538 -0
  107. pygeodesy/utily.py +1231 -0
  108. pygeodesy/utm.py +762 -0
  109. pygeodesy/utmups.py +318 -0
  110. pygeodesy/utmupsBase.py +517 -0
  111. pygeodesy/vector2d.py +785 -0
  112. pygeodesy/vector3d.py +968 -0
  113. pygeodesy/vector3dBase.py +1049 -0
  114. pygeodesy/webmercator.py +383 -0
  115. pygeodesy/wgrs.py +439 -0
pygeodesy/etm.py ADDED
@@ -0,0 +1,1190 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''A pure Python version of I{Karney}'s C{Exact Transverse Mercator} (ETM) projection.
5
+
6
+ Classes L{Etm}, L{ETMError} and L{ExactTransverseMercator}, transcoded from I{Karney}'s
7
+ C++ class U{TransverseMercatorExact<https://GeographicLib.SourceForge.io/C++/doc/
8
+ classGeographicLib_1_1TransverseMercatorExact.html>}, abbreviated as C{TMExact} below.
9
+
10
+ Class L{ExactTransverseMercator} provides C{Exact Transverse Mercator} projections while
11
+ instances of class L{Etm} represent ETM C{(easting, northing)} locations. See also
12
+ I{Karney}'s utility U{TransverseMercatorProj<https://GeographicLib.SourceForge.io/C++/doc/
13
+ TransverseMercatorProj.1.html>} and use C{"python[3] -m pygeodesy.etm ..."} to compare
14
+ the results.
15
+
16
+ Following is a copy of I{Karney}'s U{TransverseMercatorExact.hpp
17
+ <https://GeographicLib.SourceForge.io/C++/doc/TransverseMercatorExact_8hpp_source.html>}
18
+ file C{Header}.
19
+
20
+ Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2023) and licensed
21
+ under the MIT/X11 License. For more information, see the U{GeographicLib<https://
22
+ GeographicLib.SourceForge.io>} documentation.
23
+
24
+ The method entails using the U{Thompson Transverse Mercator<https://WikiPedia.org/
25
+ wiki/Transverse_Mercator_projection>} as an intermediate projection. The projections
26
+ from the intermediate coordinates to C{phi, lam} and C{x, y} are given by elliptic
27
+ functions. The inverse of these projections are found by Newton's method with a
28
+ suitable starting guess.
29
+
30
+ The relevant section of L.P. Lee's paper U{Conformal Projections Based On Jacobian
31
+ Elliptic Functions<https://DOI.org/10.3138/X687-1574-4325-WM62>} in part V, pp
32
+ 67-101. The C++ implementation and notation closely follow Lee, with the following
33
+ exceptions::
34
+
35
+ Lee here Description
36
+
37
+ x/a xi Northing (unit Earth)
38
+
39
+ y/a eta Easting (unit Earth)
40
+
41
+ s/a sigma xi + i * eta
42
+
43
+ y x Easting
44
+
45
+ x y Northing
46
+
47
+ k e Eccentricity
48
+
49
+ k^2 mu Elliptic function parameter
50
+
51
+ k'^2 mv Elliptic function complementary parameter
52
+
53
+ m k Scale
54
+
55
+ zeta zeta Complex longitude = Mercator = chi in paper
56
+
57
+ s sigma Complex GK = zeta in paper
58
+
59
+ Minor alterations have been made in some of Lee's expressions in an attempt to
60
+ control round-off. For example, C{atanh(sin(phi))} is replaced by C{asinh(tan(phi))}
61
+ which maintains accuracy near C{phi = pi/2}. Such changes are noted in the code.
62
+ '''
63
+ # make sure int/int division yields float quotient, see .basics
64
+ from __future__ import division as _; del _ # PYCHOK semicolon
65
+
66
+ from pygeodesy.basics import map1, neg, neg_, _xinstanceof
67
+ from pygeodesy.constants import EPS, EPS02, PI_2, PI_4, _K0_UTM, \
68
+ _1_EPS, _0_0, _0_1, _0_5, _1_0, _2_0, \
69
+ _3_0, _4_0, _90_0, isnear0, isnear90
70
+ from pygeodesy.datums import _ellipsoidal_datum, _WGS84, _EWGS84
71
+ # from pygeodesy.ellipsoids import _EWGS84 # from .datums
72
+ from pygeodesy.elliptic import _ALL_LAZY, Elliptic
73
+ # from pygeodesy.errors import _incompatible # from .named
74
+ from pygeodesy.fmath import cbrt, hypot, hypot1, hypot2
75
+ from pygeodesy.fsums import Fsum, fsum1f_
76
+ from pygeodesy.interns import NN, _COMMASPACE_, _DASH_, _near_, _SPACE_, \
77
+ _spherical_, _usage
78
+ from pygeodesy.karney import _copyBit, _diff182, _fix90, _norm2, _norm180, \
79
+ _tand, _unsigned2
80
+ # from pygeodesy.lazily import _ALL_LAZY # from .elliptic
81
+ from pygeodesy.named import callername, _incompatible, _NamedBase
82
+ from pygeodesy.namedTuples import Forward4Tuple, Reverse4Tuple
83
+ from pygeodesy.props import deprecated_method, deprecated_property_RO, \
84
+ Property_RO, property_RO, _update_all, \
85
+ property_doc_
86
+ from pygeodesy.streprs import Fmt, pairs, unstr
87
+ from pygeodesy.units import Degrees, Scalar_
88
+ from pygeodesy.utily import atan1d, atan2d, _loneg, sincos2
89
+ from pygeodesy.utm import _cmlon, _LLEB, _parseUTM5, _toBand, _toXtm8, \
90
+ _to7zBlldfn, Utm, UTMError
91
+
92
+ from math import asinh, atan2, degrees, radians, sinh, sqrt
93
+
94
+ __all__ = _ALL_LAZY.etm
95
+ __version__ = '24.03.22'
96
+
97
+ _OVERFLOW = _1_EPS**2 # about 2e+31
98
+ _TAYTOL = pow(EPS, 0.6)
99
+ _TAYTOL2 = _TAYTOL * _2_0
100
+ _TOL_10 = EPS * _0_1
101
+ _TRIPS = 21 # C++ 10
102
+
103
+
104
+ def _overflow(x):
105
+ '''(INTERNAL) Like C{copysign0(OVERFLOW, B{x})}.
106
+ '''
107
+ return _copyBit(_OVERFLOW, x)
108
+
109
+
110
+ class ETMError(UTMError):
111
+ '''Exact Transverse Mercator (ETM) parse, projection or other
112
+ L{Etm} issue or L{ExactTransverseMercator} conversion failure.
113
+ '''
114
+ pass
115
+
116
+
117
+ class Etm(Utm):
118
+ '''Exact Transverse Mercator (ETM) coordinate, a sub-class of L{Utm},
119
+ a Universal Transverse Mercator (UTM) coordinate using the
120
+ L{ExactTransverseMercator} projection for highest accuracy.
121
+
122
+ @note: Conversion of (geodetic) lat- and longitudes to/from L{Etm}
123
+ coordinates is 3-4 times slower than to/from L{Utm}.
124
+
125
+ @see: Karney's U{Detailed Description<https://GeographicLib.SourceForge.io/
126
+ C++/doc/classGeographicLib_1_1TransverseMercatorExact.html#details>}.
127
+ '''
128
+ _Error = ETMError # see utm.UTMError
129
+ _exactTM = None
130
+
131
+ __init__ = Utm.__init__
132
+ '''New L{Etm} Exact Transverse Mercator coordinate, raising L{ETMError}s.
133
+
134
+ @see: L{Utm.__init__} for more information.
135
+ '''
136
+
137
+ @property_doc_(''' the ETM projection (L{ExactTransverseMercator}).''')
138
+ def exactTM(self):
139
+ '''Get the ETM projection (L{ExactTransverseMercator}).
140
+ '''
141
+ if self._exactTM is None:
142
+ self.exactTM = self.datum.exactTM # ExactTransverseMercator(datum=self.datum)
143
+ return self._exactTM
144
+
145
+ @exactTM.setter # PYCHOK setter!
146
+ def exactTM(self, exactTM):
147
+ '''Set the ETM projection (L{ExactTransverseMercator}).
148
+
149
+ @raise ETMError: The B{C{exacTM}}'s datum incompatible
150
+ with this ETM coordinate's C{datum}.
151
+ '''
152
+ _xinstanceof(ExactTransverseMercator, exactTM=exactTM)
153
+
154
+ E = self.datum.ellipsoid
155
+ if E != exactTM.ellipsoid: # may be None
156
+ raise ETMError(repr(exactTM), txt=_incompatible(repr(E)))
157
+ self._exactTM = exactTM
158
+ self._scale0 = exactTM.k0
159
+
160
+ def parse(self, strETM, name=NN):
161
+ '''Parse a string to a similar L{Etm} instance.
162
+
163
+ @arg strETM: The ETM coordinate (C{str}),
164
+ see function L{parseETM5}.
165
+ @kwarg name: Optional instance name (C{str}),
166
+ overriding this name.
167
+
168
+ @return: The instance (L{Etm}).
169
+
170
+ @raise ETMError: Invalid B{C{strETM}}.
171
+
172
+ @see: Function L{pygeodesy.parseUPS5}, L{pygeodesy.parseUTM5}
173
+ and L{pygeodesy.parseUTMUPS5}.
174
+ '''
175
+ return parseETM5(strETM, datum=self.datum, Etm=self.classof,
176
+ name=name or self.name)
177
+
178
+ @deprecated_method
179
+ def parseETM(self, strETM): # PYCHOK no cover
180
+ '''DEPRECATED, use method L{Etm.parse}.
181
+ '''
182
+ return self.parse(strETM)
183
+
184
+ def toLatLon(self, LatLon=None, unfalse=True, **unused): # PYCHOK expected
185
+ '''Convert this ETM coordinate to an (ellipsoidal) geodetic point.
186
+
187
+ @kwarg LatLon: Optional, ellipsoidal class to return the geodetic
188
+ point (C{LatLon}) or C{None}.
189
+ @kwarg unfalse: Unfalse B{C{easting}} and B{C{northing}} if
190
+ C{falsed} (C{bool}).
191
+
192
+ @return: This ETM coordinate as (B{C{LatLon}}) or a
193
+ L{LatLonDatum5Tuple}C{(lat, lon, datum, gamma,
194
+ scale)} if B{C{LatLon}} is C{None}.
195
+
196
+ @raise ETMError: This ETM coordinate's C{exacTM} and this C{datum}
197
+ incompatible or no convergence transforming to
198
+ lat- and longitude.
199
+
200
+ @raise TypeError: Invalid or non-ellipsoidal B{C{LatLon}}.
201
+ '''
202
+ if not self._latlon or self._latlon._toLLEB_args != (unfalse, self.exactTM):
203
+ self._toLLEB(unfalse=unfalse)
204
+ return self._latlon5(LatLon)
205
+
206
+ def _toLLEB(self, unfalse=True, **unused): # PYCHOK signature
207
+ '''(INTERNAL) Compute (ellipsoidal) lat- and longitude.
208
+ '''
209
+ xTM, d = self.exactTM, self.datum
210
+ # double check that this and exactTM's ellipsoid match
211
+ if xTM._E != d.ellipsoid: # PYCHOK no cover
212
+ t = repr(d.ellipsoid)
213
+ raise ETMError(repr(xTM._E), txt=_incompatible(t))
214
+
215
+ e, n = self.eastingnorthing2(falsed=not unfalse)
216
+ lon0 = _cmlon(self.zone) if bool(unfalse) == self.falsed else None
217
+ lat, lon, g, k = xTM.reverse(e, n, lon0=lon0)
218
+
219
+ ll = _LLEB(lat, lon, datum=d, name=self.name) # utm._LLEB
220
+ ll._gamma = g
221
+ ll._scale = k
222
+ self._latlon5args(ll, _toBand, unfalse, xTM)
223
+
224
+ def toUtm(self): # PYCHOK signature
225
+ '''Copy this ETM to a UTM coordinate.
226
+
227
+ @return: The UTM coordinate (L{Utm}).
228
+ '''
229
+ return self._xcopy2(Utm)
230
+
231
+
232
+ class ExactTransverseMercator(_NamedBase):
233
+ '''Pure Python version of Karney's C++ class U{TransverseMercatorExact
234
+ <https://GeographicLib.SourceForge.io/C++/doc/TransverseMercatorExact_8cpp_source.html>},
235
+ a numerically exact transverse Mercator projection, further referred to as C{TMExact}.
236
+ '''
237
+ _datum = _WGS84 # Datum
238
+ _E = _EWGS84 # Ellipsoid
239
+ _extendp = False # use extended domain
240
+ # _iteration = None # ._sigmaInv2 and ._zetaInv2
241
+ _k0 = _K0_UTM # central scale factor
242
+ _lat0 = _0_0 # central parallel
243
+ _lon0 = _0_0 # central meridian
244
+ _mu = _EWGS84.e2 # 1st eccentricity squared
245
+ _mv = _EWGS84.e21 # 1 - ._mu
246
+ _raiser = False # throw Error
247
+ _sigmaC = None # most recent _sigmaInv04 case C{int}
248
+ _zetaC = None # most recent _zetaInv04 case C{int}
249
+
250
+ def __init__(self, datum=_WGS84, lon0=0, k0=_K0_UTM, extendp=False, name=NN, raiser=False):
251
+ '''New L{ExactTransverseMercator} projection.
252
+
253
+ @kwarg datum: The I{non-spherical} datum or ellipsoid (L{Datum},
254
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
255
+ @kwarg lon0: Central meridian, default (C{degrees180}).
256
+ @kwarg k0: Central scale factor (C{float}).
257
+ @kwarg extendp: Use the I{extended} domain (C{bool}), I{standard} otherwise.
258
+ @kwarg name: Optional name for the projection (C{str}).
259
+ @kwarg raiser: If C{True}, throw an L{ETMError} for convergence failures (C{bool}).
260
+
261
+ @raise ETMError: Near-spherical B{C{datum}} or C{ellipsoid} or invalid B{C{lon0}}
262
+ or B{C{k0}}.
263
+
264
+ @see: U{Constructor TransverseMercatorExact<https://GeographicLib.SourceForge.io/
265
+ C++/doc/classGeographicLib_1_1TransverseMercatorExact.html>} for more details,
266
+ especially on B{X{extendp}}.
267
+
268
+ @note: For all 255.5K U{TMcoords.dat<https://Zenodo.org/record/32470>} tests (with
269
+ C{0 <= lat <= 84} and C{0 <= lon}) the maximum error is C{5.2e-08 .forward}
270
+ (or 52 nano-meter) easting and northing and C{3.8e-13 .reverse} (or 0.38
271
+ pico-degrees) lat- and longitude (with Python 3.7.3+, 2.7.16+, PyPy6 3.5.3
272
+ and PyPy6 2.7.13, all in 64-bit on macOS 10.13.6 High Sierra C{x86_64} and
273
+ 12.2 Monterey C{arm64} and C{"arm64_x86_64"}).
274
+ '''
275
+ if extendp:
276
+ self._extendp = True
277
+ if name:
278
+ self.name = name
279
+ if raiser:
280
+ self.raiser = True
281
+
282
+ TM = ExactTransverseMercator
283
+ if datum not in (TM._datum, TM._E, None):
284
+ self.datum = datum # invokes ._resets
285
+ if lon0 or lon0 != TM._lon0:
286
+ self.lon0 = lon0
287
+ if k0 is not TM._k0:
288
+ self.k0 = k0
289
+
290
+ @property_doc_(''' the datum (L{Datum}).''')
291
+ def datum(self):
292
+ '''Get the datum (L{Datum}) or C{None}.
293
+ '''
294
+ return self._datum
295
+
296
+ @datum.setter # PYCHOK setter!
297
+ def datum(self, datum):
298
+ '''Set the datum and ellipsoid (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
299
+
300
+ @raise ETMError: Near-spherical B{C{datum}} or C{ellipsoid}.
301
+ '''
302
+ d = _ellipsoidal_datum(datum, name=self.name) # raiser=_datum_)
303
+ self._resets(d)
304
+ self._datum = d
305
+
306
+ @Property_RO
307
+ def _e(self):
308
+ '''(INTERNAL) Get and cache C{_e}.
309
+ '''
310
+ return self._E.e
311
+
312
+ @Property_RO
313
+ def _1_e_90(self): # PYCHOK no cover
314
+ '''(INTERNAL) Get and cache C{(1 - _e) * 90}.
315
+ '''
316
+ return (_1_0 - self._e) * _90_0
317
+
318
+ @property_RO
319
+ def ellipsoid(self):
320
+ '''Get the ellipsoid (L{Ellipsoid}).
321
+ '''
322
+ return self._E
323
+
324
+ @Property_RO
325
+ def _e_PI_2(self):
326
+ '''(INTERNAL) Get and cache C{_e * PI / 2}.
327
+ '''
328
+ return self._e * PI_2
329
+
330
+ @Property_RO
331
+ def _e_PI_4_(self):
332
+ '''(INTERNAL) Get and cache C{-_e * PI / 4}.
333
+ '''
334
+ return -self._e * PI_4
335
+
336
+ @Property_RO
337
+ def _1_e_PI_2(self):
338
+ '''(INTERNAL) Get and cache C{(1 - _e) * PI / 2}.
339
+ '''
340
+ return (_1_0 - self._e) * PI_2
341
+
342
+ @Property_RO
343
+ def _1_2e_PI_2(self):
344
+ '''(INTERNAL) Get and cache C{(1 - 2 * _e) * PI / 2}.
345
+ '''
346
+ return (_1_0 - self._e * _2_0) * PI_2
347
+
348
+ @property_RO
349
+ def equatoradius(self):
350
+ '''Get the C{ellipsoid}'s equatorial radius, semi-axis (C{meter}).
351
+ '''
352
+ return self._E.a
353
+
354
+ a = equatoradius
355
+
356
+ @Property_RO
357
+ def _e_TAYTOL(self):
358
+ '''(INTERNAL) Get and cache C{e * TAYTOL}.
359
+ '''
360
+ return self._e * _TAYTOL
361
+
362
+ @Property_RO
363
+ def _Eu(self):
364
+ '''(INTERNAL) Get and cache C{Elliptic(_mu)}.
365
+ '''
366
+ return Elliptic(self._mu)
367
+
368
+ @Property_RO
369
+ def _Eu_cE(self):
370
+ '''(INTERNAL) Get and cache C{_Eu.cE}.
371
+ '''
372
+ return self._Eu.cE
373
+
374
+ def _Eu_2cE_(self, xi):
375
+ '''(INTERNAL) Return C{_Eu.cE * 2 - B{xi}}.
376
+ '''
377
+ return self._Eu_cE * _2_0 - xi
378
+
379
+ @Property_RO
380
+ def _Eu_cE_4(self):
381
+ '''(INTERNAL) Get and cache C{_Eu.cE / 4}.
382
+ '''
383
+ return self._Eu_cE / _4_0
384
+
385
+ @Property_RO
386
+ def _Eu_cK(self):
387
+ '''(INTERNAL) Get and cache C{_Eu.cK}.
388
+ '''
389
+ return self._Eu.cK
390
+
391
+ @Property_RO
392
+ def _Eu_cK_cE(self):
393
+ '''(INTERNAL) Get and cache C{_Eu.cK / _Eu.cE}.
394
+ '''
395
+ return self._Eu_cK / self._Eu_cE
396
+
397
+ @Property_RO
398
+ def _Eu_2cK_PI(self):
399
+ '''(INTERNAL) Get and cache C{_Eu.cK * 2 / PI}.
400
+ '''
401
+ return self._Eu_cK / PI_2
402
+
403
+ @Property_RO
404
+ def _Ev(self):
405
+ '''(INTERNAL) Get and cache C{Elliptic(_mv)}.
406
+ '''
407
+ return Elliptic(self._mv)
408
+
409
+ @Property_RO
410
+ def _Ev_cK(self):
411
+ '''(INTERNAL) Get and cache C{_Ev.cK}.
412
+ '''
413
+ return self._Ev.cK
414
+
415
+ @Property_RO
416
+ def _Ev_cKE(self):
417
+ '''(INTERNAL) Get and cache C{_Ev.cKE}.
418
+ '''
419
+ return self._Ev.cKE
420
+
421
+ @Property_RO
422
+ def _Ev_3cKE_4(self):
423
+ '''(INTERNAL) Get and cache C{_Ev.cKE * 3 / 4}.
424
+ '''
425
+ return self._Ev_cKE * 0.75 # _0_75
426
+
427
+ @Property_RO
428
+ def _Ev_5cKE_4(self):
429
+ '''(INTERNAL) Get and cache C{_Ev.cKE * 5 / 4}.
430
+ '''
431
+ return self._Ev_cKE * 1.25 # _1_25
432
+
433
+ @Property_RO
434
+ def extendp(self):
435
+ '''Get the domain (C{bool}), I{extended} or I{standard}.
436
+ '''
437
+ return self._extendp
438
+
439
+ @property_RO
440
+ def flattening(self):
441
+ '''Get the C{ellipsoid}'s flattening (C{scalar}).
442
+ '''
443
+ return self._E.f
444
+
445
+ f = flattening
446
+
447
+ def forward(self, lat, lon, lon0=None, name=NN): # MCCABE 13
448
+ '''Forward projection, from geographic to transverse Mercator.
449
+
450
+ @arg lat: Latitude of point (C{degrees}).
451
+ @arg lon: Longitude of point (C{degrees}).
452
+ @kwarg lon0: Central meridian (C{degrees180}), overriding
453
+ the default if not C{None}.
454
+ @kwarg name: Optional name (C{str}).
455
+
456
+ @return: L{Forward4Tuple}C{(easting, northing, gamma, scale)}.
457
+
458
+ @see: C{void TMExact::Forward(real lon0, real lat, real lon,
459
+ real &x, real &y,
460
+ real &gamma, real &k)}.
461
+
462
+ @raise ETMError: No convergence, thrown iff property
463
+ C{B{raiser}=True}.
464
+ '''
465
+ lat = _fix90(lat - self._lat0)
466
+ lon, _ = _diff182((self.lon0 if lon0 is None else lon0), lon)
467
+ if self.extendp:
468
+ backside = _lat = _lon = False
469
+ else: # enforce the parity
470
+ lat, _lat = _unsigned2(lat)
471
+ lon, _lon = _unsigned2(lon)
472
+ backside = lon > 90
473
+ if backside: # PYCHOK no cover
474
+ lon = _loneg(lon)
475
+ if lat == 0:
476
+ _lat = True
477
+
478
+ # u, v = coordinates for the Thompson TM, Lee 54
479
+ if lat == 90: # isnear90(lat)
480
+ u = self._Eu_cK
481
+ v = self._iteration = self._zetaC = 0
482
+ elif lat == 0 and lon == self._1_e_90: # PYCHOK no cover
483
+ u = self._iteration = self._zetaC = 0
484
+ v = self._Ev_cK
485
+ else: # tau = tan(phi), taup = sinh(psi)
486
+ tau, lam = _tand(lat), radians(lon)
487
+ u, v = self._zetaInv2(self._E.es_taupf(tau), lam)
488
+
489
+ sncndn6 = self._sncndn6(u, v)
490
+ y, x, _ = self._sigma3(v, *sncndn6)
491
+ g, k = (lon, self.k0) if isnear90(lat) else \
492
+ self._zetaScaled(sncndn6, ll=False)
493
+
494
+ if backside:
495
+ y, g = self._Eu_2cE_(y), _loneg(g)
496
+ y *= self._k0_a
497
+ x *= self._k0_a
498
+ if _lat:
499
+ y, g = neg_(y, g)
500
+ if _lon:
501
+ x, g = neg_(x, g)
502
+ return Forward4Tuple(x, y, g, k, iteration=self._iteration,
503
+ name=name or self.name)
504
+
505
+ def _Inv03(self, psi, dlam, _3_mv_e): # (xi, deta, _3_mv)
506
+ '''(INTERNAL) Partial C{_zetaInv04} or C{_sigmaInv04}, Case 2
507
+ '''
508
+ # atan2(dlam-psi, psi+dlam) + 45d gives arg(zeta - zeta0) in
509
+ # range [-135, 225). Subtracting 180 (multiplier is negative)
510
+ # makes range [-315, 45). Multiplying by 1/3 (for cube root)
511
+ # gives range [-105, 15). In particular the range [-90, 180]
512
+ # in zeta space maps to [-90, 0] in w space as required.
513
+ a = atan2(dlam - psi, psi + dlam) / _3_0 - PI_4
514
+ s, c = sincos2(a)
515
+ h = hypot(psi, dlam)
516
+ r = cbrt(h * _3_mv_e)
517
+ u = r * c
518
+ v = r * s + self._Ev_cK
519
+ # Error using this guess is about 0.068 * rad^(5/3)
520
+ return u, v, h
521
+
522
+ @property_RO
523
+ def iteration(self):
524
+ '''Get the most recent C{ExactTransverseMercator.forward}
525
+ or C{ExactTransverseMercator.reverse} iteration number
526
+ (C{int}) or C{None} if not available/applicable.
527
+ '''
528
+ return self._iteration
529
+
530
+ @property_doc_(''' the central scale factor (C{float}).''')
531
+ def k0(self):
532
+ '''Get the central scale factor (C{float}), aka I{C{scale0}}.
533
+ '''
534
+ return self._k0 # aka scale0
535
+
536
+ @k0.setter # PYCHOK setter!
537
+ def k0(self, k0):
538
+ '''Set the central scale factor (C{float}), aka I{C{scale0}}.
539
+
540
+ @raise ETMError: Invalid B{C{k0}}.
541
+ '''
542
+ k0 = Scalar_(k0=k0, Error=ETMError, low=_TOL_10, high=_1_0)
543
+ if self._k0 != k0:
544
+ ExactTransverseMercator._k0_a._update(self) # redo ._k0_a
545
+ self._k0 = k0
546
+
547
+ @Property_RO
548
+ def _k0_a(self):
549
+ '''(INTERNAL) Get and cache C{k0 * equatoradius}.
550
+ '''
551
+ return self.k0 * self.equatoradius
552
+
553
+ @property_doc_(''' the central meridian (C{degrees180}).''')
554
+ def lon0(self):
555
+ '''Get the central meridian (C{degrees180}).
556
+ '''
557
+ return self._lon0
558
+
559
+ @lon0.setter # PYCHOK setter!
560
+ def lon0(self, lon0):
561
+ '''Set the central meridian (C{degrees180}).
562
+
563
+ @raise ETMError: Invalid B{C{lon0}}.
564
+ '''
565
+ self._lon0 = _norm180(Degrees(lon0=lon0, Error=ETMError))
566
+
567
+ @deprecated_property_RO
568
+ def majoradius(self): # PYCHOK no cover
569
+ '''DEPRECATED, use property C{equatoradius}.'''
570
+ return self.equatoradius
571
+
572
+ @Property_RO
573
+ def _1_mu_2(self):
574
+ '''(INTERNAL) Get and cache C{_mu / 2 + 1}.
575
+ '''
576
+ return self._mu * _0_5 + _1_0
577
+
578
+ @Property_RO
579
+ def _3_mv(self):
580
+ '''(INTERNAL) Get and cache C{3 / _mv}.
581
+ '''
582
+ return _3_0 / self._mv
583
+
584
+ @Property_RO
585
+ def _3_mv_e(self):
586
+ '''(INTERNAL) Get and cache C{3 / (_mv * _e)}.
587
+ '''
588
+ return _3_0 / (self._mv * self._e)
589
+
590
+ def _Newton2(self, taup, lam, u, v, C, *psi): # or (xi, eta, u, v)
591
+ '''(INTERNAL) Invert C{_zetaInv2} or C{_sigmaInv2} using Newton's method.
592
+
593
+ @return: 2-Tuple C{(u, v)}.
594
+
595
+ @raise ETMError: No convergence.
596
+ '''
597
+ sca1, tol2 = _1_0, _TOL_10
598
+ if psi: # _zetaInv2
599
+ sca1 = sca1 / hypot1(taup) # /= chokes PyChecker
600
+ tol2 = tol2 / max(psi[0], _1_0)**2
601
+
602
+ _zeta3 = self._zeta3
603
+ _zetaDwd2 = self._zetaDwd2
604
+ else: # _sigmaInv2
605
+ _zeta3 = self._sigma3
606
+ _zetaDwd2 = self._sigmaDwd2
607
+
608
+ d2, r = tol2, self.raiser
609
+ _U_2 = Fsum(u).fsum2_
610
+ _V_2 = Fsum(v).fsum2_
611
+ # min iterations 2, max 6 or 7, mean 3.9 or 4.0
612
+ for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC
613
+ sncndn6 = self._sncndn6(u, v)
614
+ du, dv = _zetaDwd2(*sncndn6)
615
+ T, L, _ = _zeta3(v, *sncndn6)
616
+ T = (taup - T) * sca1
617
+ L -= lam
618
+ u, dU = _U_2(T * du, L * dv)
619
+ v, dV = _V_2(T * dv, -L * du)
620
+ if d2 < tol2:
621
+ r = False
622
+ break
623
+ d2 = hypot2(dU, dV)
624
+
625
+ self._iteration = i
626
+ if r: # PYCHOK no cover
627
+ n = callername(up=2, underOK=True)
628
+ t = unstr(n, taup, lam, u, v, C=C)
629
+ raise ETMError(Fmt.no_convergence(d2, tol2), txt=t)
630
+ return u, v
631
+
632
+ @property_doc_(''' raise an L{ETMError} for convergence failures (C{bool}).''')
633
+ def raiser(self):
634
+ '''Get the error setting (C{bool}).
635
+ '''
636
+ return self._raiser
637
+
638
+ @raiser.setter # PYCHOK setter!
639
+ def raiser(self, raiser):
640
+ '''Set the error setting (C{bool}), if C{True} throw an L{ETMError}
641
+ for convergence failures.
642
+ '''
643
+ self._raiser = bool(raiser)
644
+
645
+ def reset(self, lat0, lon0):
646
+ '''Set the central parallel and meridian.
647
+
648
+ @arg lat0: Latitude of the central parallel (C{degrees90}).
649
+ @arg lon0: Longitude of the central parallel (C{degrees180}).
650
+
651
+ @return: 2-Tuple C{(lat0, lon0)} of the previous central
652
+ parallel and meridian.
653
+
654
+ @raise ETMError: Invalid B{C{lat0}} or B{C{lon0}}.
655
+ '''
656
+ t = self._lat0, self.lon0
657
+ self._lat0 = _fix90(Degrees(lat0=lat0, Error=ETMError))
658
+ self. lon0 = lon0
659
+ return t
660
+
661
+ def _resets(self, datum):
662
+ '''(INTERNAL) Set the ellipsoid and elliptic moduli.
663
+
664
+ @arg datum: Ellipsoidal datum (C{Datum}).
665
+
666
+ @raise ETMError: Near-spherical B{C{datum}} or C{ellipsoid}.
667
+ '''
668
+ E = datum.ellipsoid
669
+ mu = E.e2 # .eccentricity1st2
670
+ mv = E.e21 # _1_0 - mu
671
+ if isnear0(E.e) or isnear0(mu, eps0=EPS02) \
672
+ or isnear0(mv, eps0=EPS02): # or sqrt(mu) != E.e
673
+ raise ETMError(ellipsoid=E, txt=_near_(_spherical_))
674
+
675
+ if self._datum or self._E:
676
+ _i = ExactTransverseMercator.iteration._uname
677
+ _update_all(self, _i, '_sigmaC', '_zetaC') # _under
678
+
679
+ self._E = E
680
+ self._mu = mu
681
+ self._mv = mv
682
+
683
+ def reverse(self, x, y, lon0=None, name=NN):
684
+ '''Reverse projection, from Transverse Mercator to geographic.
685
+
686
+ @arg x: Easting of point (C{meters}).
687
+ @arg y: Northing of point (C{meters}).
688
+ @kwarg lon0: Central meridian (C{degrees180}), overriding
689
+ the default if not C{None}.
690
+ @kwarg name: Optional name (C{str}).
691
+
692
+ @return: L{Reverse4Tuple}C{(lat, lon, gamma, scale)}.
693
+
694
+ @see: C{void TMExact::Reverse(real lon0, real x, real y,
695
+ real &lat, real &lon,
696
+ real &gamma, real &k)}
697
+
698
+ @raise ETMError: No convergence, thrown iff property
699
+ C{B{raiser}=True}.
700
+ '''
701
+ # undoes the steps in .forward.
702
+ xi = y / self._k0_a
703
+ eta = x / self._k0_a
704
+ if self.extendp:
705
+ backside = _lat = _lon = False
706
+ else: # enforce the parity
707
+ eta, _lon = _unsigned2(eta)
708
+ xi, _lat = _unsigned2(xi)
709
+ backside = xi > self._Eu_cE
710
+ if backside: # PYCHOK no cover
711
+ xi = self._Eu_2cE_(xi)
712
+
713
+ # u, v = coordinates for the Thompson TM, Lee 54
714
+ if xi or eta != self._Ev_cKE:
715
+ u, v = self._sigmaInv2(xi, eta)
716
+ else: # PYCHOK no cover
717
+ u = self._iteration = self._sigmaC = 0
718
+ v = self._Ev_cK
719
+
720
+ if v or u != self._Eu_cK:
721
+ g, k, lat, lon = self._zetaScaled(self._sncndn6(u, v))
722
+ else: # PYCHOK no cover
723
+ g, k, lat, lon = _0_0, self.k0, _90_0, _0_0
724
+
725
+ if backside: # PYCHOK no cover
726
+ lon, g = _loneg(lon), _loneg(g)
727
+ if _lat:
728
+ lat, g = neg_(lat, g)
729
+ if _lon:
730
+ lon, g = neg_(lon, g)
731
+ lat += self._lat0
732
+ lon += self._lon0 if lon0 is None else _norm180(lon0)
733
+ return Reverse4Tuple(lat, _norm180(lon), g, k, # _norm180(lat)
734
+ iteration=self._iteration,
735
+ name=name or self.name)
736
+
737
+ def _scaled2(self, tau, d2, snu, cnu, dnu, snv, cnv, dnv):
738
+ '''(INTERNAL) C{scaled}.
739
+
740
+ @note: Argument B{C{d2}} is C{_mu * cnu**2 + _mv * cnv**2}
741
+ from C{._zeta3}.
742
+
743
+ @return: 2-Tuple C{(convergence, scale)}.
744
+
745
+ @see: C{void TMExact::Scale(real tau, real /*lam*/,
746
+ real snu, real cnu, real dnu,
747
+ real snv, real cnv, real dnv,
748
+ real &gamma, real &k)}.
749
+ '''
750
+ mu, mv = self._mu, self._mv
751
+ cnudnv = cnu * dnv
752
+ # Lee 55.12 -- negated for our sign convention. g gives
753
+ # the bearing (clockwise from true north) of grid north
754
+ g = atan2d(mv * cnv * snv * snu, cnudnv * dnu)
755
+ # Lee 55.13 with nu given by Lee 9.1 -- in sqrt change
756
+ # the numerator from (1 - snu^2 * dnv^2) to (_mv * snv^2
757
+ # + cnu^2 * dnv^2) to maintain accuracy near phi = 90
758
+ # and change the denomintor from (dnu^2 + dnv^2 - 1) to
759
+ # (_mu * cnu^2 + _mv * cnv^2) to maintain accuracy near
760
+ # phi = 0, lam = 90 * (1 - e). Similarly rewrite sqrt in
761
+ # 9.1 as _mv + _mu * c^2 instead of 1 - _mu * sin(phi)^2
762
+ if d2 > 0:
763
+ # originally: sec2 = 1 + tau**2 # sec(phi)^2
764
+ # d2 = (mu * cnu**2 + mv * cnv**2)
765
+ # q2 = (mv * snv**2 + cnudnv**2) / d2
766
+ # k = sqrt(mv + mu / sec2) * sqrt(sec2) * sqrt(q2)
767
+ # = sqrt(mv * sec2 + mu) * sqrt(q2)
768
+ # = sqrt(mv + mv * tau**2 + mu) * sqrt(q2)
769
+ k, q2 = _0_0, (mv * snv**2 + cnudnv**2)
770
+ if q2 > 0:
771
+ k2 = fsum1f_(mu, mv, mv * tau**2)
772
+ if k2 > 0:
773
+ k = sqrt(k2) * sqrt(q2 / d2) * self.k0
774
+ else:
775
+ k = _OVERFLOW
776
+ return g, k
777
+
778
+ def _sigma3(self, v, snu, cnu, dnu, snv, cnv, dnv):
779
+ '''(INTERNAL) C{sigma}.
780
+
781
+ @return: 3-Tuple C{(xi, eta, d2)}.
782
+
783
+ @see: C{void TMExact::sigma(real /*u*/, real snu, real cnu, real dnu,
784
+ real v, real snv, real cnv, real dnv,
785
+ real &xi, real &eta)}.
786
+
787
+ @raise ETMError: No convergence.
788
+ '''
789
+ mu = self._mu * cnu
790
+ mv = self._mv * cnv
791
+ # Lee 55.4 writing
792
+ # dnu^2 + dnv^2 - 1 = _mu * cnu^2 + _mv * cnv^2
793
+ d2 = cnu * mu + cnv * mv
794
+ mu *= snu * dnu
795
+ mv *= snv * dnv
796
+ if d2 > 0: # /= chokes PyChecker
797
+ mu = mu / d2
798
+ mv = mv / d2
799
+ else:
800
+ mu, mv = map1(_overflow, mu, mv)
801
+ xi = self._Eu.fE(snu, cnu, dnu) - mu
802
+ v -= self._Ev.fE(snv, cnv, dnv) - mv
803
+ return xi, v, d2
804
+
805
+ def _sigmaDwd2(self, snu, cnu, dnu, snv, cnv, dnv):
806
+ '''(INTERNAL) C{sigmaDwd}.
807
+
808
+ @return: 2-Tuple C{(du, dv)}.
809
+
810
+ @see: C{void TMExact::dwdsigma(real /*u*/, real snu, real cnu, real dnu,
811
+ real /*v*/, real snv, real cnv, real dnv,
812
+ real &du, real &dv)}.
813
+ '''
814
+ snuv = snu * snv
815
+ # Reciprocal of 55.9: dw / ds = dn(w)^2/_mv,
816
+ # expanding complex dn(w) using A+S 16.21.4
817
+ d = self._mv * (cnv**2 + self._mu * snuv**2)**2
818
+ r = cnv * dnu * dnv
819
+ i = cnu * snuv * self._mu
820
+ du = (r**2 - i**2) / d
821
+ dv = neg(r * i * _2_0 / d)
822
+ return du, dv
823
+
824
+ def _sigmaInv2(self, xi, eta):
825
+ '''(INTERNAL) Invert C{sigma} using Newton's method.
826
+
827
+ @return: 2-Tuple C{(u, v)}.
828
+
829
+ @see: C{void TMExact::sigmainv(real xi, real eta,
830
+ real &u, real &v)}.
831
+
832
+ @raise ETMError: No convergence.
833
+ '''
834
+ u, v, t, self._sigmaC = self._sigmaInv04(xi, eta)
835
+ if not t:
836
+ u, v = self._Newton2(xi, eta, u, v, self._sigmaC)
837
+ return u, v
838
+
839
+ def _sigmaInv04(self, xi, eta):
840
+ '''(INTERNAL) Starting point for C{sigmaInv}.
841
+
842
+ @return: 4-Tuple C{(u, v, trip, Case)}.
843
+
844
+ @see: C{bool TMExact::sigmainv0(real xi, real eta,
845
+ real &u, real &v)}.
846
+ '''
847
+ t = False
848
+ d = eta - self._Ev_cKE
849
+ if eta > self._Ev_5cKE_4 or (xi < d and xi < -self._Eu_cE_4):
850
+ # sigma as a simple pole at
851
+ # w = w0 = Eu.K() + i * Ev.K()
852
+ # and sigma is approximated by
853
+ # sigma = (Eu.E() + i * Ev.KE()) + 1 / (w - w0)
854
+ u, v = _norm2(xi - self._Eu_cE, -d)
855
+ u += self._Eu_cK
856
+ v += self._Ev_cK
857
+ C = 1
858
+
859
+ elif (eta > self._Ev_3cKE_4 and xi < self._Eu_cE_4) or d > 0:
860
+ # At w = w0 = i * Ev.K(), we have
861
+ # sigma = sigma0 = i * Ev.KE()
862
+ # sigma' = sigma'' = 0
863
+ # including the next term in the Taylor series gives:
864
+ # sigma = sigma0 - _mv / 3 * (w - w0)^3
865
+ # When inverting this, we map arg(w - w0) = [-pi/2, -pi/6]
866
+ # to arg(sigma - sigma0) = [-pi/2, pi/2] mapping arg =
867
+ # [-pi/2, -pi/6] to [-pi/2, pi/2]
868
+ u, v, h = self._Inv03(xi, d, self._3_mv)
869
+ t = h < _TAYTOL2
870
+ C = 2
871
+
872
+ else: # use w = sigma * Eu.K/Eu.E (correct in limit _e -> 0)
873
+ u = v = self._Eu_cK_cE
874
+ u *= xi
875
+ v *= eta
876
+ C = 3
877
+
878
+ return u, v, t, C
879
+
880
+ def _sncndn6(self, u, v):
881
+ '''(INTERNAL) Get 6-tuple C{(snu, cnu, dnu, snv, cnv, dnv)}.
882
+ '''
883
+ # snu, cnu, dnu = self._Eu.sncndn(u)
884
+ # snv, cnv, dnv = self._Ev.sncndn(v)
885
+ return self._Eu.sncndn(u) + self._Ev.sncndn(v)
886
+
887
+ def toStr(self, joined=_COMMASPACE_, **kwds): # PYCHOK signature
888
+ '''Return a C{str} representation.
889
+
890
+ @kwarg joined: Separator to join the attribute strings
891
+ (C{str} or C{None} or C{NN} for non-joined).
892
+ @kwarg kwds: Optional, overriding keyword arguments.
893
+ '''
894
+ d = dict(datum=self.datum.name, lon0=self.lon0,
895
+ k0=self.k0, extendp=self.extendp)
896
+ if self.name:
897
+ d.update(name=self.name)
898
+ t = pairs(d, **kwds)
899
+ return joined.join(t) if joined else t
900
+
901
+ def _zeta3(self, unused, snu, cnu, dnu, snv, cnv, dnv): # _sigma3 signature
902
+ '''(INTERNAL) C{zeta}.
903
+
904
+ @return: 3-Tuple C{(taup, lambda, d2)}.
905
+
906
+ @see: C{void TMExact::zeta(real /*u*/, real snu, real cnu, real dnu,
907
+ real /*v*/, real snv, real cnv, real dnv,
908
+ real &taup, real &lam)}
909
+ '''
910
+ e, cnu2, mv = self._e, cnu**2, self._mv
911
+ # Overflow value like atan(overflow) = pi/2
912
+ t1 = t2 = _overflow(snu)
913
+ # Lee 54.17 but write
914
+ # atanh(snu * dnv) = asinh(snu * dnv / sqrt(cnu^2 + _mv * snu^2 * snv^2))
915
+ # atanh(_e * snu / dnv) = asinh(_e * snu / sqrt(_mu * cnu^2 + _mv * cnv^2))
916
+ d1 = cnu2 + mv * (snu * snv)**2
917
+ if d1 > EPS02: # _EPSmin
918
+ t1 = snu * dnv / sqrt(d1)
919
+ else:
920
+ d1 = 0
921
+ d2 = self._mu * cnu2 + mv * cnv**2
922
+ if d2 > EPS02: # _EPSmin
923
+ t2 = sinh(e * asinh(e * snu / sqrt(d2)))
924
+ else:
925
+ d2 = 0
926
+ # psi = asinh(t1) - asinh(t2)
927
+ # taup = sinh(psi)
928
+ taup = t1 * hypot1(t2) - t2 * hypot1(t1)
929
+ lam = (atan2(dnu * snv, cnu * cnv) -
930
+ atan2(cnu * snv * e, dnu * cnv) * e) if d1 and d2 else _0_0
931
+ return taup, lam, d2
932
+
933
+ def _zetaDwd2(self, snu, cnu, dnu, snv, cnv, dnv):
934
+ '''(INTERNAL) C{zetaDwd}.
935
+
936
+ @return: 2-Tuple C{(du, dv)}.
937
+
938
+ @see: C{void TMExact::dwdzeta(real /*u*/, real snu, real cnu, real dnu,
939
+ real /*v*/, real snv, real cnv, real dnv,
940
+ real &du, real &dv)}.
941
+ '''
942
+ cnu2 = cnu**2 * self._mu
943
+ cnv2 = cnv**2
944
+ dnuv = dnu * dnv
945
+ dnuv2 = dnuv**2
946
+ snuv = snu * snv
947
+ snuv2 = snuv**2 * self._mu
948
+ # Lee 54.21 but write (see A+S 16.21.4)
949
+ # (1 - dnu^2 * snv^2) = (cnv^2 + _mu * snu^2 * snv^2)
950
+ d = self._mv * (cnv2 + snuv2)**2 # max(d, EPS02)?
951
+ du = cnu * dnuv * (cnv2 - snuv2) / d
952
+ dv = cnv * snuv * (cnu2 + dnuv2) / d
953
+ return du, neg(dv)
954
+
955
+ def _zetaInv2(self, taup, lam):
956
+ '''(INTERNAL) Invert C{zeta} using Newton's method.
957
+
958
+ @return: 2-Tuple C{(u, v)}.
959
+
960
+ @see: C{void TMExact::zetainv(real taup, real lam,
961
+ real &u, real &v)}.
962
+
963
+ @raise ETMError: No convergence.
964
+ '''
965
+ psi = asinh(taup)
966
+ u, v, t, self._zetaC = self._zetaInv04(psi, lam)
967
+ if not t:
968
+ u, v = self._Newton2(taup, lam, u, v, self._zetaC, psi)
969
+ return u, v
970
+
971
+ def _zetaInv04(self, psi, lam):
972
+ '''(INTERNAL) Starting point for C{zetaInv}.
973
+
974
+ @return: 4-Tuple C{(u, v, trip, Case)}.
975
+
976
+ @see: C{bool TMExact::zetainv0(real psi, real lam, # radians
977
+ real &u, real &v)}.
978
+ '''
979
+ if lam > self._1_2e_PI_2:
980
+ d = lam - self._1_e_PI_2
981
+ if psi < d and psi < self._e_PI_4_: # PYCHOK no cover
982
+ # N.B. this branch is normally *not* taken because psi < 0
983
+ # is converted psi > 0 by .forward. There's a log singularity
984
+ # at w = w0 = Eu.K() + i * Ev.K(), corresponding to the south
985
+ # pole, where we have, approximately
986
+ # psi = _e + i * pi/2 - _e * atanh(cos(i * (w - w0)/(1 + _mu/2)))
987
+ # Inverting this gives:
988
+ e = self._e # eccentricity
989
+ s, c = sincos2((PI_2 - lam) / e)
990
+ h, r = sinh(_1_0 - psi / e), self._1_mu_2
991
+ u = self._Eu_cK - r * asinh(s / hypot(c, h))
992
+ v = self._Ev_cK - r * atan2(c, h)
993
+ return u, v, False, 1
994
+
995
+ elif psi < self._e_PI_2:
996
+ # At w = w0 = i * Ev.K(), we have
997
+ # zeta = zeta0 = i * (1 - _e) * pi/2
998
+ # zeta' = zeta'' = 0
999
+ # including the next term in the Taylor series gives:
1000
+ # zeta = zeta0 - (_mv * _e) / 3 * (w - w0)^3
1001
+ # When inverting this, we map arg(w - w0) = [-90, 0]
1002
+ # to arg(zeta - zeta0) = [-90, 180]
1003
+ u, v, h = self._Inv03(psi, d, self._3_mv_e)
1004
+ return u, v, (h < self._e_TAYTOL), 2
1005
+
1006
+ # Use spherical TM, Lee 12.6 -- writing C{atanh(sin(lam) /
1007
+ # cosh(psi)) = asinh(sin(lam) / hypot(cos(lam), sinh(psi)))}.
1008
+ # This takes care of the log singularity at C{zeta = Eu.K()},
1009
+ # corresponding to the north pole.
1010
+ s, c = sincos2(lam)
1011
+ h, r = sinh(psi), self._Eu_2cK_PI
1012
+ # But scale to put 90, 0 on the right place
1013
+ u = r * atan2(h, c)
1014
+ v = r * asinh(s / hypot(h, c))
1015
+ return u, v, False, 3
1016
+
1017
+ def _zetaScaled(self, sncndn6, ll=True):
1018
+ '''(INTERNAL) Recompute (T, L) from (u, v) to improve accuracy of Scale.
1019
+
1020
+ @arg sncndn6: 6-Tuple C{(snu, cnu, dnu, snv, cnv, dnv)}.
1021
+
1022
+ @return: 2-Tuple C{(g, k)} if not C{B{ll}} else
1023
+ 4-tuple C{(g, k, lat, lon)}.
1024
+ '''
1025
+ t, lam, d2 = self._zeta3(None, *sncndn6)
1026
+ tau = self._E.es_tauf(t)
1027
+ g_k = self._scaled2(tau, d2, *sncndn6)
1028
+ if ll:
1029
+ g_k += atan1d(tau), degrees(lam)
1030
+ return g_k # or (g, k, lat, lon)
1031
+
1032
+
1033
+ def parseETM5(strUTM, datum=_WGS84, Etm=Etm, falsed=True, name=NN):
1034
+ '''Parse a string representing a UTM coordinate, consisting
1035
+ of C{"zone[band] hemisphere easting northing"}.
1036
+
1037
+ @arg strUTM: A UTM coordinate (C{str}).
1038
+ @kwarg datum: Optional datum to use (L{Datum}, L{Ellipsoid},
1039
+ L{Ellipsoid2} or L{a_f2Tuple}).
1040
+ @kwarg Etm: Optional class to return the UTM coordinate
1041
+ (L{Etm}) or C{None}.
1042
+ @kwarg falsed: Both easting and northing are C{falsed} (C{bool}).
1043
+ @kwarg name: Optional B{C{Etm}} name (C{str}).
1044
+
1045
+ @return: The UTM coordinate (B{C{Etm}}) or if B{C{Etm}} is
1046
+ C{None}, a L{UtmUps5Tuple}C{(zone, hemipole, easting,
1047
+ northing, band)}. The C{hemipole} is the hemisphere
1048
+ C{'N'|'S'}.
1049
+
1050
+ @raise ETMError: Invalid B{C{strUTM}}.
1051
+
1052
+ @raise TypeError: Invalid or near-spherical B{C{datum}}.
1053
+ '''
1054
+ r = _parseUTM5(strUTM, datum, Etm, falsed, Error=ETMError, name=name)
1055
+ return r
1056
+
1057
+
1058
+ def toEtm8(latlon, lon=None, datum=None, Etm=Etm, falsed=True,
1059
+ name=NN, strict=True,
1060
+ zone=None, **cmoff):
1061
+ '''Convert a geodetic lat-/longitude to an ETM coordinate.
1062
+
1063
+ @arg latlon: Latitude (C{degrees}) or an (ellipsoidal)
1064
+ geodetic C{LatLon} instance.
1065
+ @kwarg lon: Optional longitude (C{degrees}) or C{None}.
1066
+ @kwarg datum: Optional datum for the ETM coordinate,
1067
+ overriding B{C{latlon}}'s datum (L{Datum},
1068
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
1069
+ @kwarg Etm: Optional class to return the ETM coordinate
1070
+ (L{Etm}) or C{None}.
1071
+ @kwarg falsed: False both easting and northing (C{bool}).
1072
+ @kwarg name: Optional B{C{Utm}} name (C{str}).
1073
+ @kwarg strict: Restrict B{C{lat}} to UTM ranges (C{bool}).
1074
+ @kwarg zone: Optional UTM zone to enforce (C{int} or C{str}).
1075
+ @kwarg cmoff: DEPRECATED, use B{C{falsed}}. Offset longitude
1076
+ from the zone's central meridian (C{bool}).
1077
+
1078
+ @return: The ETM coordinate as an B{C{Etm}} instance or a
1079
+ L{UtmUps8Tuple}C{(zone, hemipole, easting, northing,
1080
+ band, datum, gamma, scale)} if B{C{Etm}} is C{None}
1081
+ or not B{C{falsed}}. The C{hemipole} is the C{'N'|'S'}
1082
+ hemisphere.
1083
+
1084
+ @raise ETMError: No convergence transforming to ETM easting
1085
+ and northing.
1086
+
1087
+ @raise ETMError: Invalid B{C{zone}} or near-spherical or
1088
+ incompatible B{C{datum}} or C{ellipsoid}.
1089
+
1090
+ @raise RangeError: If B{C{lat}} outside the valid UTM bands or
1091
+ if B{C{lat}} or B{C{lon}} outside the valid
1092
+ range and L{pygeodesy.rangerrors} set to C{True}.
1093
+
1094
+ @raise TypeError: Invalid or near-spherical B{C{datum}} or
1095
+ B{C{latlon}} not ellipsoidal.
1096
+
1097
+ @raise ValueError: The B{C{lon}} value is missing or B{C{latlon}}
1098
+ is invalid.
1099
+ '''
1100
+ z, B, lat, lon, d, f, name = _to7zBlldfn(latlon, lon, datum,
1101
+ falsed, name, zone,
1102
+ strict, ETMError, **cmoff)
1103
+ lon0 = _cmlon(z) if f else None
1104
+ x, y, g, k = d.exactTM.forward(lat, lon, lon0=lon0)
1105
+
1106
+ return _toXtm8(Etm, z, lat, x, y, B, d, g, k, f,
1107
+ name, latlon, d.exactTM, Error=ETMError)
1108
+
1109
+
1110
+ if __name__ == '__main__': # MCCABE 13
1111
+
1112
+ from pygeodesy import fstr, KTransverseMercator, printf
1113
+ from sys import argv, exit as _exit
1114
+
1115
+ # mimick some of I{Karney}'s utility C{TransverseMercatorProj}
1116
+ _f = _r = _s = _t = False
1117
+ _as = argv[1:]
1118
+ while _as and _as[0].startswith(_DASH_):
1119
+ _a = _as.pop(0)
1120
+ if len(_a) < 2:
1121
+ _exit('%s: option %r invalid' % (_usage(*argv), _a))
1122
+ elif '-forward'.startswith(_a):
1123
+ _f, _r = True, False
1124
+ elif '-reverse'.startswith(_a):
1125
+ _f, _r = False, True
1126
+ elif '-series'.startswith(_a):
1127
+ _s, _t = True, False
1128
+ elif _a == '-t':
1129
+ _s, _t = False, True
1130
+ elif '-help'.startswith(_a):
1131
+ _exit(_usage(argv[0], '[-s | -t]',
1132
+ '[-f[orward] <lat> <lon>',
1133
+ '| -r[everse] <easting> <northing>',
1134
+ '| <lat> <lon>]',
1135
+ '| -h[elp]'))
1136
+ else:
1137
+ _exit('%s: option %r not supported' % (_usage(*argv), _a))
1138
+ if len(_as) > 1:
1139
+ f2 = map1(float, *_as[:2])
1140
+ else:
1141
+ _exit('%s ...: incomplete' % (_usage(*argv),))
1142
+
1143
+ if _s: # -series
1144
+ tm = KTransverseMercator()
1145
+ else:
1146
+ tm = ExactTransverseMercator(extendp=_t)
1147
+
1148
+ if _f:
1149
+ t = tm.forward(*f2)
1150
+ elif _r:
1151
+ t = tm.reverse(*f2)
1152
+ else:
1153
+ t = tm.forward(*f2)
1154
+ printf('%s: %s', tm.classname, fstr(t, sep=_SPACE_))
1155
+ t = tm.reverse(t.easting, t.northing)
1156
+ printf('%s: %s', tm.classname, fstr(t, sep=_SPACE_))
1157
+
1158
+
1159
+ # % python3 -m pygeodesy.etm 33.33 44.44
1160
+ # ExactTransverseMercator: 4276926.114804 4727193.767015 28.375537 1.233325
1161
+ # ExactTransverseMercator: 33.33 44.44 28.375537 1.233325
1162
+
1163
+ # % python3 -m pygeodesy.etm -s 33.33 44.44
1164
+ # KTransverseMercator: 4276926.114804 4727193.767015 28.375537 1.233325
1165
+ # KTransverseMercator: 33.33 44.44 28.375537 1.233325
1166
+
1167
+ # % echo 33.33 44.44 | .../bin/TransverseMercatorProj
1168
+ # 4276926.114804 4727193.767015 28.375536563148 1.233325101778
1169
+
1170
+ # **) MIT License
1171
+ #
1172
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1173
+ #
1174
+ # Permission is hereby granted, free of charge, to any person obtaining a
1175
+ # copy of this software and associated documentation files (the "Software"),
1176
+ # to deal in the Software without restriction, including without limitation
1177
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
1178
+ # and/or sell copies of the Software, and to permit persons to whom the
1179
+ # Software is furnished to do so, subject to the following conditions:
1180
+ #
1181
+ # The above copyright notice and this permission notice shall be included
1182
+ # in all copies or substantial portions of the Software.
1183
+ #
1184
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1185
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1186
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1187
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1188
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1189
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1190
+ # OTHER DEALINGS IN THE SOFTWARE.