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/utm.py ADDED
@@ -0,0 +1,762 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''I{Veness}' Universal Transverse Mercator (UTM) projection.
5
+
6
+ Classes L{Utm} and L{UTMError} and functions L{parseUTM5}, L{toUtm8} and
7
+ L{utmZoneBand5}.
8
+
9
+ Pure Python implementation of UTM / WGS-84 conversion functions using
10
+ an ellipsoidal earth model, transcoded from JavaScript originals by
11
+ I{(C) Chris Veness 2011-2016} published under the same MIT Licence**, see
12
+ U{UTM<https://www.Movable-Type.co.UK/scripts/latlong-utm-mgrs.html>} and
13
+ U{Module utm<https://www.Movable-Type.co.UK/scripts/geodesy/docs/module-utm.html>}.
14
+
15
+ The U{UTM<https://WikiPedia.org/wiki/Universal_Transverse_Mercator_coordinate_system>}
16
+ system is a 2-dimensional Cartesian coordinate system providing another way
17
+ to identify locations on the surface of the earth. UTM is a set of 60
18
+ transverse Mercator projections, normally based on the WGS-84 ellipsoid.
19
+ Within each zone, coordinates are represented as B{C{easting}}s and B{C{northing}}s,
20
+ measured in metres.
21
+
22
+ This module includes some of I{Charles Karney}'s U{'Transverse Mercator with an
23
+ accuracy of a few nanometers'<https://ArXiv.org/pdf/1002.1417v3.pdf>}, 2011
24
+ (building on Krüger's U{'Konforme Abbildung des Erdellipsoids in der Ebene'
25
+ <https://bib.GFZ-Potsdam.DE/pub/digi/krueger2.pdf>}, 1912) and C++ class
26
+ U{TransverseMercator<https://GeographicLib.SourceForge.io/C++/doc/
27
+ classGeographicLib_1_1TransverseMercator.html>}.
28
+
29
+ Some other references are U{Universal Transverse Mercator coordinate system
30
+ <https://WikiPedia.org/wiki/Universal_Transverse_Mercator_coordinate_system>},
31
+ U{Transverse Mercator Projection<https://GeographicLib.SourceForge.io/tm.html>}
32
+ and Henrik Seidel U{'Die Mathematik der Gauß-Krueger-Abbildung'
33
+ <https://DE.WikiPedia.org/wiki/Gauß-Krüger-Koordinatensystem>}, 2006.
34
+ '''
35
+
36
+ from pygeodesy.basics import len2, map2, neg # splice
37
+ from pygeodesy.constants import EPS, EPS0, _K0_UTM, _0_0, _0_0001
38
+ from pygeodesy.datums import _ellipsoidal_datum, _WGS84
39
+ from pygeodesy.dms import degDMS, parseDMS2
40
+ from pygeodesy.errors import MGRSError, RangeError, _ValueError, \
41
+ _xkwds_get
42
+ from pygeodesy.fmath import fdot3, hypot, hypot1, _operator
43
+ from pygeodesy.interns import MISSING, NN, _by_, _COMMASPACE_, _N_, \
44
+ _NS_, _outside_, _range_, _S_, _scale0_, \
45
+ _SPACE_, _UTM_, _V_, _X_, _zone_, _under
46
+ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
47
+ # from pygeodesy.named import _xnamed # from .utmupsBase
48
+ from pygeodesy.namedTuples import EasNor2Tuple, UtmUps5Tuple, \
49
+ UtmUps8Tuple, UtmUpsLatLon5Tuple
50
+ from pygeodesy.props import deprecated_method, property_doc_, \
51
+ Property_RO
52
+ from pygeodesy.streprs import Fmt, unstr
53
+ from pygeodesy.units import Band, Int, Lat, Lon, Meter, Zone
54
+ from pygeodesy.utily import atan1, degrees90, degrees180, sincos2
55
+ from pygeodesy.utmupsBase import _hemi, _LLEB, _parseUTMUPS5, _to4lldn, \
56
+ _to3zBhp, _to3zll, _UPS_LATS, _UPS_ZONE, \
57
+ _UTM_LAT_MAX, _UTM_ZONE_MAX, \
58
+ _UTM_LAT_MIN, _UTM_ZONE_MIN, \
59
+ _UTM_ZONE_OFF_MAX, UtmUpsBase, _xnamed
60
+
61
+ from math import asinh, atanh, atan2, cos, cosh, degrees, fabs, \
62
+ radians, sin, sinh, tan, tanh
63
+ # import operator as _operator # from .fmath
64
+
65
+ __all__ = _ALL_LAZY.utm
66
+ __version__ = '24.02.29'
67
+
68
+ _Bands = 'CDEFGHJKLMNPQRSTUVWXX' # UTM latitude bands C..X (no
69
+ # I|O) 8° each, covering 80°S to 84°N and X repeated for 80-84°N
70
+ _bandLat_ = 'bandLat'
71
+ _FalseEasting = Meter( 500e3) # falsed offset (C{meter})
72
+ _FalseNorthing = Meter(10000e3) # falsed offset (C{meter})
73
+ _SvalbardXzone = {32: 9, 34: 21, 36: 33} # [zone] longitude
74
+
75
+
76
+ class UTMError(_ValueError):
77
+ '''Universal Transverse Mercator (UTM parse or other L{Utm} issue.
78
+ '''
79
+ pass
80
+
81
+
82
+ class _Kseries(object):
83
+ '''(INTERNAL) Alpha or Beta Krüger series.
84
+
85
+ Krüger series summations for B{C{eta}}, B{C{ksi}}, B{C{p}} and B{C{q}},
86
+ caching the C{cos}, C{cosh}, C{sin} and C{sinh} values for
87
+ the given B{C{eta}} and B{C{ksi}} angles (in C{radians}).
88
+ '''
89
+ def __init__(self, AB, x, y):
90
+ '''(INTERNAL) New Alpha or Beta Krüger series
91
+
92
+ @arg AB: Krüger Alpha or Beta series coefficients
93
+ (C{4-, 6- or 8-tuple}).
94
+ @arg x: Eta angle (C{radians}).
95
+ @arg y: Ksi angle (C{radians}).
96
+ '''
97
+ n, j2 = len2(range(2, len(AB) * 2 + 1, 2))
98
+ _m2, _x = map2, _operator.mul
99
+
100
+ self._ab = AB
101
+ self._pq = _m2(_x, j2, AB)
102
+ # assert len(self._ab) == len(self._pq) == n
103
+
104
+ x2 = _m2(_x, j2, (x,) * n)
105
+ self._chx = _m2(cosh, x2)
106
+ self._shx = _m2(sinh, x2)
107
+ # assert len(x2) == len(self._chx) == len(self._shx) == n
108
+
109
+ y2 = _m2(_x, j2, (y,) * n)
110
+ self._cy = _m2(cos, y2)
111
+ self._sy = _m2(sin, y2)
112
+ # self._sy, self._cy = splice(sincos2(*y2)) # PYCHOK false
113
+ # assert len(y2) == len(self._cy) == len(self._sy) == n
114
+
115
+ def xs(self, x0):
116
+ '''(INTERNAL) Eta summation (C{float}).
117
+ '''
118
+ return fdot3(self._ab, self._cy, self._shx, start=x0)
119
+
120
+ def ys(self, y0):
121
+ '''(INTERNAL) Ksi summation (C{float}).
122
+ '''
123
+ return fdot3(self._ab, self._sy, self._chx, start=y0)
124
+
125
+ def ps(self, p0):
126
+ '''(INTERNAL) P summation (C{float}).
127
+ '''
128
+ return fdot3(self._pq, self._cy, self._chx, start=p0)
129
+
130
+ def qs(self, q0):
131
+ '''(INTERNAL) Q summation (C{float}).
132
+ '''
133
+ return fdot3(self._pq, self._sy, self._shx, start=q0)
134
+
135
+
136
+ def _cmlon(zone):
137
+ '''(INTERNAL) Central meridian longitude (C{degrees180}).
138
+ '''
139
+ return (zone * 6) - 183
140
+
141
+
142
+ def _false2(e, n, h):
143
+ '''(INTERNAL) False easting and northing.
144
+ '''
145
+ # Karney, "Test data for the transverse Mercator projection (2009)"
146
+ # <https://GeographicLib.SourceForge.io/C++/doc/transversemercator.html>
147
+ # and <https://Zenodo.org/record/32470#.W4LEJS2ZON8>
148
+ e += _FalseEasting # make e relative to central meridian
149
+ if h == _S_:
150
+ n += _FalseNorthing # make n relative to equator
151
+ return e, n
152
+
153
+
154
+ def _toBand(lat, *unused, **strict_Error): # see ups._toBand
155
+ '''(INTERNAL) Get the I{latitudinal} Band (row) letter.
156
+ '''
157
+ if _UTM_LAT_MIN <= lat < _UTM_LAT_MAX: # [-80, 84) like Veness
158
+ return _Bands[int(lat - _UTM_LAT_MIN) >> 3]
159
+ elif _xkwds_get(strict_Error, strict=True):
160
+ r = _range_(_UTM_LAT_MIN, _UTM_LAT_MAX, ropen=True)
161
+ t = _SPACE_(_outside_, _UTM_, _range_, r)
162
+ E = _xkwds_get(strict_Error, Error=RangeError)
163
+ raise E(lat=degDMS(lat), txt=t)
164
+ else:
165
+ return NN # None
166
+
167
+
168
+ def _to3zBlat(zone, band, Error=UTMError): # in .mgrs
169
+ '''(INTERNAL) Check and return zone, Band and band latitude.
170
+
171
+ @arg zone: Zone number or string.
172
+ @arg band: Band letter.
173
+ @arg Error: Exception to raise (L{UTMError}).
174
+
175
+ @return: 3-Tuple (zone, Band, latitude).
176
+ '''
177
+ z, B, _ = _to3zBhp(zone, band, Error=Error)
178
+ if not (_UTM_ZONE_MIN <= z <= _UTM_ZONE_MAX or
179
+ (_UPS_ZONE == z and Error is MGRSError)):
180
+ raise Error(zone=zone)
181
+
182
+ b = None
183
+ if B:
184
+ if z == _UPS_ZONE: # polar
185
+ try:
186
+ b = Lat(_UPS_LATS[B], name=_bandLat_)
187
+ except KeyError:
188
+ raise Error(band=band or B, zone=z)
189
+ else: # UTM
190
+ b = _Bands.find(B)
191
+ if b < 0:
192
+ raise Error(band=band or B, zone=z)
193
+ b = Int((b << 3) - 80, name=_bandLat_)
194
+ B = Band(B)
195
+ elif Error is not UTMError:
196
+ raise Error(band=band, txt=MISSING)
197
+
198
+ return Zone(z), B, b
199
+
200
+
201
+ def _to4zBll(lat, lon, cmoff=True, strict=True, Error=RangeError):
202
+ '''(INTERNAL) Return zone, Band and lat- and (central) longitude in degrees.
203
+
204
+ @arg lat: Latitude (C{degrees}).
205
+ @arg lon: Longitude (C{degrees}).
206
+ @kwarg cmoff: Offset B{C{lon}} from zone's central meridian.
207
+ @kwarg strict: Restrict B{C{lat}} to UTM ranges (C{bool}).
208
+ @kwarg Error: Error for out of UTM range B{C{lat}}s.
209
+
210
+ @return: 4-Tuple (zone, Band, lat, lon).
211
+ '''
212
+ z, lat, lon = _to3zll(lat, lon) # in .utmupsBase
213
+
214
+ x = lon - _cmlon(z) # z before Norway/Svalbard
215
+ if fabs(x) > _UTM_ZONE_OFF_MAX:
216
+ t = _SPACE_(_outside_, _UTM_, _zone_, str(z), _by_, degDMS(x, prec=6))
217
+ raise Error(lon=degDMS(lon), txt=t)
218
+
219
+ B = _toBand(lat, strict=strict, Error=Error)
220
+ if B == _X_: # and 0 <= lon < 42: z = int(lon + 183) // 6 + 1
221
+ x = _SvalbardXzone.get(z, None)
222
+ if x: # Svalbard/Spitsbergen archipelago
223
+ z += 1 if lon >= x else -1
224
+ elif B == _V_ and z == 31 and lon >= 3:
225
+ z += 1 # SouthWestern Norway
226
+
227
+ if cmoff: # lon off central meridian
228
+ lon -= _cmlon(z) # z after Norway/Svalbard
229
+ return Zone(z), (Band(B) if B else None), Lat(lat), Lon(lon)
230
+
231
+
232
+ def _to7zBlldfn(latlon, lon, datum, falsed, name, zone, strict, Error, **cmoff):
233
+ '''(INTERNAL) Determine 7-tuple (zone, band, lat, lon, datum,
234
+ falsed, name) for methods L{toEtm8} and L{toUtm8}.
235
+ '''
236
+ f = falsed and _xkwds_get(cmoff, cmoff=True) # DEPRECATED
237
+ lat, lon, d, name = _to4lldn(latlon, lon, datum, name)
238
+ z, B, lat, lon = _to4zBll(lat, lon, cmoff=f, strict=strict)
239
+ if zone: # re-zone for ETM/UTM
240
+ r, _, _ = _to3zBhp(zone, B)
241
+ if r != z:
242
+ if not _UTM_ZONE_MIN <= r <= _UTM_ZONE_MAX:
243
+ raise Error(zone=zone)
244
+ if f: # re-offset from central meridian
245
+ lon += _cmlon(z) - _cmlon(r)
246
+ z = r
247
+ return z, B, lat, lon, d, f, name
248
+
249
+
250
+ class Utm(UtmUpsBase):
251
+ '''Universal Transverse Mercator (UTM) coordinate.
252
+ '''
253
+ # _band = NN # latitudinal band letter ('C'|..|'X', no 'I'|'O')
254
+ _Bands = _Bands # latitudinal Band letters (C{tuple})
255
+ _Error = UTMError # or etm.ETMError
256
+ # _scale = None # grid scale factor (C{scalar}) or C{None}
257
+ _scale0 = _K0_UTM # central scale factor (C{scalar})
258
+ _zone = 0 # longitudinal zone (C{int} 1..60)
259
+
260
+ def __init__(self, zone=31, hemisphere=_N_, easting=166022, # PYCHOK expected
261
+ northing=0, band=NN, datum=_WGS84, falsed=True,
262
+ gamma=None, scale=None, name=NN, **convergence):
263
+ '''New L{Utm} UTM coordinate.
264
+
265
+ @kwarg zone: Longitudinal UTM zone (C{int}, 1..60) or zone with/-out
266
+ I{latitudinal} Band letter (C{str}, '1C'|..|'60X').
267
+ @kwarg hemisphere: Northern or southern hemisphere (C{str}, C{'N[orth]'}
268
+ or C{'S[outh]'}).
269
+ @kwarg easting: Easting, see B{C{falsed}} (C{meter}).
270
+ @kwarg northing: Northing, see B{C{falsed}} (C{meter}).
271
+ @kwarg band: Optional, I{latitudinal} band (C{str}, 'C'|..|'X', no 'I'|'O').
272
+ @kwarg datum: Optional, this coordinate's datum (L{Datum}, L{Ellipsoid},
273
+ L{Ellipsoid2} or L{a_f2Tuple}).
274
+ @kwarg falsed: If C{True}, both B{C{easting}} and B{C{northing}} are
275
+ falsed (C{bool}).
276
+ @kwarg gamma: Optional meridian convergence, bearing off grid North,
277
+ clockwise from true North (C{degrees}) or C{None}.
278
+ @kwarg scale: Optional grid scale factor (C{scalar}) or C{None}.
279
+ @kwarg name: Optional name (C{str}).
280
+ @kwarg convergence: DEPRECATED, use keyword argument C{B{gamma}=None}.
281
+
282
+ @raise TypeError: Invalid or near-spherical B{C{datum}}.
283
+
284
+ @raise UTMError: Invalid B{C{zone}}, B{C{hemishere}}, B{C{easting}},
285
+ B{C{northing}}, B{C{band}}, B{C{convergence}} or
286
+ B{C{scale}}.
287
+ '''
288
+ if name:
289
+ self.name = name
290
+
291
+ self._zone, B, _ = _to3zBlat(zone, band)
292
+
293
+ h = str(hemisphere)[:1].upper()
294
+ if h not in _NS_:
295
+ raise self._Error(hemisphere=hemisphere)
296
+
297
+ e, n = easting, northing # Easting(easting), ...
298
+ # if not falsed:
299
+ # e, n = _false2(e, n, h)
300
+ # # check easting/northing (with 40km overlap
301
+ # # between zones) - is this worthwhile?
302
+ # @raise RangeError: If B{C{easting}} or B{C{northing}} outside
303
+ # the valid UTM range.
304
+ # if 120e3 > e or e > 880e3:
305
+ # raise RangeError(easting=easting)
306
+ # if 0 > n or n > _FalseNorthing:
307
+ # raise RangeError(northing=northing)
308
+
309
+ self._hemisphere = h
310
+ UtmUpsBase.__init__(self, e, n, band=B, datum=datum, falsed=falsed,
311
+ gamma=gamma, scale=scale, **convergence)
312
+
313
+ def __eq__(self, other):
314
+ return isinstance(other, Utm) and other.zone == self.zone \
315
+ and other.hemisphere == self.hemisphere \
316
+ and other.easting == self.easting \
317
+ and other.northing == self.northing \
318
+ and other.band == self.band \
319
+ and other.datum == self.datum
320
+
321
+ def __repr__(self):
322
+ return self.toRepr(B=True)
323
+
324
+ def __str__(self):
325
+ return self.toStr()
326
+
327
+ def _xcopy2(self, Xtm, name=NN):
328
+ '''(INTERNAL) Make copy as an B{C{Xtm}} instance.
329
+
330
+ @arg Xtm: Class to return the copy (C{Xtm=Etm}, C{Xtm=Utm} or
331
+ C{self.classof}).
332
+ '''
333
+ return Xtm(self.zone, self.hemisphere, self.easting, self.northing,
334
+ band=self.band, datum=self.datum, falsed=self.falsed,
335
+ gamma=self.gamma, scale=self.scale, name=name or self.name)
336
+
337
+ @property_doc_(''' the I{latitudinal} band.''')
338
+ def band(self):
339
+ '''Get the I{latitudinal} band (C{'C'|..|'X'}).
340
+ '''
341
+ if not self._band:
342
+ self._toLLEB()
343
+ return self._band
344
+
345
+ @band.setter # PYCHOK setter!
346
+ def band(self, band):
347
+ '''Set or reset the I{latitudinal} band letter (C{'C'|..|'X'})
348
+ or C{None} or C{""} to reset.
349
+
350
+ @raise TypeError: Invalid B{C{band}}.
351
+
352
+ @raise ValueError: Invalid B{C{band}}.
353
+ '''
354
+ self._band1(band)
355
+
356
+ @Property_RO
357
+ def _etm(self):
358
+ '''(INTERNAL) Cache for method L{toEtm}.
359
+ '''
360
+ return self._xcopy2(_MODS.etm.Etm)
361
+
362
+ @Property_RO
363
+ def falsed2(self):
364
+ '''Get the easting and northing falsing (L{EasNor2Tuple}C{(easting, northing)}).
365
+ '''
366
+ e = n = 0
367
+ if self.falsed:
368
+ e = _FalseEasting # relative to central meridian
369
+ if self.hemisphere == _S_: # relative to equator
370
+ n = _FalseNorthing
371
+ return EasNor2Tuple(e, n)
372
+
373
+ def parse(self, strUTM, name=NN):
374
+ '''Parse a string to a similar L{Utm} instance.
375
+
376
+ @arg strUTM: The UTM coordinate (C{str}),
377
+ see function L{parseUTM5}.
378
+ @kwarg name: Optional instance name (C{str}),
379
+ overriding this name.
380
+
381
+ @return: The similar instance (L{Utm}).
382
+
383
+ @raise UTMError: Invalid B{C{strUTM}}.
384
+
385
+ @see: Functions L{pygeodesy.parseUPS5} and L{pygeodesy.parseUTMUPS5}.
386
+ '''
387
+ return parseUTM5(strUTM, datum=self.datum, Utm=self.classof,
388
+ name=name or self.name)
389
+
390
+ @deprecated_method
391
+ def parseUTM(self, strUTM): # PYCHOK no cover
392
+ '''DEPRECATED, use method L{Utm.parse}.'''
393
+ return self.parse(strUTM)
394
+
395
+ @Property_RO
396
+ def pole(self):
397
+ '''Get the top center of (stereographic) projection, C{""} always.
398
+ '''
399
+ return NN # N/A for UTM
400
+
401
+ def toEtm(self):
402
+ '''Copy this UTM to an ETM coordinate.
403
+
404
+ @return: The ETM coordinate (L{Etm}).
405
+ '''
406
+ return self._etm
407
+
408
+ def toLatLon(self, LatLon=None, eps=EPS, unfalse=True, **LatLon_kwds):
409
+ '''Convert this UTM coordinate to an (ellipsoidal) geodetic point.
410
+
411
+ @kwarg LatLon: Optional, ellipsoidal class to return the geodetic
412
+ point (C{LatLon}) or C{None}.
413
+ @kwarg eps: Optional convergence limit, L{EPS} or above (C{float}).
414
+ @kwarg unfalse: Unfalse B{C{easting}} and B{C{northing}}
415
+ if falsed (C{bool}).
416
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
417
+ arguments, ignored if C{B{LatLon} is None}.
418
+
419
+ @return: This UTM as (B{C{LatLon}}) or if B{C{LatLon}} is
420
+ C{None}, as L{LatLonDatum5Tuple}C{(lat, lon, datum,
421
+ gamma, scale)}.
422
+
423
+ @raise TypeError: Invalid B{C{datum}} or B{C{LatLon}} is not ellipsoidal.
424
+
425
+ @raise UTMError: Invalid meridional radius or H-value.
426
+
427
+ '''
428
+ if eps < EPS:
429
+ eps = EPS # less doesn't converge
430
+
431
+ if self._latlon and self._latlon._toLLEB_args == (unfalse, eps):
432
+ return self._latlon5(LatLon)
433
+ else:
434
+ self._toLLEB(unfalse=unfalse, eps=eps)
435
+ return self._latlon5(LatLon, **LatLon_kwds)
436
+
437
+ def _toLLEB(self, unfalse=True, eps=EPS): # PYCHOK signature
438
+ '''(INTERNAL) Compute (ellipsoidal) lat- and longitude.
439
+ '''
440
+ x, y = self.eastingnorthing2(falsed=not unfalse)
441
+
442
+ E = self.datum.ellipsoid
443
+ # from Karney 2011 Eq 15-22, 36
444
+ A0 = self.scale0 * E.A
445
+ if A0 < EPS0:
446
+ raise self._Error(meridional=A0)
447
+ x = x / A0 # /= chokes PyChecker
448
+ y = y / A0
449
+ K = _Kseries(E.BetaKs, x, y) # Krüger series
450
+ x = neg(K.xs(-x)) # η' eta
451
+ y = neg(K.ys(-y)) # ξ' ksi
452
+
453
+ sy, cy = sincos2(y)
454
+ shx = sinh(x)
455
+ H = hypot(shx, cy)
456
+ if H < EPS0:
457
+ raise self._Error(H=H)
458
+
459
+ T = sy / H # τʹ == τ0
460
+ p = _0_0 # previous d
461
+ e = _0_0001 * eps
462
+ for T, i, d in E._es_tauf3(T, T): # 4-5 trips
463
+ # d may toggle on +/-1.12e-16 or +/-4.47e-16,
464
+ # see the references at C{Ellipsoid.es_tauf}
465
+ if fabs(d) < eps or fabs(d + p) < e:
466
+ break
467
+ p = d
468
+ else:
469
+ t = unstr(self.toLatLon, eps=eps, unfalse=unfalse)
470
+ raise self._Error(Fmt.no_convergence(d, eps), txt=t)
471
+
472
+ a = atan1(T) # phi, lat
473
+ b = atan2(shx, cy)
474
+ if unfalse and self.falsed:
475
+ b += radians(_cmlon(self.zone))
476
+ ll = _LLEB(degrees90(a), degrees180(b), datum=self.datum, name=self.name)
477
+
478
+ # gamma and scale: Karney 2011 Eq 26, 27 and 28
479
+ p = neg(K.ps(-1))
480
+ q = K.qs(0)
481
+ s = hypot(p, q) * E.a / A0
482
+ ll._gamma = degrees(atan1(tan(y) * tanh(x)) + atan2(q, p))
483
+ ll._scale = (E.e2s(sin(a)) * hypot1(T) * H / s) if s else s # INF?
484
+ ll._iteration = i
485
+ self._latlon5args(ll, _toBand, unfalse, eps)
486
+
487
+ def toRepr(self, prec=0, fmt=Fmt.SQUARE, sep=_COMMASPACE_, B=False, cs=False, **unused): # PYCHOK expected
488
+ '''Return a string representation of this UTM coordinate.
489
+
490
+ Note that UTM coordinates are rounded, not truncated (unlike
491
+ MGRS grid references).
492
+
493
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
494
+ @kwarg fmt: Enclosing backets format (C{str}).
495
+ @kwarg sep: Optional separator between name:value pairs (C{str}).
496
+ @kwarg B: Optionally, include latitudinal band (C{bool}).
497
+ @kwarg cs: Optionally, include meridian convergence and grid
498
+ scale factor (C{bool} or non-zero C{int} to specify
499
+ the precison like B{C{prec}}).
500
+
501
+ @return: This UTM as a string C{"[Z:09[band], H:N|S, E:meter,
502
+ N:meter]"} plus C{", C:degrees, S:float"} if B{C{cs}} is
503
+ C{True} (C{str}).
504
+ '''
505
+ return self._toRepr(fmt, B, cs, prec, sep)
506
+
507
+ def toStr(self, prec=0, sep=_SPACE_, B=False, cs=False): # PYCHOK expected
508
+ '''Return a string representation of this UTM coordinate.
509
+
510
+ To distinguish from MGRS grid zone designators, a space is
511
+ left between the zone and the hemisphere.
512
+
513
+ Note that UTM coordinates are rounded, not truncated (unlike
514
+ MGRS grid references).
515
+
516
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
517
+ @kwarg sep: Optional separator to join (C{str}) or C{None}
518
+ to return an unjoined C{tuple} of C{str}s.
519
+ @kwarg B: Optionally, include latitudinal band (C{bool}).
520
+ @kwarg cs: Optionally, include meridian convergence and grid
521
+ scale factor (C{bool} or non-zero C{int} to specify
522
+ the precison like B{C{prec}}).
523
+
524
+ @return: This UTM as a string with C{zone[band], hemisphere,
525
+ easting, northing, [convergence, scale]} in
526
+ C{"00 N|S meter meter"} plus C{" degrees float"} if
527
+ B{C{cs}} is C{True} (C{str}).
528
+ '''
529
+ return self._toStr(self.hemisphere, B, cs, prec, sep)
530
+
531
+ def toUps(self, pole=NN, eps=EPS, falsed=True, **unused):
532
+ '''Convert this UTM coordinate to a UPS coordinate.
533
+
534
+ @kwarg pole: Optional top/center of the UPS projection,
535
+ (C{str}, 'N[orth]'|'S[outh]').
536
+ @kwarg eps: Optional convergence limit, L{EPS} or above
537
+ (C{float}), see method L{Utm.toLatLon}.
538
+ @kwarg falsed: False both easting and northing (C{bool}).
539
+
540
+ @return: The UPS coordinate (L{Ups}).
541
+ '''
542
+ u = self._ups
543
+ if u is None or u.pole != (pole or u.pole) or falsed != bool(u.falsed):
544
+ ll = self.toLatLon(LatLon=_LLEB, eps=eps, unfalse=True)
545
+ ups = _MODS.ups
546
+ self._ups = u = ups.toUps8(ll, Ups=ups.Ups, falsed=falsed,
547
+ name=self.name, pole=pole)
548
+ return u
549
+
550
+ def toUtm(self, zone, eps=EPS, falsed=True, **unused):
551
+ '''Convert this UTM coordinate to a different zone.
552
+
553
+ @arg zone: New UTM zone (C{int}).
554
+ @kwarg eps: Optional convergence limit, L{EPS} or above
555
+ (C{float}), see method L{Utm.toLatLon}.
556
+ @kwarg falsed: False both easting and northing (C{bool}).
557
+
558
+ @return: The UTM coordinate (L{Utm}).
559
+ '''
560
+ if zone == self.zone and falsed == self.falsed:
561
+ return self.copy()
562
+ elif zone:
563
+ u = self._utm
564
+ if u is None or u.zone != zone or falsed != u.falsed:
565
+ ll = self.toLatLon(LatLon=_LLEB, eps=eps, unfalse=True)
566
+ self._utm = u = toUtm8(ll, Utm=self.classof, falsed=falsed,
567
+ name=self.name, zone=zone)
568
+ return u
569
+ raise self._Error(zone=zone)
570
+
571
+ @Property_RO
572
+ def zone(self):
573
+ '''Get the (longitudinal) zone (C{int}, 1..60).
574
+ '''
575
+ return self._zone
576
+
577
+
578
+ def _parseUTM5(strUTM, datum, Xtm, falsed, Error=UTMError, name=NN): # imported by .etm
579
+ '''(INTERNAL) Parse a string representing a UTM coordinate,
580
+ consisting of C{"zone[band] hemisphere easting northing"},
581
+ see L{pygeodesy.parseETM5} and L{parseUTM5}.
582
+ '''
583
+ z, h, e, n, B = _parseUTMUPS5(strUTM, None, Error=Error)
584
+ if _UTM_ZONE_MIN > z or z > _UTM_ZONE_MAX or (B and B not in _Bands):
585
+ raise Error(strUTM=strUTM, zone=z, band=B)
586
+
587
+ if Xtm is None:
588
+ r = UtmUps5Tuple(z, h, e, n, B, Error=Error, name=name)
589
+ else:
590
+ r = Xtm(z, h, e, n, band=B, datum=datum, falsed=falsed)
591
+ if name:
592
+ r = _xnamed(r, name, force=True)
593
+ return r
594
+
595
+
596
+ def parseUTM5(strUTM, datum=_WGS84, Utm=Utm, falsed=True, name=NN):
597
+ '''Parse a string representing a UTM coordinate, consisting
598
+ of C{"zone[band] hemisphere easting northing"}.
599
+
600
+ @arg strUTM: A UTM coordinate (C{str}).
601
+ @kwarg datum: Optional datum to use (L{Datum}, L{Ellipsoid},
602
+ L{Ellipsoid2} or L{a_f2Tuple}).
603
+ @kwarg Utm: Optional class to return the UTM coordinate
604
+ (L{Utm}) or C{None}.
605
+ @kwarg falsed: Both easting and northing are falsed (C{bool}).
606
+ @kwarg name: Optional B{C{Utm}} name (C{str}).
607
+
608
+ @return: The UTM coordinate (B{C{Utm}}) or if B{C{Utm}}
609
+ is C{None}, a L{UtmUps5Tuple}C{(zone, hemipole,
610
+ easting, northing, band)}. The C{hemipole} is
611
+ the C{'N'|'S'} hemisphere.
612
+
613
+ @raise UTMError: Invalid B{C{strUTM}}.
614
+
615
+ @raise TypeError: Invalid B{C{datum}}.
616
+ '''
617
+ return _parseUTM5(strUTM, datum, Utm, falsed, name=name)
618
+
619
+
620
+ def toUtm8(latlon, lon=None, datum=None, Utm=Utm, falsed=True,
621
+ name=NN, strict=True,
622
+ zone=None, **cmoff):
623
+ '''Convert a lat-/longitude point to a UTM coordinate.
624
+
625
+ @arg latlon: Latitude (C{degrees}) or an (ellipsoidal)
626
+ geodetic C{LatLon} point.
627
+ @kwarg lon: Optional longitude (C{degrees}) or C{None}.
628
+ @kwarg datum: Optional datum for this UTM coordinate,
629
+ overriding B{C{latlon}}'s datum (L{Datum},
630
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
631
+ @kwarg Utm: Optional class to return the UTM coordinate
632
+ (L{Utm}) or C{None}.
633
+ @kwarg falsed: False both easting and northing (C{bool}).
634
+ @kwarg name: Optional B{C{Utm}} name (C{str}).
635
+ @kwarg strict: Restrict B{C{lat}} to UTM ranges (C{bool}).
636
+ @kwarg zone: Optional UTM zone to enforce (C{int} or C{str}).
637
+ @kwarg cmoff: DEPRECATED, use B{C{falsed}}. Offset longitude
638
+ from the zone's central meridian (C{bool}).
639
+
640
+ @return: The UTM coordinate (B{C{Utm}}) or if B{C{Utm}} is
641
+ C{None} or not B{C{falsed}}, a L{UtmUps8Tuple}C{(zone,
642
+ hemipole, easting, northing, band, datum, gamma,
643
+ scale)}. The C{hemipole} is the C{'N'|'S'} hemisphere.
644
+
645
+ @raise RangeError: If B{C{lat}} outside the valid UTM bands or if
646
+ B{C{lat}} or B{C{lon}} outside the valid range
647
+ and L{pygeodesy.rangerrors} set to C{True}.
648
+
649
+ @raise TypeError: Invalid B{C{datum}} or B{C{latlon}} not ellipsoidal.
650
+
651
+ @raise UTMError: Invalid B{C{zone}}.
652
+
653
+ @raise ValueError: If B{C{lon}} value is missing or if
654
+ B{C{latlon}} is invalid.
655
+
656
+ @note: Implements Karney’s method, using 8-th order Krüger series,
657
+ giving results accurate to 5 nm (or better) for distances
658
+ up to 3,900 Km from the central meridian.
659
+ '''
660
+ z, B, lat, lon, d, f, name = _to7zBlldfn(latlon, lon, datum,
661
+ falsed, name, zone,
662
+ strict, UTMError, **cmoff)
663
+ d = _ellipsoidal_datum(d, name=name)
664
+ E = d.ellipsoid
665
+
666
+ a, b = radians(lat), radians(lon)
667
+ # easting, northing: Karney 2011 Eq 7-14, 29, 35
668
+ sb, cb = sincos2(b)
669
+
670
+ T = tan(a)
671
+ T12 = hypot1(T)
672
+ S = sinh(E.e * atanh(E.e * T / T12))
673
+
674
+ T_ = T * hypot1(S) - S * T12
675
+ H = hypot(T_, cb)
676
+
677
+ y = atan2(T_, cb) # ξ' ksi
678
+ x = asinh(sb / H) # η' eta
679
+
680
+ A0 = E.A * getattr(Utm, _under(_scale0_), _K0_UTM) # Utm is class or None
681
+
682
+ K = _Kseries(E.AlphaKs, x, y) # Krüger series
683
+ y = K.ys(y) * A0 # ξ
684
+ x = K.xs(x) * A0 # η
685
+
686
+ # convergence: Karney 2011 Eq 23, 24
687
+ p_ = K.ps(1)
688
+ q_ = K.qs(0)
689
+ g = degrees(atan2(T_ * tan(b), hypot1(T_)) + atan2(q_, p_))
690
+ # scale: Karney 2011 Eq 25
691
+ k = E.e2s(sin(a)) * T12 / H * (A0 / E.a * hypot(p_, q_))
692
+
693
+ return _toXtm8(Utm, z, lat, x, y,
694
+ B, d, g, k, f, name, latlon, EPS)
695
+
696
+
697
+ def _toXtm8(Xtm, z, lat, x, y, B, d, g, k, f, # PYCHOK 13+ args
698
+ name, latlon, eps, Error=UTMError):
699
+ '''(INTERNAL) Helper for methods L{toEtm8} and L{toUtm8}.
700
+ '''
701
+ h = _hemi(lat)
702
+ if f:
703
+ x, y = _false2(x, y, h)
704
+ if Xtm is None: # DEPRECATED
705
+ r = UtmUps8Tuple(z, h, x, y, B, d, g, k, Error=Error, name=name)
706
+ else:
707
+ r = _xnamed(Xtm(z, h, x, y, band=B, datum=d, falsed=f,
708
+ gamma=g, scale=k), name)
709
+ if isinstance(latlon, _LLEB) and d is latlon.datum: # see ups.toUtm8
710
+ r._latlon5args(latlon, _toBand, f, eps) # XXX weakref(latlon)?
711
+ latlon._gamma = g
712
+ latlon._scale = k
713
+ elif not r._band:
714
+ r._band = _toBand(lat)
715
+ return r
716
+
717
+
718
+ def utmZoneBand5(lat, lon, cmoff=False, name=NN):
719
+ '''Return the UTM zone number, Band letter, hemisphere and
720
+ (clipped) lat- and longitude for a given location.
721
+
722
+ @arg lat: Latitude in degrees (C{scalar} or C{str}).
723
+ @arg lon: Longitude in degrees (C{scalar} or C{str}).
724
+ @kwarg cmoff: Offset longitude from the zone's central
725
+ meridian (C{bool}).
726
+ @kwarg name: Optional name (C{str}).
727
+
728
+ @return: A L{UtmUpsLatLon5Tuple}C{(zone, band, hemipole,
729
+ lat, lon)} where C{hemipole} is the C{'N'|'S'}
730
+ UTM hemisphere.
731
+
732
+ @raise RangeError: If B{C{lat}} outside the valid UTM bands or if
733
+ B{C{lat}} or B{C{lon}} outside the valid range
734
+ and L{pygeodesy.rangerrors} set to C{True}.
735
+
736
+ @raise ValueError: Invalid B{C{lat}} or B{C{lon}}.
737
+ '''
738
+ lat, lon = parseDMS2(lat, lon)
739
+ z, B, lat, lon = _to4zBll(lat, lon, cmoff=cmoff)
740
+ return UtmUpsLatLon5Tuple(z, B, _hemi(lat), lat, lon, name=name)
741
+
742
+ # **) MIT License
743
+ #
744
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
745
+ #
746
+ # Permission is hereby granted, free of charge, to any person obtaining a
747
+ # copy of this software and associated documentation files (the "Software"),
748
+ # to deal in the Software without restriction, including without limitation
749
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
750
+ # and/or sell copies of the Software, and to permit persons to whom the
751
+ # Software is furnished to do so, subject to the following conditions:
752
+ #
753
+ # The above copyright notice and this permission notice shall be included
754
+ # in all copies or substantial portions of the Software.
755
+ #
756
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
757
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
758
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
759
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
760
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
761
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
762
+ # OTHER DEALINGS IN THE SOFTWARE.