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/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.
|