pygeodesy 24.7.7__py2.py3-none-any.whl → 24.8.4__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/units.py CHANGED
@@ -11,11 +11,11 @@ from pygeodesy.constants import EPS, EPS1, PI, PI2, PI_2, _umod_360, _0_0, \
11
11
  _0_001, _0_5, INT0 # PYCHOK for .mgrs, .namedTuples
12
12
  from pygeodesy.dms import F__F, F__F_, S_NUL, S_SEP, parseDMS, parseRad, _toDMS
13
13
  from pygeodesy.errors import _AssertionError, TRFError, UnitError, _xattr, _xcallable
14
- from pygeodesy.interns import NN, _band_, _bearing_, _COMMASPACE_, _degrees_, \
15
- _degrees2_, _distance_, _E_, _easting_, _epoch_, _EW_, \
16
- _feet_, _height_, _lam_, _lat_, _LatLon_, _lon_, \
17
- _meter_, _meter2_, _N_, _negative_, _northing_, _NS_, \
18
- _NSEW_, _number_, _PERCENT_, _phi_, _precision_, \
14
+ from pygeodesy.interns import NN, _azimuth_, _band_, _bearing_, _COMMASPACE_, \
15
+ _degrees_, _degrees2_, _distance_, _E_, _easting_, \
16
+ _epoch_, _EW_, _feet_, _height_, _lam_, _lat_, _LatLon_, \
17
+ _lon_, _meter_, _meter2_, _N_, _negative_, _northing_, \
18
+ _NS_, _NSEW_, _number_, _PERCENT_, _phi_, _precision_, \
19
19
  _radians_, _radians2_, _radius_, _S_, _scalar_, \
20
20
  _units_, _W_, _zone_, _std_ # PYCHOK used!
21
21
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _getenv
@@ -24,10 +24,10 @@ from pygeodesy.props import Property_RO
24
24
  # from pygeodesy.streprs import Fmt, fstr # from .unitsBase
25
25
  from pygeodesy.unitsBase import Float, Int, _NamedUnit, Radius, Str, Fmt, fstr
26
26
 
27
- from math import degrees, radians
27
+ from math import degrees, isnan, radians
28
28
 
29
29
  __all__ = _ALL_LAZY.units
30
- __version__ = '24.06.29'
30
+ __version__ = '24.08.02'
31
31
 
32
32
 
33
33
  class Float_(Float):
@@ -339,18 +339,35 @@ class Radians2(Float_):
339
339
  return Float_.__new__(cls, arg=arg, name=name, low=_0_0, **Error_name_arg)
340
340
 
341
341
 
342
+ def _Degrees_new(cls, **arg_name_suffix_clip_Error_name_arg):
343
+ d = Degrees.__new__(cls, **arg_name_suffix_clip_Error_name_arg)
344
+ b = _umod_360(d) # 0 <= b < 360
345
+ return d if b == d else Degrees.__new__(cls, arg=b, name=d.name)
346
+
347
+
348
+ class Azimuth(Degrees):
349
+ '''Named C{float} representing an azimuth in compass C{degrees} from (true) North.
350
+ '''
351
+ _ddd_ = 1
352
+ _suf_ = _W_, S_NUL, _E_ # no zero suffix
353
+
354
+ def __new__(cls, arg=None, name=_azimuth_, **clip_Error_name_arg):
355
+ '''New, named L{Azimuth} with optional suffix 'E' for clockwise or 'W' for
356
+ anti-clockwise, see L{Degrees}.
357
+ '''
358
+ return _Degrees_new(cls, arg=arg, name=name, suffix=_EW_, **clip_Error_name_arg)
359
+
360
+
342
361
  class Bearing(Degrees):
343
362
  '''Named C{float} representing a bearing in compass C{degrees} from (true) North.
344
363
  '''
345
364
  _ddd_ = 1
346
365
  _suf_ = _N_ * 3 # always suffix N
347
366
 
348
- def __new__(cls, arg=None, name=_bearing_, clip=0, **Error_name_arg):
367
+ def __new__(cls, arg=None, name=_bearing_, **clip_Error_name_arg):
349
368
  '''New, named L{Bearing}, see L{Degrees}.
350
369
  '''
351
- d = Degrees.__new__(cls, arg=arg, name=name, suffix=_N_, clip=clip, **Error_name_arg)
352
- b = _umod_360(d) # 0 <= b < 360
353
- return d if b == d else Degrees.__new__(cls, arg=b, name=d.name)
370
+ return _Degrees_new(cls, arg=arg, name=name, suffix=_N_, **clip_Error_name_arg)
354
371
 
355
372
 
356
373
  class Bearing_(Radians):
@@ -782,7 +799,7 @@ class Zone(Int):
782
799
 
783
800
 
784
801
  _ScalarU = Float, Float_, Scalar, Scalar_
785
- _Degrees = (Bearing, Bearing_, Degrees, Degrees_) + _ScalarU
802
+ _Degrees = (Azimuth, Bearing, Bearing_, Degrees, Degrees_) + _ScalarU
786
803
  _Meters = (Distance, Distance_, Meter, Meter_) + _ScalarU
787
804
  _Radians = (Radians, Radians_) + _ScalarU # PYCHOK unused
788
805
  _Radii = _Meters + (Radius, Radius_)
@@ -794,7 +811,7 @@ def _isDegrees(obj):
794
811
 
795
812
 
796
813
  def _isHeight(obj):
797
- # Check for valid heigth types.
814
+ # Check for valid height types.
798
815
  return isinstance(obj, _Meters) or _isScalar(obj)
799
816
 
800
817
 
@@ -823,13 +840,13 @@ def _toUnit(Unit, arg, name=NN, **Error):
823
840
 
824
841
 
825
842
  def _xlimits(arg, low, high, g=False):
826
- '''(INTERNAL) Check C{low <= unit <= high}.
843
+ '''(INTERNAL) Check C{low <= arg <= high}.
827
844
  '''
828
- if (low is not None) and arg < low:
845
+ if (low is not None) and (arg < low or isnan(arg)):
829
846
  if g:
830
847
  low = Fmt.g(low, prec=6, ints=isinstance(arg, Epoch))
831
848
  t = Fmt.limit(below=low)
832
- elif (high is not None) and arg > high:
849
+ elif (high is not None) and (arg > high or isnan(arg)):
833
850
  if g:
834
851
  high = Fmt.g(high, prec=6, ints=isinstance(arg, Epoch))
835
852
  t = Fmt.limit(above=high)
@@ -847,7 +864,7 @@ def _std_repr(*Classes):
847
864
  if _getenv(env, _std_).lower() != _std_:
848
865
  C._std_repr = False
849
866
 
850
- _std_repr(Bearing, Bool, Degrees, Float, Int, Meter, Radians, Str) # PYCHOK expected
867
+ _std_repr(Azimuth, Bearing, Bool, Degrees, Float, Int, Meter, Radians, Str) # PYCHOK expected
851
868
  del _std_repr
852
869
 
853
870
  __all__ += _ALL_DOCS(_NamedUnit)
pygeodesy/wgrs.py CHANGED
@@ -1,21 +1,22 @@
1
1
 
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- u'''World Geographic Reference System (WGRS) en-/decoding.
4
+ u'''World Geographic Reference System (WGRS) en-/decoding, aka GEOREF.
5
5
 
6
- Classes L{Georef} and L{WGRSError} and several functions to encode,
7
- decode and inspect WGRS references.
6
+ Class L{Georef} and several functions to encode, decode and inspect
7
+ WGRS (or GEOREF) references.
8
8
 
9
9
  Transcoded from I{Charles Karney}'s C++ class U{Georef
10
10
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Georef.html>},
11
- but with modified C{precision} and extended with C{height} and C{radius}. See
12
- also U{World Geographic Reference System
13
- <https://WikiPedia.org/wiki/World_Geographic_Reference_System>}.
11
+ but with modified C{precision} and extended with C{height} and C{radius}.
12
+
13
+ @see: U{World Geographic Reference System
14
+ <https://WikiPedia.org/wiki/World_Geographic_Reference_System>}.
14
15
  '''
15
16
  # from pygeodesy.basics import isstr # from .named
16
17
  from pygeodesy.constants import INT0, _float, _off90, _0_001, \
17
18
  _0_5, _1_0, _2_0, _60_0, _1000_0
18
- from pygeodesy.dms import parse3llh # parseDMS2
19
+ from pygeodesy.dms import parse3llh
19
20
  from pygeodesy.errors import _ValueError, _xattr, _xStrError
20
21
  from pygeodesy.interns import NN, _0to9_, _AtoZnoIO_, _COMMA_, \
21
22
  _height_, _radius_, _SPACE_
@@ -31,13 +32,12 @@ from pygeodesy.utily import ft2m, m2ft, m2NM
31
32
  from math import floor
32
33
 
33
34
  __all__ = _ALL_LAZY.wgrs
34
- __version__ = '24.06.15'
35
+ __version__ = '24.08.02'
35
36
 
36
37
  _Base = 10
37
38
  _BaseLen = 4
38
39
  _DegChar = _AtoZnoIO_.tillQ
39
40
  _Digits = _0to9_
40
- _Height_ = Height.__name__
41
41
  _INV_ = 'INV' # INValid
42
42
  _LatOrig = -90
43
43
  _LatTile = _AtoZnoIO_.tillM
@@ -45,7 +45,6 @@ _LonOrig = -180
45
45
  _LonTile = _AtoZnoIO_
46
46
  _60B = 60000000000 # == 60_000_000_000 == 60e9
47
47
  _MaxPrec = 11
48
- _Radius_ = Radius.__name__
49
48
  _Tile = 15 # tile size in degrees
50
49
 
51
50
  _MaxLen = _BaseLen + 2 * _MaxPrec
@@ -68,15 +67,15 @@ def _divmod3(x, _Orig_60B):
68
67
  return xt, xd, x
69
68
 
70
69
 
71
- def _fllh3(lat, lon, height=None):
72
- '''(INTERNAL) Convert lat, lon, height.
70
+ def _2fll(lat, lon):
71
+ '''(INTERNAL) Convert lat, lon.
73
72
  '''
74
73
  # lat, lon = parseDMS2(lat, lon)
75
74
  return (Lat(lat, Error=WGRSError),
76
- Lon(lon, Error=WGRSError), height)
75
+ Lon(lon, Error=WGRSError))
77
76
 
78
77
 
79
- def _geostr2(georef):
78
+ def _2geostr2(georef):
80
79
  '''(INTERNAL) Check a georef string.
81
80
  '''
82
81
  try:
@@ -85,14 +84,14 @@ def _geostr2(georef):
85
84
  if o or n < _MinLen or n > _MaxLen \
86
85
  or geostr[:3] == _INV_ \
87
86
  or not geostr.isalnum():
88
- raise ValueError
89
- return geostr, _Precision(p - 1)
87
+ raise ValueError()
88
+ return geostr, _2Precision(p - 1)
90
89
 
91
90
  except (AttributeError, TypeError, ValueError) as x:
92
91
  raise WGRSError(Georef.__name__, georef, cause=x)
93
92
 
94
93
 
95
- def _Precision(precision):
94
+ def _2Precision(precision):
96
95
  '''(INTERNAL) Return a L{Precision_} instance.
97
96
  '''
98
97
  return Precision_(precision, Error=WGRSError, low=0, high=_MaxPrec)
@@ -108,48 +107,51 @@ class Georef(Str):
108
107
  '''Georef class, a named C{str}.
109
108
  '''
110
109
  # no str.__init__ in Python 3
111
- def __new__(cls, cll, precision=3, name=NN):
110
+ def __new__(cls, lat_gll, lon=None, height=None, precision=3, name=NN):
112
111
  '''New L{Georef} from an other L{Georef} instance or georef
113
112
  C{str} or from a C{LatLon} instance or lat-/longitude C{str}.
114
113
 
115
- @arg cll: Cell or location (L{Georef} or C{str}, C{LatLon}
116
- or C{str}).
117
- @kwarg precision: Optional, the desired georef resolution
118
- and length (C{int} 0..11), see function
119
- L{wgrs.encode} for more details.
114
+ @arg lat_gll: Latitude (C{degrees90}), a georef (L{Georef},
115
+ C{str}) or a location (C{LatLon}, C{LatLon*Tuple}).
116
+ @kwarg lon: Logitude (C{degrees180)}, required if B{C{lat_gll}}
117
+ is C{degrees90}, ignored otherwise.
118
+ @kwarg height: Optional height in C{meter}, used if B{C{lat_gll}}
119
+ is a location.
120
+ @kwarg precision: The desired georef resolution and length (C{int}
121
+ 0..11), see L{encode<pygeodesy.wgrs.encode>}.
120
122
  @kwarg name: Optional name (C{str}).
121
123
 
122
124
  @return: New L{Georef}.
123
125
 
124
- @raise RangeError: Invalid B{C{cll}} lat- or longitude.
126
+ @raise RangeError: Invalid B{C{lat_gll}} or B{C{lon}}.
125
127
 
126
- @raise TypeError: Invalid B{C{cll}}.
128
+ @raise TypeError: Invalid B{C{lat_gll}} or B{C{lon}}.
127
129
 
128
- @raise WGRSError: INValid or non-alphanumeric B{C{cll}}.
130
+ @raise WGRSError: INValid B{C{lat_gll}}.
129
131
  '''
130
- ll = p = None
131
-
132
- if isinstance(cll, Georef):
133
- g, p = _geostr2(str(cll))
134
-
135
- elif isstr(cll):
136
- if _COMMA_ in cll:
137
- lat, lon, h = _fllh3(*parse3llh(cll, height=None))
138
- g = encode(lat, lon, precision=precision, height=h) # PYCHOK false
139
- ll = lat, lon # original lat, lon
140
- else:
141
- g = cll.upper()
142
-
143
- else: # assume LatLon
144
- try:
145
- lat, lon, h = _fllh3(cll.lat, cll.lon)
146
- except AttributeError:
147
- raise _xStrError(Georef, cll=cll) # Error=WGRSError
148
- h = _xattr(cll, height=h)
149
- g = encode(lat, lon, precision=precision, height=h) # PYCHOK false
150
- ll = lat, lon # original lat, lon
151
-
152
- self = Str.__new__(cls, g, name=name or nameof(cll))
132
+ if lon is None:
133
+ if isinstance(lat_gll, Georef):
134
+ g, ll, p = str(lat_gll), lat_gll.latlon, lat_gll.precision
135
+ elif isstr(lat_gll):
136
+ if _COMMA_ in lat_gll or _SPACE_ in lat_gll:
137
+ lat, lon, h = parse3llh(lat_gll, height=height)
138
+ g, ll, p = _encode3(lat, lon, precision, h=h)
139
+ else:
140
+ g, ll = lat_gll.upper(), None
141
+ try:
142
+ _, p = _2geostr2(g) # validate
143
+ except WGRSError: # R00H00?
144
+ p = None # = decode5(g).precision?
145
+ else: # assume LatLon
146
+ try:
147
+ g, ll, p = _encode3(lat_gll.lat, lat_gll.lon, precision,
148
+ h=_xattr(lat_gll, height=height))
149
+ except AttributeError:
150
+ raise _xStrError(Georef, gll=lat_gll) # Error=WGRSError
151
+ else:
152
+ g, ll, p = _encode3(lat_gll, lon, precision, h=height)
153
+
154
+ self = Str.__new__(cls, g, name=name or nameof(lat_gll))
153
155
  self._latlon = ll
154
156
  self._precision = p
155
157
  return self
@@ -255,7 +257,7 @@ def decode3(georef, center=True):
255
257
  raise _Error(i)
256
258
  return k
257
259
 
258
- g, precision = _geostr2(georef)
260
+ g, precision = _2geostr2(georef)
259
261
  lon = _index(_LonTile, g, 0) + _LonOrig_Tile
260
262
  lat = _index(_LatTile, g, 1) + _LatOrig_Tile
261
263
 
@@ -288,30 +290,29 @@ def decode5(georef, center=True):
288
290
  @kwarg center: If C{True} the center, otherwise the south-west,
289
291
  lower-left corner (C{bool}).
290
292
 
291
- @return: A L{LatLonPrec5Tuple}C{(lat, lon,
292
- precision, height, radius)} where C{height} and/or
293
- C{radius} are C{None} if missing.
293
+ @return: A L{LatLonPrec5Tuple}C{(lat, lon, precision, height, radius)}
294
+ where C{height} and/or C{radius} are C{None} if missing.
294
295
 
295
- @raise WGRSError: Invalid B{C{georef}}, INValid, non-alphanumeric
296
- or odd length B{C{georef}}.
296
+ @raise WGRSError: Invalid B{C{georef}}.
297
297
  '''
298
- def _h2m(kft, name):
299
- return Height(ft2m(kft * _1000_0), name=name, Error=WGRSError)
298
+ def _h2m(kft, g_n):
299
+ return Height(ft2m(kft * _1000_0), name=g_n, Error=WGRSError)
300
300
 
301
- def _r2m(NM, name):
302
- return Radius(NM / m2NM(1), name=name, Error=WGRSError)
301
+ def _r2m(NM, g_n):
302
+ return Radius(NM / m2NM(1), name=g_n, Error=WGRSError)
303
303
 
304
- def _split2(g, name, _2m):
305
- i = max(g.find(name[0]), g.rfind(name[0]))
304
+ def _split2(g, Unit, _2m):
305
+ n = Unit.__name__
306
+ i = max(g.find(n[0]), g.rfind(n[0]))
306
307
  if i > _BaseLen:
307
- return g[:i], _2m(int(g[i+1:]), _SPACE_(georef, name))
308
+ return g[:i], _2m(int(g[i+1:]), _SPACE_(georef, n))
308
309
  else:
309
310
  return g, None
310
311
 
311
312
  g = Str(georef, Error=WGRSError)
312
313
 
313
- g, h = _split2(g, _Height_, _h2m) # H is last
314
- g, r = _split2(g, _Radius_, _r2m) # R before H
314
+ g, h = _split2(g, Height, _h2m) # H is last
315
+ g, r = _split2(g, Radius, _r2m) # R before H
315
316
 
316
317
  return decode3(g, center=center).to5Tuple(h, r)
317
318
 
@@ -345,30 +346,39 @@ def encode(lat, lon, precision=3, height=None, radius=None): # MCCABE 14
345
346
  f = Scalar_(m, name=name, Error=WGRSError)
346
347
  return NN(name[0].upper(), int(m2_(f * K) + _0_5))
347
348
 
348
- p = _Precision(precision)
349
+ g, _, _ = _encode3(lat, lon, precision)
350
+ if radius is not None: # R before H
351
+ g += _option(_radius_, radius, m2NM, _1_0)
352
+ if height is not None: # H is last
353
+ g += _option(_height_, height, m2ft, _0_001)
354
+ return g
355
+
349
356
 
350
- lat, lon, _ = _fllh3(lat, lon)
351
- lat = _off90(lat)
357
+ def _encode3(lat, lon, precision, h=None):
358
+ '''Return 3-tuple C{(georef, (lat, lon), p)}.
359
+ '''
360
+ p = _2Precision(precision)
352
361
 
353
- xt, xd, x = _divmod3(lon, _LonOrig_60B)
354
- yt, yd, y = _divmod3(lat, _LatOrig_60B)
362
+ lat, lon = _2fll(lat, lon)
355
363
 
356
- g = _LonTile[xt], _LatTile[yt]
357
- if p > 0:
358
- g += _DegChar[xd], _DegChar[yd]
359
- p -= 1
360
- if p > 0:
361
- d = pow(_Base, _MaxPrec - p)
362
- x = _0wd(p, x // d)
363
- y = _0wd(p, y // d)
364
- g += x, y
364
+ if h is None:
365
+ xt, xd, x = _divmod3( lon, _LonOrig_60B)
366
+ yt, yd, y = _divmod3(_off90(lat), _LatOrig_60B)
365
367
 
366
- if radius is not None: # R before H
367
- g += _option(_radius_, radius, m2NM, _1_0),
368
- if height is not None: # H is last
369
- g += _option(_height_, height, m2ft, _0_001),
368
+ g = _LonTile[xt], _LatTile[yt]
369
+ if p > 0:
370
+ g += _DegChar[xd], _DegChar[yd]
371
+ p -= 1
372
+ if p > 0:
373
+ d = pow(_Base, _MaxPrec - p)
374
+ x = _0wd(p, x // d)
375
+ y = _0wd(p, y // d)
376
+ g += x, y
377
+ g = NN.join(g)
378
+ else:
379
+ g = encode(lat, lon, precision=p, height=h)
370
380
 
371
- return NN.join(g) # XXX Georef(''.join(g))
381
+ return g, (lat, lon), p # XXX Georef(''.join(g))
372
382
 
373
383
 
374
384
  def precision(res):