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
@@ -0,0 +1,517 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''(INTERNAL) Private class C{UtmUpsBase}, functions and constants
5
+ for L{epsg}, L{etm}, L{mgrs}, L{ups} and L{utm}.
6
+ '''
7
+
8
+ from pygeodesy.basics import isint, isscalar, isstr, neg_, \
9
+ _xinstanceof, _xsubclassof
10
+ from pygeodesy.constants import _float, _0_0, _0_5, _N_90_0, _180_0
11
+ from pygeodesy.datums import _ellipsoidal_datum, _WGS84
12
+ from pygeodesy.dms import degDMS, parseDMS2
13
+ from pygeodesy.ellipsoidalBase import LatLonEllipsoidalBase as _LLEB
14
+ from pygeodesy.errors import _or, ParseError, _parseX, _ValueError, \
15
+ _xkwds, _xkwds_get, _xkwds_not
16
+ from pygeodesy.interns import NN, _A_, _B_, _COMMA_, _Error_, \
17
+ _gamma_, _n_a_, _not_, _N_, _NS_, _PLUS_, \
18
+ _scale_, _SPACE_, _Y_, _Z_, _under
19
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
20
+ from pygeodesy.named import _NamedBase, nameof, notOverloaded, _xnamed
21
+ from pygeodesy.namedTuples import EasNor2Tuple, LatLonDatum5Tuple
22
+ from pygeodesy.props import deprecated_method, property_doc_, _update_all, \
23
+ deprecated_property_RO, Property_RO, property_RO
24
+ from pygeodesy.streprs import Fmt, fstr, _fstrENH2, _xattrs, _xzipairs
25
+ from pygeodesy.units import Band, Easting, Northing, Scalar, Zone
26
+ from pygeodesy.utily import _Wrap, wrap360
27
+
28
+ __all__ = _ALL_LAZY.utmupsBase
29
+ __version__ = '23.11.29'
30
+
31
+ _UPS_BANDS = _A_, _B_, _Y_, _Z_ # UPS polar bands SE, SW, NE, NW
32
+ # _UTM_BANDS = _MODS.utm._Bands
33
+
34
+ _UTM_LAT_MAX = _float( 84) # PYCHOK for export (C{degrees})
35
+ _UTM_LAT_MIN = _float(-80) # PYCHOK for export (C{degrees})
36
+
37
+ _UPS_LAT_MAX = _UTM_LAT_MAX - _0_5 # PYCHOK includes 30' UTM overlap
38
+ _UPS_LAT_MIN = _UTM_LAT_MIN + _0_5 # PYCHOK includes 30' UTM overlap
39
+
40
+ _UPS_LATS = {_A_: _N_90_0, _Y_: _UTM_LAT_MAX, # UPS band bottom latitudes,
41
+ _B_: _N_90_0, _Z_: _UTM_LAT_MAX} # PYCHOK see .Mgrs.bandLatitude
42
+
43
+ _UTM_ZONE_MAX = 60 # PYCHOK for export
44
+ _UTM_ZONE_MIN = 1 # PYCHOK for export
45
+ _UTM_ZONE_OFF_MAX = 60 # PYCHOK max Central meridian offset (C{degrees})
46
+
47
+ _UPS_ZONE = _UTM_ZONE_MIN - 1 # PYCHOK for export
48
+ _UPS_ZONE_STR = Fmt.zone(_UPS_ZONE) # PYCHOK for export
49
+
50
+ _UTMUPS_ZONE_INVALID = -4 # PYCHOK for export too
51
+ _UTMUPS_ZONE_MAX = _UTM_ZONE_MAX # PYCHOK for export too, by .units.py
52
+ _UTMUPS_ZONE_MIN = _UPS_ZONE # PYCHOK for export too, by .units.py
53
+
54
+ # _MAX_PSEUDO_ZONE = -1
55
+ # _MIN_PSEUDO_ZONE = -4
56
+ # _UTMUPS_ZONE_MATCH = -3
57
+ # _UTMUPS_ZONE_STANDARD = -1
58
+ # _UTM = -2
59
+
60
+
61
+ def _hemi(lat, N=0): # imported by .ups, .utm
62
+ '''Return the hemisphere letter.
63
+
64
+ @arg lat: Latitude (C{degrees} or C{radians}).
65
+ @kwarg N: Minimal North latitude, C{0} or C{_N_}.
66
+
67
+ @return: C{'N'|'S'} for north-/southern hemisphere.
68
+ '''
69
+ return _NS_[int(lat < N)]
70
+
71
+
72
+ def _to4lldn(latlon, lon, datum, name, wrap=False):
73
+ '''(INTERNAL) Return 4-tuple (C{lat, lon, datum, name}).
74
+ '''
75
+ try:
76
+ # if lon is not None:
77
+ # raise AttributeError
78
+ lat, lon = float(latlon.lat), float(latlon.lon)
79
+ _xinstanceof(_LLEB, LatLonDatum5Tuple, latlon=latlon)
80
+ if wrap:
81
+ _Wrap.latlon(lat, lon)
82
+ d = datum or latlon.datum
83
+ except AttributeError:
84
+ lat, lon = _Wrap.latlonDMS2(latlon, lon) if wrap else \
85
+ parseDMS2(latlon, lon) # clipped
86
+ d = datum or _WGS84
87
+ return lat, lon, d, (name or nameof(latlon))
88
+
89
+
90
+ def _to3zBhp(zone, band, hemipole=NN, Error=_ValueError): # imported by .epsg, .ups, .utm, .utmups
91
+ '''Parse UTM/UPS zone, Band letter and hemisphere/pole letter.
92
+
93
+ @arg zone: Zone with/-out Band (C{scalar} or C{str}).
94
+ @kwarg band: Optional I{longitudinal/polar} Band letter (C{str}).
95
+ @kwarg hemipole: Optional hemisphere/pole letter (C{str}).
96
+ @kwarg Error: Optional error to raise, overriding the default
97
+ C{ValueError}.
98
+
99
+ @return: 3-Tuple (C{zone, Band, hemisphere/pole}) as (C{int, str,
100
+ 'N'|'S'}) where C{zone} is C{0} for UPS or C{1..60} for
101
+ UTM and C{Band} is C{'A'..'Z'} I{NOT} checked for valid
102
+ UTM/UPS bands.
103
+
104
+ @raise ValueError: Invalid B{C{zone}}, B{C{band}} or B{C{hemipole}}.
105
+ '''
106
+ try:
107
+ B, z = band, _UTMUPS_ZONE_INVALID
108
+ if isscalar(zone):
109
+ z = int(zone)
110
+ elif zone and isstr(zone):
111
+ if zone.isdigit():
112
+ z = int(zone)
113
+ elif len(zone) > 1:
114
+ B = zone[-1:]
115
+ z = int(zone[:-1])
116
+ elif zone.upper() in _UPS_BANDS: # single letter
117
+ B = zone
118
+ z = _UPS_ZONE
119
+
120
+ if _UTMUPS_ZONE_MIN <= z <= _UTMUPS_ZONE_MAX:
121
+ hp = hemipole[:1].upper()
122
+ if hp in _NS_ or not hp:
123
+ z = Zone(z)
124
+ B = Band(B.upper())
125
+ if B.isalpha():
126
+ return z, B, (hp or _hemi(B, _N_))
127
+ elif not B:
128
+ return z, B, hp
129
+
130
+ raise ValueError # _invalid_
131
+ except (AttributeError, IndexError, TypeError, ValueError) as x:
132
+ raise Error(zone=zone, band=B, hemipole=hemipole, cause=x)
133
+
134
+
135
+ def _to3zll(lat, lon): # imported by .ups, .utm
136
+ '''Wrap lat- and longitude and determine UTM zone.
137
+
138
+ @arg lat: Latitude (C{degrees}).
139
+ @arg lon: Longitude (C{degrees}).
140
+
141
+ @return: 3-Tuple (C{zone, lat, lon}) as (C{int}, C{degrees90},
142
+ C{degrees180}) where C{zone} is C{1..60} for UTM.
143
+ '''
144
+ x = wrap360(lon + _180_0) # use wrap360 to get ...
145
+ z = int(x) // 6 + 1 # ... longitudinal UTM zone [1, 60] and ...
146
+ return Zone(z), lat, (x - _180_0) # ... -180 <= lon < 180
147
+
148
+
149
+ class UtmUpsBase(_NamedBase):
150
+ '''(INTERNAL) Base class for L{Utm} and L{Ups} coordinates.
151
+ '''
152
+ _band = NN # latitude band letter ('A..Z')
153
+ _Bands = NN # valid Band letters, see L{Utm} and L{Ups}
154
+ _datum = _WGS84 # L{Datum}
155
+ _easting = _0_0 # Easting, see B{C{falsed}} (C{meter})
156
+ _Error = None # I{Must be overloaded}, see function C{notOverloaded}
157
+ _falsed = True # falsed easting and northing (C{bool})
158
+ _gamma = None # meridian conversion (C{degrees})
159
+ _hemisphere = NN # hemisphere ('N' or 'S'), different from UPS pole
160
+ _latlon = None # cached toLatLon (C{LatLon} or C{._toLLEB})
161
+ _northing = _0_0 # Northing, see B{C{falsed}} (C{meter})
162
+ _scale = None # grid or point scale factor (C{scalar}) or C{None}
163
+ # _scale0 = _K0 # central scale factor (C{scalar})
164
+ _ups = None # cached toUps (L{Ups})
165
+ _utm = None # cached toUtm (L{Utm})
166
+
167
+ def __init__(self, easting, northing, band=NN, datum=None, falsed=True,
168
+ gamma=None, scale=None, **convergence):
169
+ '''(INTERNAL) New L{UtmUpsBase}.
170
+ '''
171
+ E = self._Error
172
+ if not E: # PYCHOK no cover
173
+ notOverloaded(self, callername=_under(_Error_))
174
+
175
+ self._easting = Easting(easting, Error=E)
176
+ self._northing = Northing(northing, Error=E)
177
+
178
+ if band:
179
+ self._band1(band)
180
+
181
+ if datum not in (None, self._datum):
182
+ self._datum = _ellipsoidal_datum(datum) # raiser=_datum_, name=band
183
+
184
+ if not falsed:
185
+ self._falsed = False
186
+
187
+ if convergence: # for backward compatibility
188
+ gamma = _xkwds_get(convergence, convergence=gamma)
189
+ if gamma is not self._gamma:
190
+ self._gamma = Scalar(gamma=gamma, Error=E)
191
+ if scale is not self._scale:
192
+ self._scale = Scalar(scale=scale, Error=E)
193
+
194
+ def __repr__(self):
195
+ return self.toRepr(B=True)
196
+
197
+ def __str__(self):
198
+ return self.toStr()
199
+
200
+ def _band1(self, band):
201
+ '''(INTERNAL) Re/set the latitudinal or polar band.
202
+ '''
203
+ if band:
204
+ _xinstanceof(str, band=band)
205
+ # if not self._Bands: # PYCHOK no cover
206
+ # notOverloaded(self, callername=_under('Bands'))
207
+ if band not in self._Bands:
208
+ t = _or(*sorted(set(map(repr, self._Bands))))
209
+ raise self._Error(band=band, txt=_not_(t))
210
+ self._band = band
211
+ elif self._band: # reset
212
+ self._band = NN
213
+
214
+ @deprecated_property_RO
215
+ def convergence(self):
216
+ '''DEPRECATED, use property C{gamma}.'''
217
+ return self.gamma
218
+
219
+ @property_doc_(''' the (ellipsoidal) datum of this coordinate.''')
220
+ def datum(self):
221
+ '''Get the datum (L{Datum}).
222
+ '''
223
+ return self._datum
224
+
225
+ @datum.setter # PYCHOK setter!
226
+ def datum(self, datum):
227
+ '''Set the (ellipsoidal) datum L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
228
+ '''
229
+ d = _ellipsoidal_datum(datum)
230
+ if self._datum != d:
231
+ _update_all(self)
232
+ self._datum = d
233
+
234
+ @Property_RO
235
+ def easting(self):
236
+ '''Get the easting (C{meter}).
237
+ '''
238
+ return self._easting
239
+
240
+ @Property_RO
241
+ def eastingnorthing(self):
242
+ '''Get easting and northing (L{EasNor2Tuple}C{(easting, northing)}).
243
+ '''
244
+ return EasNor2Tuple(self.easting, self.northing)
245
+
246
+ def eastingnorthing2(self, falsed=True):
247
+ '''Return easting and northing, falsed or unfalsed.
248
+
249
+ @kwarg falsed: If C{True} return easting and northing falsed
250
+ (C{bool}), otherwise unfalsed.
251
+
252
+ @return: An L{EasNor2Tuple}C{(easting, northing)} in C{meter}.
253
+ '''
254
+ e, n = self.falsed2
255
+ if self.falsed and not falsed:
256
+ e, n = neg_(e, n)
257
+ elif falsed and not self.falsed:
258
+ pass
259
+ else:
260
+ e = n = _0_0
261
+ return EasNor2Tuple(Easting( e + self.easting, Error=self._Error),
262
+ Northing(n + self.northing, Error=self._Error))
263
+
264
+ @Property_RO
265
+ def _epsg(self):
266
+ '''(INTERNAL) Cache for method L{toEpsg}.
267
+ '''
268
+ return _MODS.epsg.Epsg(self)
269
+
270
+ @Property_RO
271
+ def falsed(self):
272
+ '''Get easting and northing falsed (C{bool}).
273
+ '''
274
+ return self._falsed
275
+
276
+ @Property_RO
277
+ def falsed2(self): # PYCHOK no cover
278
+ '''I{Must be overloaded}.'''
279
+ notOverloaded(self)
280
+
281
+ @Property_RO
282
+ def gamma(self):
283
+ '''Get the meridian convergence (C{degrees}) or C{None}
284
+ if not available.
285
+ '''
286
+ return self._gamma
287
+
288
+ @property_RO
289
+ def hemisphere(self):
290
+ '''Get the hemisphere (C{str}, 'N'|'S').
291
+ '''
292
+ if not self._hemisphere:
293
+ self._toLLEB()
294
+ return self._hemisphere
295
+
296
+ def _latlon5(self, LatLon, **LatLon_kwds):
297
+ '''(INTERNAL) Get cached C{._toLLEB} as B{C{LatLon}} instance.
298
+ '''
299
+ ll = self._latlon
300
+ if LatLon is None:
301
+ r = LatLonDatum5Tuple(ll.lat, ll.lon, ll.datum,
302
+ ll.gamma, ll.scale)
303
+ else:
304
+ _xsubclassof(_LLEB, LatLon=LatLon)
305
+ kwds = _xkwds(LatLon_kwds, datum=ll.datum)
306
+ r = _xattrs(LatLon(ll.lat, ll.lon, **kwds),
307
+ ll, _under(_gamma_), _under(_scale_))
308
+ return _xnamed(r, ll.name)
309
+
310
+ def _latlon5args(self, ll, _toBand, unfalse, *other):
311
+ '''(INTERNAL) See C{._toLLEB} methods, functions C{ups.toUps8} and C{utm._toXtm8}
312
+ '''
313
+ ll._toLLEB_args = (unfalse,) + other
314
+ if unfalse:
315
+ if not self._band:
316
+ self._band = _toBand(ll.lat, ll.lon)
317
+ if not self._hemisphere:
318
+ self._hemisphere = _hemi(ll.lat)
319
+ self._latlon = ll
320
+
321
+ @Property_RO
322
+ def _lowerleft(self): # by .ellipsoidalBase._lowerleft
323
+ '''Get this UTM or UPS C{un}-centered (L{Utm} or L{Ups}) to its C{lowerleft}.
324
+ '''
325
+ return _lowerleft(self, 0)
326
+
327
+ @Property_RO
328
+ def _mgrs(self):
329
+ '''(INTERNAL) Cache for method L{toMgrs}.
330
+ '''
331
+ return _toMgrs(self)
332
+
333
+ @Property_RO
334
+ def _mgrs_lowerleft(self):
335
+ '''(INTERNAL) Cache for method L{toMgrs}, I{un}-centered.
336
+ '''
337
+ utmups = self._lowerleft
338
+ return self._mgrs if utmups is self else _toMgrs(utmups)
339
+
340
+ @Property_RO
341
+ def northing(self):
342
+ '''Get the northing (C{meter}).
343
+ '''
344
+ return self._northing
345
+
346
+ @Property_RO
347
+ def scale(self):
348
+ '''Get the grid scale (C{float}) or C{None}.
349
+ '''
350
+ return self._scale
351
+
352
+ @Property_RO
353
+ def scale0(self):
354
+ '''Get the central scale factor (C{float}).
355
+ '''
356
+ return self._scale0
357
+
358
+ @deprecated_method
359
+ def to2en(self, falsed=True): # PYCHOK no cover
360
+ '''DEPRECATED, use method C{eastingnorthing2}.
361
+
362
+ @return: An L{EasNor2Tuple}C{(easting, northing)}.
363
+ '''
364
+ return self.eastingnorthing2(falsed=falsed)
365
+
366
+ def toEpsg(self):
367
+ '''Determine the B{EPSG (European Petroleum Survey Group)} code.
368
+
369
+ @return: C{EPSG} code (C{int}).
370
+
371
+ @raise EPSGError: See L{Epsg}.
372
+ '''
373
+ return self._epsg
374
+
375
+ def _toLLEB(self, **kwds): # PYCHOK no cover
376
+ '''(INTERNAL) I{Must be overloaded}.'''
377
+ notOverloaded(self, **kwds)
378
+
379
+ def toMgrs(self, center=False):
380
+ '''Convert this UTM/UPS coordinate to an MGRS grid reference.
381
+
382
+ @kwarg center: If C{True}, I{un}-center this UTM or UPS to
383
+ its C{lowerleft} (C{bool}) or by C{B{center}
384
+ meter} (C{scalar}).
385
+
386
+ @return: The MGRS grid reference (L{Mgrs}).
387
+
388
+ @see: Function L{pygeodesy.toMgrs} in module L{mgrs} for more details.
389
+
390
+ @note: If not specified, the I{latitudinal} C{band} is computed from
391
+ the (geodetic) latitude and the C{datum}.
392
+ '''
393
+ return self._mgrs if center in (False, 0, _0_0) else (
394
+ self._mgrs_lowerleft if center in (True,) else
395
+ _toMgrs(_lowerleft(self, center))) # PYCHOK indent
396
+
397
+ def _toRepr(self, fmt, B, cs, prec, sep): # PYCHOK expected
398
+ '''(INTERNAL) Return a representation for this ETM/UTM/UPS coordinate.
399
+ '''
400
+ t = self.toStr(prec=prec, sep=None, B=B, cs=cs) # hemipole
401
+ T = 'ZHENCS'[:len(t)]
402
+ return _xzipairs(T, t, sep=sep, fmt=fmt)
403
+
404
+ def _toStr(self, hemipole, B, cs, prec, sep):
405
+ '''(INTERNAL) Return a string for this ETM/UTM/UPS coordinate.
406
+ '''
407
+ z = NN(Fmt.zone(self.zone), (self.band if B else NN)) # PYCHOK band
408
+ t = (z, hemipole) + _fstrENH2(self, prec, None)[0]
409
+ if cs:
410
+ prec = cs if isint(cs) else 8 # for backward compatibility
411
+ t += (_n_a_ if self.gamma is None else
412
+ degDMS(self.gamma, prec=prec, pos=_PLUS_),
413
+ _n_a_ if self.scale is None else
414
+ fstr(self.scale, prec=prec))
415
+ return t if sep is None else sep.join(t)
416
+
417
+
418
+ def _lowerleft(utmups, center): # by .ellipsoidalBase._lowerleft
419
+ '''(INTERNAL) I{Un}-center a B{C{utmups}} to its C{lowerleft} by
420
+ C{B{center} meter} or by a I{guess} if B{C{center}} is C{0}.
421
+ '''
422
+ if center:
423
+ e = n = -center
424
+ else:
425
+ c = 5 # center
426
+ for _ in range(3):
427
+ c *= 10 # 50, 500, 5000
428
+ t = c * 2
429
+ e = int(utmups.easting % t)
430
+ n = int(utmups.northing % t)
431
+ if (e == c and n in (c, c - 1)) or \
432
+ (n == c and e in (c, c - 1)):
433
+ break
434
+ else:
435
+ return utmups # unchanged
436
+
437
+ r = _xkwds_not(None, datum=utmups.datum,
438
+ gamma=utmups.gamma,
439
+ scale=utmups.scale)
440
+ return utmups.classof(utmups.zone, utmups.hemisphere,
441
+ utmups.easting - e, utmups.northing - n,
442
+ band=utmups.band, falsed=utmups.falsed, **r)
443
+
444
+
445
+ def _parseUTMUPS5(strUTMUPS, UPS, Error=ParseError, band=NN, sep=_COMMA_):
446
+ '''(INTERNAL) Parse a string representing a UTM or UPS coordinate
447
+ consisting of C{"zone[band] hemisphere/pole easting northing"}.
448
+
449
+ @arg strUTMUPS: A UTM or UPS coordinate (C{str}).
450
+ @kwarg band: Optional, default Band letter (C{str}).
451
+ @kwarg sep: Optional, separator to split (",").
452
+
453
+ @return: 5-Tuple (C{zone, hemisphere/pole, easting, northing,
454
+ band}).
455
+
456
+ @raise ParseError: Invalid B{C{strUTMUPS}}.
457
+ '''
458
+ def _UTMUPS5(strUTMUPS, UPS, band, sep):
459
+ u = strUTMUPS.lstrip()
460
+ if UPS and not u.startswith(_UPS_ZONE_STR):
461
+ raise ValueError(_not_(_UPS_ZONE_STR))
462
+
463
+ u = u.replace(sep, _SPACE_).strip().split()
464
+ if len(u) < 4:
465
+ raise ValueError(_not_(sep))
466
+
467
+ z, h = u[:2]
468
+ if h[:1].upper() not in _NS_:
469
+ raise ValueError(_SPACE_(h, _not_(_NS_)))
470
+
471
+ if z.isdigit():
472
+ z, B = int(z), band
473
+ else:
474
+ for i in range(len(z)):
475
+ if not z[i].isdigit():
476
+ # int('') raises ValueError
477
+ z, B = int(z[:i]), z[i:]
478
+ break
479
+ else:
480
+ raise ValueError(z)
481
+
482
+ e, n = map(float, u[2:4])
483
+ return z, h.upper(), e, n, B.upper()
484
+
485
+ return _parseX(_UTMUPS5, strUTMUPS, UPS, band, sep,
486
+ strUTMUPS=strUTMUPS, Error=Error)
487
+
488
+
489
+ def _toMgrs(utmups):
490
+ '''(INTERNAL) Convert a L{Utm} or L{Ups} to an L{Mgrs} instance.
491
+ '''
492
+ return _MODS.mgrs.toMgrs(utmups, datum=utmups.datum, name=utmups.name)
493
+
494
+
495
+ __all__ += _ALL_DOCS(UtmUpsBase)
496
+
497
+ # **) MIT License
498
+ #
499
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
500
+ #
501
+ # Permission is hereby granted, free of charge, to any person obtaining a
502
+ # copy of this software and associated documentation files (the "Software"),
503
+ # to deal in the Software without restriction, including without limitation
504
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
505
+ # and/or sell copies of the Software, and to permit persons to whom the
506
+ # Software is furnished to do so, subject to the following conditions:
507
+ #
508
+ # The above copyright notice and this permission notice shall be included
509
+ # in all copies or substantial portions of the Software.
510
+ #
511
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
512
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
513
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
514
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
515
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
516
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
517
+ # OTHER DEALINGS IN THE SOFTWARE.