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.
- PyGeodesy-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- 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.
|