pygeodesy 24.11.11__py2.py3-none-any.whl → 25.1.5__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 (118) hide show
  1. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/METADATA +34 -35
  2. PyGeodesy-25.1.5.dist-info/RECORD +118 -0
  3. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/WHEEL +1 -1
  4. pygeodesy/__init__.py +19 -19
  5. pygeodesy/__main__.py +1 -1
  6. pygeodesy/albers.py +5 -5
  7. pygeodesy/auxilats/_CX_4.py +1 -1
  8. pygeodesy/auxilats/_CX_6.py +1 -1
  9. pygeodesy/auxilats/_CX_8.py +1 -1
  10. pygeodesy/auxilats/_CX_Rs.py +1 -1
  11. pygeodesy/auxilats/__init__.py +1 -1
  12. pygeodesy/auxilats/__main__.py +1 -1
  13. pygeodesy/auxilats/auxAngle.py +5 -5
  14. pygeodesy/auxilats/auxDLat.py +6 -6
  15. pygeodesy/auxilats/auxDST.py +2 -2
  16. pygeodesy/auxilats/auxLat.py +5 -5
  17. pygeodesy/auxilats/auxily.py +2 -2
  18. pygeodesy/azimuthal.py +5 -5
  19. pygeodesy/basics.py +60 -8
  20. pygeodesy/booleans.py +1 -1
  21. pygeodesy/cartesianBase.py +22 -61
  22. pygeodesy/clipy.py +1 -1
  23. pygeodesy/constants.py +5 -5
  24. pygeodesy/css.py +1 -1
  25. pygeodesy/datums.py +1 -1
  26. pygeodesy/deprecated/__init__.py +2 -2
  27. pygeodesy/deprecated/bases.py +1 -1
  28. pygeodesy/deprecated/classes.py +86 -2
  29. pygeodesy/deprecated/consterns.py +1 -1
  30. pygeodesy/deprecated/datum.py +5 -5
  31. pygeodesy/deprecated/functions.py +42 -8
  32. pygeodesy/deprecated/nvector.py +1 -1
  33. pygeodesy/deprecated/rhumbBase.py +1 -1
  34. pygeodesy/deprecated/rhumbaux.py +1 -1
  35. pygeodesy/deprecated/rhumbsolve.py +1 -1
  36. pygeodesy/deprecated/rhumbx.py +1 -1
  37. pygeodesy/dms.py +1 -1
  38. pygeodesy/ecef.py +53 -56
  39. pygeodesy/elevations.py +1 -1
  40. pygeodesy/ellipsoidalBase.py +3 -3
  41. pygeodesy/ellipsoidalBaseDI.py +1 -1
  42. pygeodesy/ellipsoidalExact.py +1 -1
  43. pygeodesy/ellipsoidalGeodSolve.py +1 -1
  44. pygeodesy/ellipsoidalKarney.py +1 -1
  45. pygeodesy/ellipsoidalNvector.py +1 -1
  46. pygeodesy/ellipsoidalVincenty.py +6 -5
  47. pygeodesy/ellipsoids.py +4 -5
  48. pygeodesy/elliptic.py +6 -6
  49. pygeodesy/epsg.py +1 -1
  50. pygeodesy/errors.py +1 -1
  51. pygeodesy/etm.py +5 -5
  52. pygeodesy/fmath.py +18 -17
  53. pygeodesy/formy.py +409 -555
  54. pygeodesy/frechet.py +29 -86
  55. pygeodesy/fstats.py +1 -1
  56. pygeodesy/fsums.py +32 -33
  57. pygeodesy/gars.py +1 -1
  58. pygeodesy/geodesici.py +7 -7
  59. pygeodesy/geodesicw.py +1 -1
  60. pygeodesy/geodesicx/_C4_24.py +2 -2
  61. pygeodesy/geodesicx/_C4_27.py +2 -2
  62. pygeodesy/geodesicx/_C4_30.py +2 -2
  63. pygeodesy/geodesicx/__init__.py +2 -2
  64. pygeodesy/geodesicx/__main__.py +4 -5
  65. pygeodesy/geodesicx/gx.py +6 -5
  66. pygeodesy/geodesicx/gxarea.py +2 -2
  67. pygeodesy/geodesicx/gxbases.py +2 -2
  68. pygeodesy/geodesicx/gxline.py +16 -12
  69. pygeodesy/geodsolve.py +1 -1
  70. pygeodesy/geohash.py +1 -1
  71. pygeodesy/geoids.py +277 -203
  72. pygeodesy/hausdorff.py +23 -81
  73. pygeodesy/heights.py +115 -150
  74. pygeodesy/internals.py +1 -1
  75. pygeodesy/interns.py +2 -3
  76. pygeodesy/iters.py +1 -1
  77. pygeodesy/karney.py +3 -3
  78. pygeodesy/ktm.py +16 -15
  79. pygeodesy/latlonBase.py +323 -409
  80. pygeodesy/lazily.py +53 -44
  81. pygeodesy/lcc.py +1 -1
  82. pygeodesy/ltp.py +46 -50
  83. pygeodesy/ltpTuples.py +147 -130
  84. pygeodesy/mgrs.py +1 -1
  85. pygeodesy/named.py +149 -3
  86. pygeodesy/namedTuples.py +58 -7
  87. pygeodesy/nvectorBase.py +122 -105
  88. pygeodesy/osgr.py +1 -1
  89. pygeodesy/points.py +1 -1
  90. pygeodesy/props.py +1 -1
  91. pygeodesy/resections.py +18 -17
  92. pygeodesy/rhumb/__init__.py +1 -1
  93. pygeodesy/rhumb/aux_.py +2 -2
  94. pygeodesy/rhumb/bases.py +2 -2
  95. pygeodesy/rhumb/ekx.py +4 -4
  96. pygeodesy/rhumb/solve.py +1 -1
  97. pygeodesy/simplify.py +289 -401
  98. pygeodesy/solveBase.py +1 -1
  99. pygeodesy/sphericalBase.py +1 -1
  100. pygeodesy/sphericalNvector.py +5 -5
  101. pygeodesy/sphericalTrigonometry.py +7 -6
  102. pygeodesy/streprs.py +10 -5
  103. pygeodesy/trf.py +1 -1
  104. pygeodesy/triaxials.py +23 -16
  105. pygeodesy/units.py +16 -16
  106. pygeodesy/unitsBase.py +1 -1
  107. pygeodesy/ups.py +4 -4
  108. pygeodesy/utily.py +341 -211
  109. pygeodesy/utm.py +5 -5
  110. pygeodesy/utmups.py +1 -1
  111. pygeodesy/utmupsBase.py +1 -1
  112. pygeodesy/vector2d.py +5 -5
  113. pygeodesy/vector3d.py +14 -3
  114. pygeodesy/vector3dBase.py +5 -5
  115. pygeodesy/webmercator.py +1 -1
  116. pygeodesy/wgrs.py +1 -1
  117. PyGeodesy-24.11.11.dist-info/RECORD +0 -118
  118. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/top_level.txt +0 -0
pygeodesy/geoids.py CHANGED
@@ -3,15 +3,16 @@
3
3
 
4
4
  u'''Geoid models and geoid height interpolations.
5
5
 
6
- Classes L{GeoidG2012B}, L{GeoidKarney} and L{GeoidPGM} to interpolate the
7
- height of various U{geoid<https://WikiPedia.org/wiki/Geoid>}s at C{LatLon}
8
- locations or separate lat-/longitudes using different interpolation methods
9
- and C{geoid} model files.
6
+ Classes L{GeoidEGM96}, L{GeoidG2012B}, L{GeoidKarney} and L{GeoidPGM} to
7
+ interpolate the height of various U{geoid<https://WikiPedia.org/wiki/Geoid>}s
8
+ at C{LatLon} locations or separate lat-/longitudes using various interpolation
9
+ methods and C{geoid} model files.
10
10
 
11
11
  L{GeoidKarney} is a transcoding of I{Charles Karney}'s C++ class U{Geoid
12
12
  <https://GeographicLib.SourceForge.io/C++/doc/geoid.html>} to pure Python.
13
- The L{GeoidG2012B} and L{GeoidPGM} interpolators both depend on U{scipy
14
- <https://SciPy.org>} and U{numpy<https://PyPI.org/project/numpy>} and
13
+
14
+ The L{GeoidEGM96}, L{GeoidG2012B} and L{GeoidPGM} interpolators both depend on
15
+ U{scipy<https://SciPy.org>} and U{numpy<https://PyPI.org/project/numpy>} and
15
16
  require those packages to be installed.
16
17
 
17
18
  In addition, each geoid interpolator needs C{grid knots} (down)loaded from
@@ -22,19 +23,19 @@ there are several interpolation choices, like I{linear}, I{cubic}, etc.
22
23
  Typical usage
23
24
  =============
24
25
 
25
- 1. Choose one of the interpolator classes L{GeoidG2012B}, L{GeoidKarney}
26
+ 1. Choose an interpolator class L{GeoidEGM96}, L{GeoidG2012B}, L{GeoidKarney}
26
27
  or L{GeoidPGM} and download a C{geoid} model file, containing locations with
27
- known heights also referred to as the C{grid knots}. See the documentation
28
- of the interpolator class for references to available C{grid} models.
28
+ known heights also referred to as the C{grid knots}. See the documentation of
29
+ the interpolator class for references to available C{grid} models.
29
30
 
30
- C{>>> from pygeodesy import GeoidG2012B # or -Karney or -PGM as GeoidXyz}
31
+ C{>>> from pygeodesy import GeoidEGM96 # or -G2012B, -Karney or -PGM as GeoidXyz}
31
32
 
32
33
  2. Instantiate an interpolator with the C{geoid} model file and use keyword
33
34
  arguments to select different interpolation options
34
35
 
35
36
  C{>>> ginterpolator = GeoidXyz(geoid_model_file, **options)}
36
37
 
37
- 3. Get the interpolated geoid height of other C{LatLon} location(s) with
38
+ 3. Get the interpolated geoid height of C{LatLon} location(s) with
38
39
 
39
40
  C{>>> ll = LatLon(1, 2, ...)}
40
41
 
@@ -48,7 +49,7 @@ or a list, tuple, generator, etc. of C{LatLon}s
48
49
 
49
50
  C{>>> hs = ginterpolator(lls)}
50
51
 
51
- 4. For separate lat- and longitudes invoke the C{.height} method as
52
+ 4. For separate lat- and longitudes invoke the C{height} method as
52
53
 
53
54
  C{>>> h = ginterpolator.height(lat, lon)}
54
55
 
@@ -56,10 +57,14 @@ or as 2 lists, 2 tuples, etc.
56
57
 
57
58
  C{>>> hs = ginterpolator.height(lats, lons)}
58
59
 
60
+ or for several positionals use the C{height_} method
61
+
62
+ C{>>> h1, h2, ... = ginterpolator.height_(lat1, lon1, lat2, lon2, ...)}
63
+
59
64
  5. An example is in U{issue #64<https://GitHub.com/mrJean1/PyGeodesy/issues/64>},
60
65
  courtesy of SBFRF.
61
66
 
62
- @note: Classes L{GeoidG2012B} and L{GeoidPGM} require both U{numpy
67
+ @note: Classes L{GeoidEGM96}, L{GeoidG2012B} and L{GeoidPGM} require both U{numpy
63
68
  <https://PyPI.org/project/numpy>} and U{scipy<https://PyPI.org/project/scipy>}
64
69
  to be installed.
65
70
 
@@ -70,6 +75,8 @@ courtesy of SBFRF.
70
75
  @see: I{Karney}'s U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/index.html>},
71
76
  U{Geoid height<https://GeographicLib.SourceForge.io/C++/doc/geoid.html>} and U{Installing
72
77
  the Geoid datasets<https://GeographicLib.SourceForge.io/C++/doc/geoid.html#geoidinst>},
78
+ World Geodetic System 1984 (WG84) and U{Earth Gravitational Model 96 (EGM96) Data and
79
+ Apps<https://earth-info.NGA.mil/index.php?dir=wgs84&action=wgs84>},
73
80
  U{SciPy<https://docs.SciPy.org/doc/scipy/reference/interpolate.html>} interpolation
74
81
  U{RectBivariateSpline<https://docs.SciPy.org/doc/scipy/reference/generated/scipy.interpolate.
75
82
  RectBivariateSpline.html>}, U{bisplrep/-ev<https://docs.scipy.org/doc/scipy/reference/generated/
@@ -82,22 +89,23 @@ courtesy of SBFRF.
82
89
  # make sure int/int division yields float quotient, see .basics
83
90
  from __future__ import division as _; del _ # PYCHOK semicolon
84
91
 
85
- from pygeodesy.basics import len2, map1, isodd, ub2str as _ub2str
92
+ from pygeodesy.basics import len2, min2, isodd, ub2str as _ub2str
86
93
  from pygeodesy.constants import EPS, _float as _F, _1_0, _N_90_0, _180_0, \
87
94
  _N_180_0, _360_0
88
- # from pygeodesy.datums import _ellipsoidal_datum # from .heights
95
+ from pygeodesy.datums import Datums, _ellipsoidal_datum, _WGS84
89
96
  # from pygeodesy.dms import parseDMS2 # _MODS
90
97
  from pygeodesy.errors import _incompatible, LenError, RangeError, _SciPyIssue, \
91
98
  _xkwds_pop2
92
99
  from pygeodesy.fmath import favg, Fdot, fdot, Fhorner, frange
93
100
  # from pygoedesy.formy import heightOrthometric # _MODS
94
101
  from pygeodesy.heights import _as_llis2, _ascalar, _HeightBase, HeightError, \
95
- _ellipsoidal_datum, _Wrap
102
+ _Wrap
96
103
  # from pygeodesy.internals import _version2 # _MODS
97
104
  from pygeodesy.interns import NN, _COLONSPACE_, _COMMASPACE_, _E_, _height_, \
98
105
  _in_, _kind_, _lat_, _lon_, _mean_, _N_, _n_a_, \
99
106
  _numpy_, _on_, _outside_, _S_, _s_, _scipy_, \
100
107
  _SPACE_, _stdev_, _tbd_, _W_, _width_, _4_
108
+ from pygeodesy.interns import _COMMA_ # PYCHOK used!
101
109
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _FOR_DOCS
102
110
  from pygeodesy.named import _name__, _Named, _NamedTuple
103
111
  # from pygeodesy.namedTuples import LatLon3Tuple # _MODS
@@ -118,18 +126,17 @@ except ImportError: # Python 3+
118
126
  from io import BytesIO as _BytesIO # PYCHOK expected
119
127
 
120
128
  __all__ = _ALL_LAZY.geoids
121
- __version__ = '24.11.05'
129
+ __version__ = '24.12.31'
122
130
 
123
- _assert_ = 'assert'
124
- _bHASH_ = b'#'
125
- _endian_ = 'endian'
126
- _format_ = '%s %r'
127
- _header_ = 'header'
128
- _intCs = {} # cache int value
129
- _lli_ = 'lli'
130
- _non_increasing_ = 'non-increasing'
131
- _rb_ = 'rb'
132
- _supported_ = 'supported'
131
+ _assert_ = 'assert'
132
+ _bHASH_ = b'#'
133
+ _endian_ = 'endian'
134
+ _format_ = '%s %r'
135
+ _header_ = 'header'
136
+ _intCs = {} # cache int value, del below
137
+ _lli_ = 'lli'
138
+ _rb_ = 'rb'
139
+ _supported_ = 'supported'
133
140
 
134
141
 
135
142
  class GeoidError(HeightError):
@@ -149,6 +156,7 @@ class _GeoidBase(_HeightBase):
149
156
  _Error = GeoidError # in ._HeightBase._as_lls, ...
150
157
  _geoid = _n_a_
151
158
  _hs_y_x = None # numpy 2darray, row-major order
159
+ _iscipy = True # scipy or Karney's interpolation
152
160
  _kind = 3 # order for interp2d, RectBivariateSpline
153
161
  # _kmin = 2 # min number of knots
154
162
  _knots = 0 # nlat * nlon
@@ -203,7 +211,7 @@ class _GeoidBase(_HeightBase):
203
211
  s=self._smooth).ev
204
212
  self._hs_y_x = hs # numpy 2darray, row-major
205
213
  self._nBytes = hs.nbytes # numpy size in bytes
206
- self._knots = p.knots # grid knots
214
+ self._knots = p.knots # grid knots == len(hs)
207
215
  self._lon_of = float(p.flon) # forward offset
208
216
  self._lon_og = g = float(p.glon) # reverse offset
209
217
  # shrink the bounding box by 1 unit on every side:
@@ -216,18 +224,18 @@ class _GeoidBase(_HeightBase):
216
224
  self._lon_hi -= g
217
225
 
218
226
  def __call__(self, *llis, **wrap_H):
219
- '''Interpolate the geoid height for one or several locations.
227
+ '''Interpolate the geoid (or orthometric) height for one or more locations.
220
228
 
221
- @arg llis: One or more locations (C{LatLon}s), all positional.
229
+ @arg llis: One or several locations (each C{LatLon}), all positional.
222
230
  @kwarg wrap_H: Keyword arguments C{B{wrap}=False} (C{bool}) and
223
- C{B{H}=False} (C{bool}). If C{B{wrap} is True},
224
- wrap or I{normalize} all B{C{llis}} locations. If
225
- C{B{H} is True}, return the I{orthometric} height
226
- instead of the I{geoid} height at each location.
231
+ C{B{H}=False} (C{bool}). Use C{B{wrap}=True} to wrap
232
+ or I{normalize} all B{C{llis}} locations. If C{B{H}
233
+ is True}, return the I{orthometric} height instead of
234
+ the I{geoid} height at each location.
227
235
 
228
- @return: A single interpolated geoid (or orthometric) height
229
- (C{float}) or a list or tuple of interpolated geoid
230
- (or orthometric) heights (C{float}s).
236
+ @return: A single geoid (or orthometric) height (C{float}) or
237
+ a list or tuple of geoid (or orthometric) heights (each
238
+ C{float}).
231
239
 
232
240
  @raise GeoidError: Insufficient number of B{C{llis}}, an invalid
233
241
  B{C{lli}} or the C{egm*.pgm} geoid file is closed.
@@ -245,7 +253,7 @@ class _GeoidBase(_HeightBase):
245
253
 
246
254
  @see: Function L{pygeodesy.heightOrthometric}.
247
255
  '''
248
- return self._called(llis, True, **wrap_H)
256
+ return self._called(llis, **wrap_H)
249
257
 
250
258
  def __enter__(self):
251
259
  '''Open context.
@@ -264,12 +272,12 @@ class _GeoidBase(_HeightBase):
264
272
  def __str__(self):
265
273
  return Fmt.PAREN(self.classname, repr(self.name))
266
274
 
267
- def _called(self, llis, iscipy, wrap=False, H=False):
275
+ def _called(self, llis, wrap=False, H=False):
268
276
  # handle __call__
269
277
  _H = self._heightOrthometric if H else None
270
278
  _as, llis = _as_llis2(llis, Error=GeoidError)
271
- hs, _w = [], _Wrap._latlonop(wrap)
272
- _a, _h = hs.append, self._hGeoid
279
+ _w, hs = _Wrap._latlonop(wrap), []
280
+ _h, _a = self._hGeoid, hs.append
273
281
  try:
274
282
  for i, lli in enumerate(llis):
275
283
  N = _h(*_w(lli.lat, lli.lon))
@@ -278,11 +286,11 @@ class _GeoidBase(_HeightBase):
278
286
  return _as(hs)
279
287
  except (GeoidError, RangeError) as x:
280
288
  # XXX avoid str(LatLon()) degree symbols
281
- t = _lli_ if _as is _ascalar else Fmt.INDEX(llis=i)
282
- lli = fstr((lli.lat, lli.lon), strepr=repr)
283
- raise type(x)(t, lli, wrap=wrap, H=H, cause=x)
289
+ n = _lli_ if _as is _ascalar else Fmt.INDEX(llis=i)
290
+ t = fstr((lli.lat, lli.lon), strepr=repr)
291
+ raise type(x)(n, t, wrap=wrap, H=H, cause=x)
284
292
  except Exception as x:
285
- if iscipy and self.scipy:
293
+ if self._iscipy and self.scipy:
286
294
  raise _SciPyIssue(x, self._ev_name)
287
295
  else:
288
296
  raise
@@ -350,12 +358,12 @@ class _GeoidBase(_HeightBase):
350
358
  raise LenError(self.__class__, grid=m, **{name: n})
351
359
  if d < 0:
352
360
  d, a = -d, list(reversed(a))
353
- for i in range(1, m):
354
- e = a[i] - a[i-1]
355
- if e < EPS: # non-increasing axis
356
- i = Fmt.INDEX(name, i)
357
- raise GeoidError(i, e, txt=_non_increasing_)
358
- return self.numpy.array(a), d
361
+ a = self.numpy.array(a)
362
+ m, i = min2(*map(float, a[1:] - a[:-1]))
363
+ if m < EPS: # non-increasing axis
364
+ i = Fmt.INDEX(name, i + 1)
365
+ raise GeoidError(i, m, txt_not_='increasing')
366
+ return a, d
359
367
 
360
368
  def _g2ll2(self, lat, lon): # PYCHOK no cover
361
369
  '''(INTERNAL) I{Must be overloaded}.'''
@@ -369,26 +377,39 @@ class _GeoidBase(_HeightBase):
369
377
  def height(self, lats, lons, **wrap):
370
378
  '''Interpolate the geoid height for one or several lat-/longitudes.
371
379
 
372
- @arg lats: Latitude or latitudes (C{degrees} or C{degrees}s).
373
- @arg lons: Longitude or longitudes (C{degrees} or C{degrees}s).
374
- @kwarg wrap: If C{True}, wrap or I{normalize} all B{C{lats}}
375
- and B{C{lons}} locations (C{bool}).
380
+ @arg lats: Latitude or latitudes (each C{degrees}).
381
+ @arg lons: Longitude or longitudes (each C{degrees}).
382
+ @kwarg wrap: Use C{B{wrap}=True} to wrap or I{normalize} all
383
+ B{C{lats}} and B{C{lons}}.
376
384
 
377
- @return: A single interpolated geoid height (C{float}) or a
378
- list of interpolated geoid heights (C{float}s).
385
+ @return: A single geoid height (C{float}) or a list of geoid
386
+ heights (each C{float}).
379
387
 
380
- @raise GeoidError: Insufficient or non-matching number of
381
- B{C{lats}} and B{C{lons}}.
388
+ @raise GeoidError: Insufficient or unequal number of B{C{lats}}
389
+ and B{C{lons}}.
382
390
 
383
- @raise RangeError: A B{C{lat}} or B{C{lon}} is outside this
384
- geoid's lat- or longitude range.
391
+ @raise RangeError: A B{C{lat}} or B{C{lon}} is outside this geoid's
392
+ lat- or longitude range.
385
393
 
386
394
  @raise SciPyError: A C{scipy} issue.
387
395
 
388
396
  @raise SciPyWarning: A C{scipy} warning as exception.
389
397
  '''
390
- lls = self._as_lls(lats, lons)
391
- return self(lls, **wrap) # __call__(ll) or __call__(lls)
398
+ return _HeightBase.height(self, lats, lons, **wrap)
399
+
400
+ def height_(self, *latlons, **wrap):
401
+ '''Interpolate the geoid height for each M{(latlons[i], latlons[i+1])
402
+ pair for i in range(0, len(latlons), B{2})}.
403
+
404
+ @arg latlons: Alternating lat-/longitude pairs (each C{degrees}),
405
+ all positional.
406
+
407
+ @see: Method L{height} for further details.
408
+
409
+ @return: A tuple of geoid heights (each C{float}).
410
+ '''
411
+ lls = tuple(self._as_lls(latlons[0::2], *latlons[1::2]))
412
+ return self._called(lls, **wrap)
392
413
 
393
414
  @property_ROver
394
415
  def _heightOrthometric(self):
@@ -396,9 +417,9 @@ class _GeoidBase(_HeightBase):
396
417
 
397
418
  def _hGeoid(self, lat, lon):
398
419
  out = self.outside(lat, lon)
399
- if out:
400
- lli = fstr((lat, lon), strepr=repr)
401
- raise RangeError(lli=lli, txt=_SPACE_(_outside_, _on_, out))
420
+ if out: # XXX avoid str(LatLon()) degree symbols
421
+ t = fstr((lat, lon), strepr=repr)
422
+ raise RangeError(lli=t, txt=_SPACE_(_outside_, _on_, out))
402
423
  return float(self._ev(*self._ll2g2(lat, lon)))
403
424
 
404
425
  @Property_RO
@@ -461,10 +482,10 @@ class _GeoidBase(_HeightBase):
461
482
  y, x = np.unravel_index(arg(hs, axis=None), hs.shape)
462
483
  return self._g2ll2(*self._gyx2g2(y, x)) + (float(hs[y, x]),)
463
484
 
464
- def _load(self, g, dtype, n, offset=0):
485
+ def _load(self, g, dtype=float, n=-1, offset=0, **sep): # sep=NN
465
486
  # numpy.fromfile, like .frombuffer
466
487
  g.seek(offset, _MODS.os.SEEK_SET)
467
- return self.numpy.fromfile(g, dtype, n)
488
+ return self.numpy.fromfile(g, dtype, count=n, **sep)
468
489
 
469
490
  @Property_RO
470
491
  def _lowerleft(self):
@@ -562,19 +583,17 @@ class _GeoidBase(_HeightBase):
562
583
  return g
563
584
 
564
585
  def outside(self, lat, lon):
565
- '''Check whether a location is outside this geoid's
566
- lat-/longitude or crop range.
586
+ '''Check whether a location is outside this geoid's lat-/longitude
587
+ or crop range.
567
588
 
568
589
  @arg lat: The latitude (C{degrees}).
569
590
  @arg lon: The longitude (C{degrees}).
570
591
 
571
- @return: A 1- or 2-character C{str} if outside or an
572
- empty C{str} if inside.
592
+ @return: A 1- or 2-character C{str} if outside, an empty C{str} otherwise.
573
593
  '''
574
- return (_S_ if lat < self._lat_lo else
575
- (_N_ if lat > self._lat_hi else NN)) + \
576
- (_W_ if lon < self._lon_lo else
577
- (_E_ if lon > self._lon_hi else NN))
594
+ lat = _S_ if lat < self._lat_lo else (_N_ if lat > self._lat_hi else NN)
595
+ lon = _W_ if lon < self._lon_lo else (_E_ if lon > self._lon_hi else NN)
596
+ return NN(lat, lon) if lat and lon else (lat or lon)
578
597
 
579
598
  @Property_RO
580
599
  def pgm(self):
@@ -687,6 +706,88 @@ class _GeoidBase(_HeightBase):
687
706
  return self._llh3LL(self._upperright, LatLon)
688
707
 
689
708
 
709
+ class GeoidEGM96(_GeoidBase):
710
+ '''Geoid height interpolator for the EGM96 U{15 Minute Interpolation Grid<https://earth-info.NGA.mil>}
711
+ based on C{SciPy} interpolation U{RectBivariateSpline<https://docs.SciPy.org/doc/scipy/reference/
712
+ generated/scipy.interpolate.RectBivariateSpline.html>}, U{interp2d<https://docs.SciPy.org/doc/scipy/
713
+ reference/generated/scipy.interpolate.interp2d.html>} or U{bisplrep/-ev<https://docs.scipy.org/doc/
714
+ scipy/reference/generated/scipy.interpolate.bisplrep.html>}.
715
+
716
+ Use only the C{WW15MGH.GRD} file, unzipped from the EGM96 U{15 Minute Interpolation Grid
717
+ <https://earth-info.NGA.mil/index.php?dir=wgs84&action=wgs84>} download.
718
+ '''
719
+ def __init__(self, EGM96_grd, datum=_WGS84, kind=3, smooth=0, **name_crop):
720
+ '''New L{GeoidEGM96} interpolator.
721
+
722
+ @arg EGM96_grd: An C{EGM96_grd} grid file name (C{.GRD}).
723
+ @kwarg datum: Optional grid datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}),
724
+ overriding C{WGS84}.
725
+ @kwarg kind: C{scipy.interpolate} order (C{int}), use 1..5 for U{RectBivariateSpline
726
+ <https://docs.SciPy.org/doc/scipy/reference/generated/scipy.interpolate.
727
+ RectBivariateSpline.html>} or -1, -3 or -5 for U{bisplrep/-ev<https://
728
+ docs.SciPy.org/doc/scipy/reference/generated/scipy.interpolate.bisplrep.html>}
729
+ or U{interp2d<https://docs.SciPy.org/doc/scipy/reference/generated/scipy.
730
+ interpolate.interp2d.html>} C{linear}, C{cubic} respectively C{quintic},
731
+ see note for more details.
732
+ @kwarg smooth: Smoothing factor for C{B{kind}=1..5} only (C{int}).
733
+ @kwarg name_crop: Optional geoid C{B{name}=NN} (C{str}) and UNSUPPORTED keyword argument
734
+ C{B{crop}=None}.
735
+
736
+ @raise GeoidError: Invalid B{C{crop}}, B{C{kind}} or B{C{smooth}} or a ECM96 grid file
737
+ B{C{ECM96_grd}} issue.
738
+
739
+ @raise ImportError: Package C{numpy} or C{scipy} not found or not installed.
740
+
741
+ @raise LenError: Grid file B{C{EGM96_grd}} axis mismatch.
742
+
743
+ @raise SciPyError: A C{scipy} issue.
744
+
745
+ @raise SciPyWarning: A C{scipy} warning as exception.
746
+
747
+ @raise TypeError: Invalid B{C{datum}}.
748
+
749
+ @note: Specify C{B{kind}=-1, -3 or -5} to use C{scipy.interpolate.interp2d}
750
+ before or C{scipy.interpolate.bisplrep/-ev} since C{Scipy} version 1.14.
751
+ '''
752
+ crop, name = _xkwds_pop2(name_crop, crop=None)
753
+ if crop is not None:
754
+ raise GeoidError(crop=crop, txt_not_=_supported_)
755
+
756
+ g = self._open(EGM96_grd, datum, kind, _name__(**name), smooth)
757
+ _ = self.numpy # import numpy for .fromfile, .reshape
758
+
759
+ try:
760
+ p, hs = _Gpars(), self._load(g, sep=_SPACE_) # text
761
+ p.slat, n, p.wlon, e, p.dlat, p.dlon = hs[:6] # n-s, 0-E
762
+ p.nlat = int((n - p.slat) / p.dlat) + 1 # include S
763
+ p.nlon = int((e - p.wlon) / p.dlon) + 1 # include W
764
+ p.knots = p.nlat * p.nlon # inverted lats N downto S
765
+ p.glon = _180_0 # Eastern lons 0-360
766
+ hs = hs[6:].reshape(p.nlat, p.nlon)
767
+ _GeoidBase.__init__(self, hs, p)
768
+
769
+ except Exception as x:
770
+ raise _SciPyIssue(x, _in_, repr(EGM96_grd))
771
+ finally:
772
+ g.close()
773
+
774
+ def _g2ll2(self, lat, lon):
775
+ # convert grid (lat, lon) to earth (lat, lon)
776
+ while lon > _180_0: # Eastern
777
+ lon -= _360_0
778
+ return -lat, lon # invert lat
779
+
780
+ def _ll2g2(self, lat, lon):
781
+ # convert earth (lat, lon) to grid (lat, lon)
782
+ while lon < 0: # Eastern
783
+ lon += _360_0
784
+ return -lat, lon # invert lat
785
+
786
+ if _FOR_DOCS:
787
+ __call__ = _GeoidBase.__call__
788
+ height = _GeoidBase.height
789
+
790
+
690
791
  class GeoidG2012B(_GeoidBase):
691
792
  '''Geoid height interpolator for U{GEOID12B Model
692
793
  <https://www.NGS.NOAA.gov/GEOID/GEOID12B/>} grids U{CONUS
@@ -698,24 +799,24 @@ class GeoidG2012B(_GeoidBase):
698
799
  U{Puerto Rico and U.S. Virgin Islands
699
800
  <https://www.NGS.NOAA.gov/GEOID/GEOID12B/GEOID12B_PRVI.shtml>} and
700
801
  U{American Samoa<https://www.NGS.NOAA.gov/GEOID/GEOID12B/GEOID12B_AS.shtml>}
701
- based on C{SciPy} U{RectBivariateSpline<https://docs.SciPy.org/doc/scipy/
702
- reference/generated/scipy.interpolate.RectBivariateSpline.html>}, U{interp2d
703
- <https://docs.SciPy.org/doc/scipy/reference/generated/scipy.interpolate
704
- .interp2d.html>} or U{bisplrep/-ev<https://docs.scipy.org/doc/scipy/reference/
705
- generated/scipy.interpolate.bisplrep.html>} interpolation.
706
-
707
- Use any of the binary C{le} (little endian) or C{be} (big endian)
708
- C{g2012b*.bin} grid files.
802
+ based on C{SciPy} interpolation U{RectBivariateSpline<https://docs.SciPy.org/doc/
803
+ scipy/reference/generated/scipy.interpolate.RectBivariateSpline.html>}, U{interp2d
804
+ <https://docs.SciPy.org/doc/scipy/reference/generated/scipy.interpolate.interp2d.html>}
805
+ or U{bisplrep/-ev<https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.
806
+ bisplrep.html>}.
807
+
808
+ Use any of the C{le} (little endian) or C{be} (big endian) C{g2012b*.bin} binary grid files.
709
809
  '''
710
- def __init__(self, g2012b_bin, datum=None, # NAD 83 Ellipsoid
711
- kind=3, smooth=0, **name_crop):
810
+ _datum = Datums.NAD83
811
+
812
+ def __init__(self, g2012b_bin, datum=Datums.NAD83, kind=3, smooth=0, **name_crop):
712
813
  '''New L{GeoidG2012B} interpolator.
713
814
 
714
815
  @arg g2012b_bin: A C{GEOID12B} grid file name (C{.bin}).
715
816
  @kwarg datum: Optional grid datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or
716
- L{a_f2Tuple}), default C{WGS84}.
817
+ L{a_f2Tuple}), overriding C{NAD83}.
717
818
  @kwarg kind: C{scipy.interpolate} order (C{int}), use 1..5 for U{RectBivariateSpline
718
- <https://docs.SciPy.org/doc/scipy/ reference/generated/scipy.interpolate.
819
+ <https://docs.SciPy.org/doc/scipy/reference/generated/scipy.interpolate.
719
820
  RectBivariateSpline.html>} or -1, -3 or -5 for U{bisplrep/-ev<https://
720
821
  docs.SciPy.org/doc/scipy/reference/generated/scipy.interpolate.bisplrep.html>}
721
822
  or U{interp2d<https://docs.SciPy.org/doc/scipy/reference/generated/scipy.
@@ -804,15 +905,17 @@ class GeoidHeight5Tuple(_NamedTuple): # .geoids.py
804
905
  def _I(i):
805
906
  '''(INTERNAL) Cache a single C{int} constant.
806
907
  '''
908
+ i = int(i)
807
909
  return _intCs.setdefault(i, i) # PYCHOK undefined due to del _intCs
808
910
 
809
911
 
810
- def _T(*cs):
912
+ def _T(cs):
811
913
  '''(INTERNAL) Cache a tuple of single C{int} constants.
812
914
  '''
813
- return map1(_I, *cs)
915
+ cs = cs.replace(_COMMA_, _SPACE_).strip()
916
+ return tuple(map(_I, cs.split()))
814
917
 
815
- _T0s12 = (_I(0),) * 12 # PYCHOK _T(0, 0, ..., 0)
918
+ _T0s12 = (_I(0),) * 12 # PYCHOK _T('0, 0, ..., 0')
816
919
 
817
920
 
818
921
  class GeoidKarney(_GeoidBase):
@@ -831,56 +934,56 @@ class GeoidKarney(_GeoidBase):
831
934
  '''
832
935
  _C0 = _F(372), _F(240), _F(372) # n, _ and s common denominators
833
936
  # matrices c3n_, c3, c3s_, transposed from GeographicLib/Geoid.cpp
834
- _C3 = ((_T(0, 0, 62, 124, 124, 62, 0, 0, 0, 0, 0, 0),
937
+ _C3 = ((_T('0, 0, 62, 124, 124, 62, 0, 0, 0, 0, 0, 0'),
835
938
  _T0s12,
836
- _T(-131, 7, -31, -62, -62, -31, 45, 216, 156, -45, -55, -7),
939
+ _T('-131, 7, -31, -62, -62, -31, 45, 216, 156, -45, -55, -7'),
837
940
  _T0s12,
838
- _T(138, -138, 0, 0, 0, 0, -183, 33, 153, -3, 48, -48), # PYCHOK indent
839
- _T(144, 42, -62, -124, -124, -62, -9, 87, 99, 9, 42, -42),
941
+ _T('138, -138, 0, 0, 0, 0, -183, 33, 153, -3, 48, -48'), # PYCHOK indent
942
+ _T('144, 42, -62, -124, -124, -62, -9, 87, 99, 9, 42, -42'),
840
943
  _T0s12,
841
- _T(0, 0, 0, 0, 0, 0, 93, -93, -93, 93, 0, 0),
842
- _T(-102, 102, 0, 0, 0, 0, 18, 12, -12, -18, -84, 84),
843
- _T(-31, -31, 31, 62, 62, 31, 0, -93, -93, 0, 31, 31)), # PYCHOK indent
844
-
845
- (_T(9, -9, 9, 186, 54, -9, -9, 54, -54, 9, -9, 9),
846
- _T(-18, 18, -88, -42, 162, -32, 8, -78, 78, -8, 18, -18),
847
- _T(-88, 8, -18, -42, -78, 18, 18, 162, 78, -18, -32, -8),
848
- _T(0, 0, 90, -150, 30, 30, 30, -90, 90, -30, 0, 0),
849
- _T(96, -96, 96, -96, -24, 24, -96, -24, 144, -24, 24, -24), # PYCHOK indent
850
- _T(90, 30, 0, -150, -90, 0, 0, 30, 90, 0, 30, -30),
851
- _T(0, 0, -20, 60, -60, 20, -20, 60, -60, 20, 0, 0),
852
- _T(0, 0, -60, 60, 60, -60, 60, -60, -60, 60, 0, 0),
853
- _T(-60, 60, 0, 60, -60, 0, 0, 60, -60, 0, -60, 60),
854
- _T(-20, -20, 0, 60, 60, 0, 0, -60, -60, 0, 20, 20)),
855
-
856
- (_T(18, -18, 36, 210, 162, -36, 0, 0, 0, 0, -18, 18), # PYCHOK indent
857
- _T(-36, 36, -165, 45, 141, -21, 0, 0, 0, 0, 36, -36),
858
- _T(-122, -2, -27, -111, -75, 27, 62, 124, 124, 62, -64, 2),
859
- _T(0, 0, 93, -93, -93, 93, 0, 0, 0, 0, 0, 0),
860
- _T(120, -120, 147, -57, -129, 39, 0, 0, 0, 0, 66, -66), # PYCHOK indent
861
- _T(135, 51, -9, -192, -180, 9, 31, 62, 62, 31, 51, -51),
944
+ _T('0, 0, 0, 0, 0, 0, 93, -93, -93, 93, 0, 0'),
945
+ _T('-102, 102, 0, 0, 0, 0, 18, 12, -12, -18, -84, 84'),
946
+ _T('-31, -31, 31, 62, 62, 31, 0, -93, -93, 0, 31, 31')), # PYCHOK indent
947
+
948
+ (_T('9, -9, 9, 186, 54, -9, -9, 54, -54, 9, -9, 9'),
949
+ _T('-18, 18, -88, -42, 162, -32, 8, -78, 78, -8, 18, -18'),
950
+ _T('-88, 8, -18, -42, -78, 18, 18, 162, 78, -18, -32, -8'),
951
+ _T('0, 0, 90, -150, 30, 30, 30, -90, 90, -30, 0, 0'),
952
+ _T('96, -96, 96, -96, -24, 24, -96, -24, 144, -24, 24, -24'), # PYCHOK indent
953
+ _T('90, 30, 0, -150, -90, 0, 0, 30, 90, 0, 30, -30'),
954
+ _T('0, 0, -20, 60, -60, 20, -20, 60, -60, 20, 0, 0'),
955
+ _T('0, 0, -60, 60, 60, -60, 60, -60, -60, 60, 0, 0'),
956
+ _T('-60, 60, 0, 60, -60, 0, 0, 60, -60, 0, -60, 60'),
957
+ _T('-20, -20, 0, 60, 60, 0, 0, -60, -60, 0, 20, 20')),
958
+
959
+ (_T('18, -18, 36, 210, 162, -36, 0, 0, 0, 0, -18, 18'), # PYCHOK indent
960
+ _T('-36, 36, -165, 45, 141, -21, 0, 0, 0, 0, 36, -36'),
961
+ _T('-122, -2, -27, -111, -75, 27, 62, 124, 124, 62, -64, 2'),
962
+ _T('0, 0, 93, -93, -93, 93, 0, 0, 0, 0, 0, 0'),
963
+ _T('120, -120, 147, -57, -129, 39, 0, 0, 0, 0, 66, -66'), # PYCHOK indent
964
+ _T('135, 51, -9, -192, -180, 9, 31, 62, 62, 31, 51, -51'),
862
965
  _T0s12,
863
- _T(0, 0, -93, 93, 93, -93, 0, 0, 0, 0, 0, 0),
864
- _T(-84, 84, 18, 12, -12, -18, 0, 0, 0, 0, -102, 102),
865
- _T(-31, -31, 0, 93, 93, 0, -31, -62, -62, -31, 31, 31)))
866
-
867
- _BT = (_T(0, 0), # bilinear 4-tuple [i, j] indices
868
- _T(1, 0),
869
- _T(0, 1),
870
- _T(1, 1))
871
-
872
- _CM = (_T( 0, -1), # 10x12 cubic matrix [i, j] indices
873
- _T( 1, -1),
874
- _T(-1, 0),
875
- _T( 0, 0),
876
- _T( 1, 0),
877
- _T( 2, 0),
878
- _T(-1, 1),
879
- _T( 0, 1),
880
- _T( 1, 1),
881
- _T( 2, 1),
882
- _T( 0, 2),
883
- _T( 1, 2))
966
+ _T('0, 0, -93, 93, 93, -93, 0, 0, 0, 0, 0, 0'),
967
+ _T('-84, 84, 18, 12, -12, -18, 0, 0, 0, 0, -102, 102'),
968
+ _T('-31, -31, 0, 93, 93, 0, -31, -62, -62, -31, 31, 31')))
969
+
970
+ _BT = (_T('0, 0'), # bilinear 4-tuple [i, j] indices
971
+ _T('1, 0'),
972
+ _T('0, 1'),
973
+ _T('1, 1'))
974
+
975
+ _CM = (_T(' 0, -1'), # 10x12 cubic matrix [i, j] indices
976
+ _T(' 1, -1'),
977
+ _T('-1, 0'),
978
+ _T(' 0, 0'),
979
+ _T(' 1, 0'),
980
+ _T(' 2, 0'),
981
+ _T('-1, 1'),
982
+ _T(' 0, 1'),
983
+ _T(' 1, 1'),
984
+ _T(' 2, 1'),
985
+ _T(' 0, 2'),
986
+ _T(' 1, 2'))
884
987
 
885
988
  # _cropped = None
886
989
  _endian = '>H' # struct.unpack 1 ushort (big endian, unsigned short)
@@ -889,12 +992,13 @@ class GeoidKarney(_GeoidBase):
889
992
  # _highest = (-8.4, 147.367, 85.839) if egm2008-1.pgm else (
890
993
  # (-8.167, 147.25, 85.422) if egm96-5.pgm else
891
994
  # (-4.5, 148.75, 81.33)) # egm84-15.pgm
995
+ _iscipy = False
892
996
  # _lowest = (4.7, 78.767, -106.911) if egm2008-1.pgm else (
893
997
  # (4.667, 78.833, -107.043) if egm96-5.pgm else
894
998
  # (4.75, 79.25, -107.34)) # egm84-15.pgm
895
999
  _mean = _F(-1.317) # from egm2008-1, -1.438 egm96-5, -0.855 egm84-15
896
1000
  _nBytes = None # not applicable
897
- _nterms = len(_C3[0]) # columns length, number of row
1001
+ _nterms = len(_C3[0]) # columns length, number of rows
898
1002
  _smooth = None # not applicable
899
1003
  _stdev = _F(29.244) # from egm2008-1, 29.227 egm96-5, 29.183 egm84-15
900
1004
  _u2B = _calcsize(_endian) # pixelsize_ in bytes
@@ -904,8 +1008,7 @@ class GeoidKarney(_GeoidBase):
904
1008
  _yx_i = () # cached (y, x) indices
905
1009
  _yx_t = () # cached 4- or 10-tuple for _ev2k resp. _ev3k
906
1010
 
907
- def __init__(self, egm_pgm, crop=None, datum=None, # WGS84
908
- kind=3, **name_smooth):
1011
+ def __init__(self, egm_pgm, crop=None, datum=_WGS84, kind=3, **name_smooth):
909
1012
  '''New L{GeoidKarney} interpolator.
910
1013
 
911
1014
  @arg egm_pgm: An U{EGM geoid dataset<https://GeographicLib.SourceForge.io/
@@ -916,7 +1019,7 @@ class GeoidKarney(_GeoidBase):
916
1019
  with 2 C{degrees90} lat- and C{degrees180} longitudes or as
917
1020
  2-tuple (C{LatLonSW, LatLonNE}) of C{LatLon} instances.
918
1021
  @kwarg datum: Optional grid datum (C{Datum}, L{Ellipsoid}, L{Ellipsoid2} or
919
- L{a_f2Tuple}), default C{WGS84}.
1022
+ L{a_f2Tuple}), overriding C{WGS84}.
920
1023
  @kwarg kind: Interpolation order (C{int}), 2 for C{bilinear} or 3 for C{cubic}.
921
1024
  @kwarg name_smooth: Optional geoid C{B{name}=NN} (C{str}) and UNSUPPORTED
922
1025
  keyword argument C{B{smooth}}, use C{B{smooth}=None} to ignore.
@@ -954,34 +1057,6 @@ class GeoidKarney(_GeoidBase):
954
1057
  self._lat_hi, self._lon_hi = self._swne(crop if crop else p.crop4)
955
1058
  self._cropped = bool(crop)
956
1059
 
957
- def __call__(self, *llis, **wrap_H):
958
- '''Interpolate the geoid height for one or several locations.
959
-
960
- @arg llis: One or more locations (C{LatLon}s), all positional.
961
- @kwarg wrap_H: Keyword arguments C{B{wrap}=False, B{H}=False}.
962
- If C{B{wrap} is True}, wrap or I{normalize} all
963
- B{C{llis}} locations (C{bool}). If C{B{H} is True},
964
- return the I{orthometric} height instead of the
965
- I{geoid} height at each location (C{bool}).
966
-
967
- @return: A single interpolated geoid (or orthometric) height
968
- (C{float}) or a list or tuple of interpolated geoid
969
- (or orthometric) heights (C{float}s).
970
-
971
- @raise GeoidError: Insufficient number of B{C{llis}}, an invalid
972
- B{C{lli}} or the C{egm*.pgm} geoid file is closed.
973
-
974
- @raise RangeError: An B{C{lli}} is outside this geoid's lat- or
975
- longitude range.
976
-
977
- @note: To obtain I{orthometric} heights, each B{C{llis}} location
978
- must have an ellipsoid C{height} or C{h} attribute, otherwise
979
- C{height=0} is used.
980
-
981
- @see: Function L{pygeodesy.heightOrthometric}.
982
- '''
983
- return self._called(llis, False, **wrap_H)
984
-
985
1060
  def _c0c3v(self, y, x):
986
1061
  # get the common denominator, the 10x12 cubic matrix and
987
1062
  # the 12 cubic v-coefficients around geoid index (y, x)
@@ -1036,7 +1111,7 @@ class GeoidKarney(_GeoidBase):
1036
1111
  else:
1037
1112
  y, x = self._yx_i = yx
1038
1113
  self._yx_t = self._raws(y, x, GeoidKarney._BT)
1039
- t = self._yx_t
1114
+ t = self._yx_t
1040
1115
  v = _1_0, -fx, fx
1041
1116
  H = Fdot(v, t[0], t[0], t[1]).fmul(_1_0 - fy) # c = a * (1 - fy)
1042
1117
  H += Fdot(v, t[2], t[2], t[3]).fmul(fy) # c += b * fy
@@ -1055,7 +1130,7 @@ class GeoidKarney(_GeoidBase):
1055
1130
  # real h = t[0] + fx * (t[1] + fx * (t[3] + fx * t[6])) +
1056
1131
  # fy * (t[2] + fx * (t[4] + fx * t[7]) +
1057
1132
  # fy * (t[5] + fx * t[8] + fy * t[9]));
1058
- t = self._yx_t
1133
+ t = self._yx_t
1059
1134
  v = _1_0, fx, fy
1060
1135
  H = Fdot(v, t[5], t[8], t[9])
1061
1136
  H *= fy
@@ -1083,27 +1158,6 @@ class GeoidKarney(_GeoidBase):
1083
1158
  p = self._pgm
1084
1159
  return (p.slat + p.dlat * y), (p.wlon + p.dlon * x)
1085
1160
 
1086
- def height(self, lats, lons, **wrap):
1087
- '''Interpolate the geoid height for one or several lat-/longitudes.
1088
-
1089
- @arg lats: Latitude or latitudes (C{degrees} or C{degrees}s).
1090
- @arg lons: Longitude or longitudes (C{degrees} or C{degrees}s).
1091
- @kwarg wrap: If C{True}, wrap or I{normalize} all B{C{lats}}
1092
- and B{C{lons}} locations (C{bool}).
1093
-
1094
- @return: A single interpolated geoid height (C{float}) or a
1095
- list of interpolated geoid heights (C{float}s).
1096
-
1097
- @raise GeoidError: Insufficient or non-matching number of
1098
- B{C{lats}} and B{C{lons}} or the C{egm*.pgm}
1099
- geoid file is closed.
1100
-
1101
- @raise RangeError: A B{C{lat}} or B{C{lon}} is outside this
1102
- geoid's lat- or longitude range.
1103
- '''
1104
- lls = self._as_lls(lats, lons)
1105
- return self(lls, **wrap) # __call__(ll) or __call__(lls)
1106
-
1107
1161
  @Property_RO
1108
1162
  def _highest_ltd(self):
1109
1163
  '''(INTERNAL) Cache for C{.highest}.
@@ -1249,8 +1303,7 @@ class GeoidPGM(_GeoidBase):
1249
1303
  _cropped = False
1250
1304
  _endian = '>u2'
1251
1305
 
1252
- def __init__(self, egm_pgm, crop=None, datum=None, # WGS84
1253
- kind=3, smooth=0, **name):
1306
+ def __init__(self, egm_pgm, crop=None, datum=_WGS84, kind=3, smooth=0, **name):
1254
1307
  '''New L{GeoidPGM} interpolator.
1255
1308
 
1256
1309
  @arg egm_pgm: An U{EGM geoid dataset<https://GeographicLib.SourceForge.io/
@@ -1260,7 +1313,7 @@ class GeoidPGM(_GeoidBase):
1260
1313
  in C{degrees90} lat- and C{degrees180} longitudes or a 2-tuple
1261
1314
  (C{LatLonSW, LatLonNE}) of C{LatLon} instances.
1262
1315
  @kwarg datum: Optional grid datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or
1263
- L{a_f2Tuple}), default C{WGS84}.
1316
+ L{a_f2Tuple}), overriding C{WGS84}.
1264
1317
  @kwarg kind: C{scipy.interpolate} order (C{int}), use 1..5 for U{RectBivariateSpline
1265
1318
  <https://docs.SciPy.org/doc/scipy/reference/generated/scipy.interpolate.
1266
1319
  RectBivariateSpline.html>} or -1, -3 or -5 for U{bisplrep/-ev<https://
@@ -1670,7 +1723,7 @@ if __name__ == '__main__': # MCCABE 14
1670
1723
  from pygeodesy.internals import printf, _secs2str, _versions, _sys
1671
1724
  from time import time
1672
1725
 
1673
- _crop = ()
1726
+ _crop = {}
1674
1727
  _GeoidEGM = GeoidKarney
1675
1728
  _kind = 3
1676
1729
 
@@ -1679,7 +1732,10 @@ if __name__ == '__main__': # MCCABE 14
1679
1732
  geoid = geoids.pop(0)
1680
1733
 
1681
1734
  if '-crop'.startswith(geoid.lower()):
1682
- _crop = 20, -125, 50, -65 # CONUS
1735
+ _crop = dict(crp=(20, -125, 50, -65)) # CONUS
1736
+
1737
+ elif '-egm96'.startswith(geoid.lower()):
1738
+ _GeoidEGM = GeoidEGM96
1683
1739
 
1684
1740
  elif '-karney'.startswith(geoid.lower()):
1685
1741
  _GeoidEGM = GeoidKarney
@@ -1690,13 +1746,15 @@ if __name__ == '__main__': # MCCABE 14
1690
1746
  elif '-pgm'.startswith(geoid.lower()):
1691
1747
  _GeoidEGM = GeoidPGM
1692
1748
 
1693
- elif geoid[-4:].lower() in ('.pgm',):
1694
- g = _GeoidEGM(geoid, crop=_crop, kind=_kind)
1749
+ elif geoid[-4:].lower() in ('.pgm', '.grd'):
1750
+ g = _GeoidEGM(geoid, kind=_kind, **_crop)
1695
1751
  t = time()
1696
1752
  _ = g.highest()
1697
1753
  t = _secs2str(time() - t)
1698
1754
  printf('%s: %s (%s)', g.toStr(), t, _versions(), nl=1, nt=1)
1699
- printf(repr(g.pgm), nt=1)
1755
+ t = g.pgm
1756
+ if t:
1757
+ printf(repr(t), nt=1)
1700
1758
  # <https://GeographicLib.SourceForge.io/cgi-bin/GeoidEval>:
1701
1759
  # The height of the EGM96 geoid at Timbuktu
1702
1760
  # echo 16:46:33N 3:00:34W | GeoidEval
@@ -1722,8 +1780,8 @@ if __name__ == '__main__': # MCCABE 14
1722
1780
  else:
1723
1781
  raise GeoidError(grid=repr(geoid))
1724
1782
 
1725
- _I = int # PYCHOK unused _I
1726
- del _intCs # trash ints cache
1783
+ _I = int # PYCHOK unused _I
1784
+ del _intCs, _T, _T0s12 # trash ints cache and map
1727
1785
 
1728
1786
 
1729
1787
  # <https://GeographicLib.SourceForge.io/cgi-bin/GeoidEval>
@@ -1740,6 +1798,14 @@ del _intCs # trash ints cache
1740
1798
  # _upperright = 90, 180, 13.0980 # egm84-15.pgm
1741
1799
 
1742
1800
 
1801
+ # % python3.12 -m pygeodesy.geoids -egm96 ../testGeoids/WW15MGH.GRD
1802
+ #
1803
+ # GeoidEGM96('WW15MGH.GRD'): lowerleft(-90.0, -180.0, -29.534), upperright(90.0, 180.25, 13.606), center(0.0, 0.125, 17.125), highest(-8.25, -32.75, 85.391), lowest(4.75, -101.25, -106.991): 1.267 ms (pygeodesy 24.12.24 Python 3.12.7 64bit arm64 macOS 14.6.1)
1804
+ #
1805
+ # Timbuktu GeoidEGM96('WW15MGH.GRD').height(16.775833, -3.009444): 28.7073 vs 28.7880
1806
+ # Timbuktu GeoidEGM96('WW15MGH.GRD').height(16.776, -3.009): 28.7072 vs 28.7880
1807
+
1808
+
1743
1809
  # % python3.12 -m pygeodesy.geoids -Karney ../testGeoids/egm*.pgm
1744
1810
  #
1745
1811
  # GeoidKarney('egm2008-1.pgm'): lowerleft(-90.0, -180.0, -30.15), upperright(90.0, 180.0, 14.898), center(0.0, 0.0, 17.226), highest(-8.4, 147.367, 85.839), lowest(4.7, 78.767, -106.911): 204.334 ms (pygeodesy 24.8.24 Python 3.12.5 64bit arm64 macOS 14.6.1)
@@ -1764,6 +1830,14 @@ del _intCs # trash ints cache
1764
1830
  # Timbuktu GeoidKarney('egm96-5.pgm').height(16.776, -3.009): 28.7067 vs 28.7067
1765
1831
 
1766
1832
 
1833
+ # % python3.8 -m pygeodesy.geoids -egm96 ../testGeoids/WW15MGH.GRD
1834
+ #
1835
+ # GeoidEGM96('WW15MGH.GRD'): lowerleft(-90.0, -180.0, -29.534), upperright(90.0, 180.25, 13.606), center(0.0, 0.125, 17.125), highest(-8.25, -32.75, 85.391), lowest(4.75, -101.25, -106.991): 1.267 ms (pygeodesy 24.12.24 Python 3.8.10 64bit arm64_x86_64 macOS 10.16)
1836
+ #
1837
+ # Timbuktu GeoidEGM96('WW15MGH.GRD').height(16.775833, -3.009444): 28.7073 vs 28.7880
1838
+ # Timbuktu GeoidEGM96('WW15MGH.GRD').height(16.776, -3.009): 28.7072 vs 28.7880
1839
+
1840
+
1767
1841
  # % python3.8 -m pygeodesy.geoids -Karney ../testGeoids/egm*.pgm
1768
1842
  #
1769
1843
  # GeoidKarney('egm2008-1.pgm'): lowerleft(-90.0, -180.0, -30.15), upperright(90.0, 180.0, 14.898), center(0.0, 0.0, 17.226), highest(-8.4, 147.367, 85.839), lowest(4.7, 78.767, -106.911): 353.050 ms (pygeodesy 24.8.24 Python 3.8.10 64bit arm64_x86_64 macOS 10.16)
@@ -1837,7 +1911,7 @@ del _intCs # trash ints cache
1837
1911
 
1838
1912
  # **) MIT License
1839
1913
  #
1840
- # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1914
+ # Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
1841
1915
  #
1842
1916
  # Permission is hereby granted, free of charge, to any person obtaining a
1843
1917
  # copy of this software and associated documentation files (the "Software"),