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/ups.py ADDED
@@ -0,0 +1,538 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''I{Karney}'s Universal Polar Stereographic (UPS) projection.
5
+
6
+ Classes L{Ups} and L{UPSError} and functions L{parseUPS5}, L{toUps8} and L{upsZoneBand5}.
7
+
8
+ A pure Python implementation, partially transcoded from I{Karney}'s C++ class U{PolarStereographic
9
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1PolarStereographic.html>}.
10
+
11
+ The U{UPS<https://WikiPedia.org/wiki/Universal_polar_stereographic_coordinate_system>} system is used
12
+ in conjuction with U{UTM<https://WikiPedia.org/wiki/Universal_Transverse_Mercator_coordinate_system>}
13
+ for locations on the polar regions of the earth. UPS covers areas south of 79.5°S and north of 83.5°N,
14
+ slightly overlapping the UTM range from 80°S to 84°N by 30' at each end.
15
+
16
+ Env variable C{PYGEODESY_UPS_POLES} determines the UPS zones I{at} latitude 90°S and 90°N. By default,
17
+ the encoding follows I{Karney}'s and U{Appendix B-3 of DMA TM8358.1<https://Web.Archive.org/web/
18
+ 20161226192038/http://earth-info.nga.mil/GandG/publications/tm8358.1/pdf/TM8358_1.pdf>}, using only
19
+ zones C{'B'} respectively C{'Z'} and digraph C{'AN'}. If C{PYGEODESY_UPS_POLES} is set to anything
20
+ other than C{"std"}, zones C{'A'} and C{'Y'} are used for negative, west longitudes I{at} latitude
21
+ 90°S respectively 90°N (for backward compatibility).
22
+ '''
23
+
24
+ # from pygeodesy.basics import neg as _neg # from .dms
25
+ from pygeodesy.constants import EPS, EPS0, _EPSmin as _Tol90, \
26
+ isnear90, _0_0, _0_5, _1_0, _2_0
27
+ from pygeodesy.datums import _ellipsoidal_datum, _WGS84
28
+ from pygeodesy.dms import degDMS, _neg, parseDMS2
29
+ from pygeodesy.errors import RangeError, _ValueError
30
+ from pygeodesy.fmath import hypot, hypot1, sqrt0
31
+ from pygeodesy.interns import NN, _COMMASPACE_, _inside_, _N_, _pole_, \
32
+ _range_, _S_, _scale0_, _SPACE_, _std_, \
33
+ _to_, _UTM_, _under
34
+ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _getenv
35
+ from pygeodesy.named import nameof, _xnamed
36
+ from pygeodesy.namedTuples import EasNor2Tuple, UtmUps5Tuple, \
37
+ UtmUps8Tuple, UtmUpsLatLon5Tuple
38
+ from pygeodesy.props import deprecated_method, property_doc_, \
39
+ Property_RO, _update_all
40
+ # from pygeodesy.streprs import Fmt # from .utmupsBase
41
+ from pygeodesy.units import Float, Float_, Meter, Lat
42
+ from pygeodesy.utily import atan1d, degrees180, sincos2d
43
+ from pygeodesy.utmupsBase import Fmt, _LLEB, _hemi, _parseUTMUPS5, _to4lldn, \
44
+ _to3zBhp, _to3zll, _UPS_BANDS as _Bands, \
45
+ _UPS_LAT_MAX, _UPS_LAT_MIN, _UPS_ZONE, \
46
+ _UPS_ZONE_STR, UtmUpsBase
47
+
48
+ from math import atan2, fabs, radians, tan
49
+
50
+ __all__ = _ALL_LAZY.ups
51
+ __version__ = '23.09.29'
52
+
53
+ _BZ_UPS = _getenv('PYGEODESY_UPS_POLES', _std_) == _std_
54
+ _Falsing = Meter(2000e3) # false easting and northing (C{meter})
55
+ _K0_UPS = Float(_K0_UPS= 0.994) # scale factor at central meridian
56
+ _K1_UPS = Float(_K1_UPS=_1_0) # rescale point scale factor
57
+
58
+
59
+ def _scale(E, rho, tau):
60
+ # compute the point scale factor, ala Karney
61
+ t = hypot1(tau)
62
+ return Float(scale=(rho / E.a) * t * sqrt0(E.e21 + E.e2 / t**2))
63
+
64
+
65
+ def _toBand(lat, lon): # see utm._toBand
66
+ '''(INTERNAL) Get the I{polar} Band letter for a (lat, lon).
67
+ '''
68
+ return _Bands[(0 if lat < 0 else 2) + (0 if -180 < lon < 0 else 1)]
69
+
70
+
71
+ class UPSError(_ValueError):
72
+ '''Universal Polar Stereographic (UPS) parse or other L{Ups} issue.
73
+ '''
74
+ pass
75
+
76
+
77
+ class Ups(UtmUpsBase):
78
+ '''Universal Polar Stereographic (UPS) coordinate.
79
+ '''
80
+ # _band = NN # polar band ('A', 'B', 'Y' or 'Z')
81
+ _Bands = _Bands # polar Band letters (C{tuple})
82
+ _Error = UPSError # Error class
83
+ _pole = NN # UPS projection top/center ('N' or 'S')
84
+ # _scale = None # point scale factor (C{scalar})
85
+ _scale0 = _K0_UPS # central scale factor (C{scalar})
86
+
87
+ def __init__(self, zone=0, pole=_N_, easting=_Falsing, # PYCHOK expected
88
+ northing=_Falsing, band=NN, datum=_WGS84,
89
+ falsed=True, gamma=None, scale=None,
90
+ name=NN, **convergence):
91
+ '''New L{Ups} UPS coordinate.
92
+
93
+ @kwarg zone: UPS zone (C{int}, zero) or zone with/-out I{polar} Band
94
+ letter (C{str}, '00', '00A', '00B', '00Y' or '00Z').
95
+ @kwarg pole: Top/center of (stereographic) projection (C{str},
96
+ C{'N[orth]'} or C{'S[outh]'}).
97
+ @kwarg easting: Easting, see B{C{falsed}} (C{meter}).
98
+ @kwarg northing: Northing, see B{C{falsed}} (C{meter}).
99
+ @kwarg band: Optional, I{polar} Band (C{str}, 'A'|'B'|'Y'|'Z').
100
+ @kwarg datum: Optional, this coordinate's datum (L{Datum},
101
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
102
+ @kwarg falsed: If C{True}, both B{C{easting}} and B{C{northing}}
103
+ are falsed (C{bool}).
104
+ @kwarg gamma: Optional, meridian convergence to save (C{degrees}).
105
+ @kwarg scale: Optional, computed scale factor k to save
106
+ (C{scalar}).
107
+ @kwarg name: Optional name (C{str}).
108
+ @kwarg convergence: DEPRECATED, use keyword argument C{B{gamma}=None}.
109
+
110
+ @raise TypeError: Invalid B{C{datum}}.
111
+
112
+ @raise UPSError: Invalid B{C{zone}}, B{C{pole}}, B{C{easting}},
113
+ B{C{northing}}, B{C{band}}, B{C{convergence}}
114
+ or B{C{scale}}.
115
+ '''
116
+ if name:
117
+ self.name = name
118
+
119
+ try:
120
+ z, B, p = _to3zBhp(zone, band, hemipole=pole)
121
+ if z != _UPS_ZONE or (B and (B not in _Bands)):
122
+ raise ValueError
123
+ except (TypeError, ValueError) as x:
124
+ raise UPSError(zone=zone, pole=pole, band=band, cause=x)
125
+ self._pole = p
126
+ UtmUpsBase.__init__(self, easting, northing, band=B, datum=datum, falsed=falsed,
127
+ gamma=gamma, scale=scale, **convergence)
128
+
129
+ def __eq__(self, other):
130
+ return isinstance(other, Ups) and other.zone == self.zone \
131
+ and other.pole == self.pole \
132
+ and other.easting == self.easting \
133
+ and other.northing == self.northing \
134
+ and other.band == self.band \
135
+ and other.datum == self.datum
136
+
137
+ @property_doc_(''' the I{polar} band.''')
138
+ def band(self):
139
+ '''Get the I{polar} band (C{'A'|'B'|'Y'|'Z'}).
140
+ '''
141
+ if not self._band:
142
+ self._toLLEB()
143
+ return self._band
144
+
145
+ @band.setter # PYCHOK setter!
146
+ def band(self, band):
147
+ '''Set or reset the I{polar} band letter (C{'A'|'B'|'Y'|'Z'})
148
+ or C{None} or C{""} to reset.
149
+
150
+ @raise TypeError: Invalid B{C{band}}.
151
+
152
+ @raise ValueError: Invalid B{C{band}}.
153
+ '''
154
+ self._band1(band)
155
+
156
+ @Property_RO
157
+ def falsed2(self):
158
+ '''Get the easting and northing falsing (L{EasNor2Tuple}C{(easting, northing)}).
159
+ '''
160
+ f = _Falsing if self.falsed else 0
161
+ return EasNor2Tuple(f, f)
162
+
163
+ def parse(self, strUPS, name=NN):
164
+ '''Parse a string to a similar L{Ups} instance.
165
+
166
+ @arg strUPS: The UPS coordinate (C{str}),
167
+ see function L{parseUPS5}.
168
+ @kwarg name: Optional instance name (C{str}),
169
+ overriding this name.
170
+
171
+ @return: The similar instance (L{Ups}).
172
+
173
+ @raise UTMError: Invalid B{C{strUPS}}.
174
+
175
+ @see: Functions L{parseUTM5} and L{pygeodesy.parseUTMUPS5}.
176
+ '''
177
+ return parseUPS5(strUPS, datum=self.datum, Ups=self.classof,
178
+ name=name or self.name)
179
+
180
+ @deprecated_method
181
+ def parseUPS(self, strUPS): # PYCHOK no cover
182
+ '''DEPRECATED, use method L{parse}.'''
183
+ return self.parse(strUPS)
184
+
185
+ @Property_RO
186
+ def pole(self):
187
+ '''Get the top/center of (stereographic) projection (C{'N'|'S'} or C{""}).
188
+ '''
189
+ return self._pole
190
+
191
+ def rescale0(self, lat, scale0=_K0_UPS):
192
+ '''Set the central scale factor for this UPS projection.
193
+
194
+ @arg lat: Northern latitude (C{degrees}).
195
+ @arg scale0: UPS k0 scale at B{C{lat}} latitude (C{scalar}).
196
+
197
+ @raise RangeError: If B{C{lat}} outside the valid range and
198
+ L{pygeodesy.rangerrors} set to C{True}.
199
+
200
+ @raise UPSError: Invalid B{C{scale}}.
201
+ '''
202
+ s0 = Float_(scale0=scale0, Error=UPSError, low=EPS) # <= 1.003 or 1.0016?
203
+ u = toUps8(fabs(Lat(lat)), _0_0, datum=self.datum, Ups=_Ups_K1)
204
+ k = s0 / u.scale
205
+ if self.scale0 != k:
206
+ _update_all(self)
207
+ self._band = NN # force re-compute
208
+ self._latlon = self._utm = None
209
+ self._scale0 = Float(scale0=k)
210
+
211
+ def toLatLon(self, LatLon=None, unfalse=True, **LatLon_kwds):
212
+ '''Convert this UPS coordinate to an (ellipsoidal) geodetic point.
213
+
214
+ @kwarg LatLon: Optional, ellipsoidal class to return the
215
+ geodetic point (C{LatLon}) or C{None}.
216
+ @kwarg unfalse: Unfalse B{C{easting}} and B{C{northing}}
217
+ if falsed (C{bool}).
218
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
219
+ arguments, ignored if C{B{LatLon} is None}.
220
+
221
+ @return: This UPS coordinate (B{C{LatLon}}) or if B{C{LatLon}}
222
+ is C{None}, a L{LatLonDatum5Tuple}C{(lat, lon, datum,
223
+ gamma, scale)}.
224
+
225
+ @raise TypeError: If B{C{LatLon}} is not ellipsoidal.
226
+
227
+ @raise UPSError: Invalid meridional radius or H-value.
228
+ '''
229
+ if self._latlon and self._latlon._toLLEB_args == (unfalse,):
230
+ return self._latlon5(LatLon)
231
+ else:
232
+ self._toLLEB(unfalse=unfalse)
233
+ return self._latlon5(LatLon, **LatLon_kwds)
234
+
235
+ def _toLLEB(self, unfalse=True): # PYCHOK signature
236
+ '''(INTERNAL) Compute (ellipsoidal) lat- and longitude.
237
+ '''
238
+ E = self.datum.ellipsoid # XXX vs LatLon.datum.ellipsoid
239
+
240
+ x, y = self.eastingnorthing2(falsed=not unfalse)
241
+
242
+ r = hypot(x, y)
243
+ t = (r * E.es_c / (self.scale0 * E.a * _2_0)) if r > 0 else EPS0
244
+ t = E.es_tauf((_1_0 / t - t) * _0_5)
245
+ a = atan1d(t)
246
+ if self._pole == _N_:
247
+ b, g = atan2(x, -y), 1
248
+ else:
249
+ b, g, a = atan2(x, y), -1, _neg(a)
250
+ ll = _LLEB(a, degrees180(b), datum=self._datum, name=self.name)
251
+
252
+ ll._gamma = b * g
253
+ ll._scale = _scale(E, r, t) if r > 0 else self.scale0
254
+ self._latlon5args(ll, _toBand, unfalse)
255
+
256
+ def toRepr(self, prec=0, fmt=Fmt.SQUARE, sep=_COMMASPACE_, B=False, cs=False, **unused): # PYCHOK expected
257
+ '''Return a string representation of this UPS coordinate.
258
+
259
+ Note that UPS coordinates are rounded, not truncated (unlike
260
+ MGRS grid references).
261
+
262
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
263
+ @kwarg fmt: Enclosing backets format (C{str}).
264
+ @kwarg sep: Optional separator between name:value pairs (C{str}).
265
+ @kwarg B: Optionally, include polar band letter (C{bool}).
266
+ @kwarg cs: Optionally, include gamma meridian convergence and
267
+ point scale factor (C{bool} or non-zero C{int} to
268
+ specify the precison like B{C{prec}}).
269
+
270
+ @return: This UPS as a string with C{00[Band] pole, easting,
271
+ northing, [convergence, scale]} as C{"[Z:00[Band],
272
+ P:N|S, E:meter, N:meter]"} plus C{", C:DMS, S:float"}
273
+ if B{C{cs}} is C{True}, where C{[Band]} is present and
274
+ C{'A'|'B'|'Y'|'Z'} only if B{C{B}} is C{True} and
275
+ convergence C{DMS} is in I{either} degrees, minutes
276
+ I{or} seconds (C{str}).
277
+
278
+ @note: Pseudo zone zero (C{"00"}) for UPS follows I{Karney}'s U{zone UPS
279
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UTMUPS.html>}.
280
+ '''
281
+ return self._toRepr(fmt, B, cs, prec, sep)
282
+
283
+ def toStr(self, prec=0, sep=_SPACE_, B=False, cs=False): # PYCHOK expected
284
+ '''Return a string representation of this UPS coordinate.
285
+
286
+ Note that UPS coordinates are rounded, not truncated (unlike
287
+ MGRS grid references).
288
+
289
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
290
+ @kwarg sep: Optional separator to join (C{str}) or C{None}
291
+ to return an unjoined C{tuple} of C{str}s.
292
+ @kwarg B: Optionally, include and polar band letter (C{bool}).
293
+ @kwarg cs: Optionally, include gamma meridian convergence and
294
+ point scale factor (C{bool} or non-zero C{int} to
295
+ specify the precison like B{C{prec}}).
296
+
297
+ @return: This UPS as a string with C{00[Band] pole, easting,
298
+ northing, [convergence, scale]} as C{"00[B] N|S
299
+ meter meter"} plus C{" DMS float"} if B{C{cs}} is C{True},
300
+ where C{[Band]} is present and C{'A'|'B'|'Y'|'Z'} only
301
+ if B{C{B}} is C{True} and convergence C{DMS} is in
302
+ I{either} degrees, minutes I{or} seconds (C{str}).
303
+
304
+ @note: Zone zero (C{"00"}) for UPS follows I{Karney}'s U{zone UPS
305
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UTMUPS.html>}.
306
+ '''
307
+ return self._toStr(self.pole, B, cs, prec, sep) # PYCHOK pole
308
+
309
+ def toUps(self, pole=NN, **unused):
310
+ '''Duplicate this UPS coordinate.
311
+
312
+ @kwarg pole: Optional top/center of the UPS projection,
313
+ (C{str}, 'N[orth]'|'S[outh]').
314
+
315
+ @return: A copy of this UPS coordinate (L{Ups}).
316
+
317
+ @raise UPSError: Invalid B{C{pole}} or attempt to transfer
318
+ the projection top/center.
319
+ '''
320
+ if self.pole == pole or not pole:
321
+ return self.copy()
322
+ t = _SPACE_(_pole_, repr(self.pole), _to_, repr(pole))
323
+ raise UPSError('no transfer', txt=t)
324
+
325
+ def toUtm(self, zone, falsed=True, **unused):
326
+ '''Convert this UPS coordinate to a UTM coordinate.
327
+
328
+ @arg zone: The UTM zone (C{int}).
329
+ @kwarg falsed: False both easting and northing (C{bool}).
330
+
331
+ @return: The UTM coordinate (L{Utm}).
332
+ '''
333
+ u = self._utm
334
+ if u is None or u.zone != zone or falsed != bool(u.falsed):
335
+ ll = self.toLatLon(LatLon=None, unfalse=True)
336
+ utm = _MODS.utm
337
+ self._utm = u = utm.toUtm8(ll, Utm=utm.Utm, falsed=falsed,
338
+ name=self.name, zone=zone)
339
+ return u
340
+
341
+ @Property_RO
342
+ def zone(self):
343
+ '''Get the polar pseudo zone (C{0}), like I{Karney}'s U{zone UPS<https://
344
+ GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UTMUPS.html>}.
345
+ '''
346
+ return _UPS_ZONE
347
+
348
+
349
+ class _Ups_K1(Ups):
350
+ '''(INTERNAL) For method L{Ups.rescale0}.
351
+ '''
352
+ _scale0 = _K1_UPS
353
+
354
+
355
+ def parseUPS5(strUPS, datum=_WGS84, Ups=Ups, falsed=True, name=NN):
356
+ '''Parse a string representing a UPS coordinate, consisting of
357
+ C{"[zone][band] pole easting northing"} where B{C{zone}} is
358
+ pseudo zone C{"00"|"0"|""} and C{band} is C{'A'|'B'|'Y'|'Z'|''}.
359
+
360
+ @arg strUPS: A UPS coordinate (C{str}).
361
+ @kwarg datum: Optional datum to use (L{Datum}).
362
+ @kwarg Ups: Optional class to return the UPS coordinate (L{Ups})
363
+ or C{None}.
364
+ @kwarg falsed: Both B{C{easting}} and B{C{northing}} are falsed (C{bool}).
365
+ @kwarg name: Optional B{C{Ups}} name (C{str}).
366
+
367
+ @return: The UPS coordinate (B{C{Ups}}) or a
368
+ L{UtmUps5Tuple}C{(zone, hemipole, easting, northing,
369
+ band)} if B{C{Ups}} is C{None}. The C{hemipole} is
370
+ the C{'N'|'S'} pole, the UPS projection top/center.
371
+
372
+ @raise UPSError: Invalid B{C{strUPS}}.
373
+ '''
374
+ z, p, e, n, B = _parseUTMUPS5(strUPS, _UPS_ZONE_STR, Error=UPSError)
375
+ if z != _UPS_ZONE or (B and B not in _Bands):
376
+ raise UPSError(strUPS=strUPS, zone=z, band=B)
377
+
378
+ r = UtmUps5Tuple(z, p, e, n, B, Error=UPSError) if Ups is None \
379
+ else Ups(z, p, e, n, band=B, falsed=falsed, datum=datum)
380
+ return _xnamed(r, name)
381
+
382
+
383
+ def toUps8(latlon, lon=None, datum=None, Ups=Ups, pole=NN,
384
+ falsed=True, strict=True, name=NN):
385
+ '''Convert a lat-/longitude point to a UPS coordinate.
386
+
387
+ @arg latlon: Latitude (C{degrees}) or an (ellipsoidal)
388
+ geodetic C{LatLon} point.
389
+ @kwarg lon: Optional longitude (C{degrees}) or C{None} if
390
+ B{C{latlon}} is a C{LatLon}.
391
+ @kwarg datum: Optional datum for this UPS coordinate,
392
+ overriding B{C{latlon}}'s datum (C{Datum},
393
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
394
+ @kwarg Ups: Optional class to return the UPS coordinate
395
+ (L{Ups}) or C{None}.
396
+ @kwarg pole: Optional top/center of (stereographic) projection
397
+ (C{str}, C{'N[orth]'} or C{'S[outh]'}).
398
+ @kwarg falsed: False both easting and northing (C{bool}).
399
+ @kwarg strict: Restrict B{C{lat}} to UPS ranges (C{bool}).
400
+ @kwarg name: Optional B{C{Ups}} name (C{str}).
401
+
402
+ @return: The UPS coordinate (B{C{Ups}}) or a
403
+ L{UtmUps8Tuple}C{(zone, hemipole, easting, northing,
404
+ band, datum, gamma, scale)} if B{C{Ups}} is C{None}.
405
+ The C{hemipole} is the C{'N'|'S'} pole, the UPS
406
+ projection top/center.
407
+
408
+ @raise RangeError: If B{C{strict}} and B{C{lat}} outside the valid
409
+ UPS bands or if B{C{lat}} or B{C{lon}} outside
410
+ the valid range and L{pygeodesy.rangerrors} set
411
+ to C{True}.
412
+
413
+ @raise TypeError: If B{C{latlon}} is not ellipsoidal or
414
+ B{C{datum}} invalid.
415
+
416
+ @raise ValueError: If B{C{lon}} value is missing or if B{C{latlon}}
417
+ is invalid.
418
+
419
+ @see: I{Karney}'s C++ class U{UPS
420
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UPS.html>}.
421
+ '''
422
+ lat, lon, d, name = _to4lldn(latlon, lon, datum, name)
423
+ z, B, p, lat, lon = upsZoneBand5(lat, lon, strict=strict) # PYCHOK UtmUpsLatLon5Tuple
424
+
425
+ d = _ellipsoidal_datum(d, name=name)
426
+ E = d.ellipsoid
427
+
428
+ p = str(pole or p)[:1].upper()
429
+ S = p == _S_ # at south[pole]
430
+
431
+ a = -lat if S else lat
432
+ P = isnear90(a, eps90=_Tol90) # at pole
433
+
434
+ t = tan(radians(a))
435
+ T = E.es_taupf(t)
436
+ r = (hypot1(T) - T) if T < 0 else (_0_0 if P else _1_0 /
437
+ (hypot1(T) + T))
438
+
439
+ k0 = getattr(Ups, _under(_scale0_), _K0_UPS) # Ups is class or None
440
+ r *= k0 * E.a * _2_0 / E.es_c
441
+
442
+ k = k0 if P else _scale(E, r, t)
443
+ g = lon # [-180, 180) from .upsZoneBand5
444
+ x, y = sincos2d(g)
445
+ x *= r
446
+ y *= r
447
+ if S:
448
+ g = _neg(g)
449
+ else:
450
+ y = _neg(y)
451
+
452
+ if falsed:
453
+ x += _Falsing
454
+ y += _Falsing
455
+
456
+ n = name or nameof(latlon)
457
+ if Ups is None:
458
+ r = UtmUps8Tuple(z, p, x, y, B, d, g, k, Error=UPSError, name=n)
459
+ else:
460
+ if z != _UPS_ZONE and not strict:
461
+ z = _UPS_ZONE # ignore UTM zone
462
+ r = Ups(z, p, x, y, band=B, datum=d, falsed=falsed,
463
+ gamma=g, scale=k, name=n)
464
+ if isinstance(latlon, _LLEB) and d is latlon.datum: # see utm._toXtm8
465
+ r._latlon5args(latlon, _toBand, falsed) # XXX weakref(latlon)?
466
+ latlon._gamma = g
467
+ latlon._scale = k
468
+ else:
469
+ r._hemisphere = _hemi(lat)
470
+ if not r._band:
471
+ r._band = _toBand(lat, lon)
472
+ return r
473
+
474
+
475
+ def upsZoneBand5(lat, lon, strict=True, name=NN):
476
+ '''Return the UTM/UPS zone number, I{polar} Band letter, pole and
477
+ clipped lat- and longitude for a given location.
478
+
479
+ @arg lat: Latitude in degrees (C{scalar} or C{str}).
480
+ @arg lon: Longitude in degrees (C{scalar} or C{str}).
481
+ @kwarg strict: Restrict B{C{lat}} to UPS ranges (C{bool}).
482
+ @kwarg name: Optional name (C{str}).
483
+
484
+ @return: A L{UtmUpsLatLon5Tuple}C{(zone, band, hemipole,
485
+ lat, lon)} where C{hemipole} is the C{'N'|'S'} pole,
486
+ the UPS projection top/center and C{lon} [-180..180).
487
+
488
+ @note: The C{lon} is set to C{0} if B{C{lat}} is C{-90} or
489
+ C{90}, see env variable C{PYGEODESY_UPS_POLES} in
490
+ module L{pygeodesy.ups}.
491
+
492
+ @raise RangeError: If B{C{strict}} and B{C{lat}} in the UTM
493
+ and not the UPS range or if B{C{lat}} or
494
+ B{C{lon}} outside the valid range and
495
+ L{pygeodesy.rangerrors} set to C{True}.
496
+
497
+ @raise ValueError: Invalid B{C{lat}} or B{C{lon}}.
498
+ '''
499
+ z, lat, lon = _to3zll(*parseDMS2(lat, lon))
500
+ if _BZ_UPS and lon < 0 and isnear90(fabs(lat), eps90=_Tol90): # DMA TM8358.1 only ...
501
+ lon = 0 # ... zones B and Z at 90°S and 90°N, see also GeoConvert
502
+
503
+ if lat < _UPS_LAT_MIN: # includes 30' overlap
504
+ z, B, p = _UPS_ZONE, _toBand(lat, lon), _S_
505
+
506
+ elif lat > _UPS_LAT_MAX: # includes 30' overlap
507
+ z, B, p = _UPS_ZONE, _toBand(lat, lon), _N_
508
+
509
+ elif strict:
510
+ r = _range_(_UPS_LAT_MIN, _UPS_LAT_MAX, prec=1)
511
+ t = _SPACE_(_inside_, _UTM_, _range_, r)
512
+ raise RangeError(lat=degDMS(lat), txt=t)
513
+
514
+ else:
515
+ B, p = NN, _hemi(lat)
516
+ return UtmUpsLatLon5Tuple(z, B, p, lat, lon, Error=UPSError, name=name)
517
+
518
+ # **) MIT License
519
+ #
520
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
521
+ #
522
+ # Permission is hereby granted, free of charge, to any person obtaining a
523
+ # copy of this software and associated documentation files (the "Software"),
524
+ # to deal in the Software without restriction, including without limitation
525
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
526
+ # and/or sell copies of the Software, and to permit persons to whom the
527
+ # Software is furnished to do so, subject to the following conditions:
528
+ #
529
+ # The above copyright notice and this permission notice shall be included
530
+ # in all copies or substantial portions of the Software.
531
+ #
532
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
533
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
534
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
535
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
536
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
537
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
538
+ # OTHER DEALINGS IN THE SOFTWARE.