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/geoids.py
ADDED
|
@@ -0,0 +1,1884 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Geoid models and geoid height interpolations.
|
|
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.
|
|
10
|
+
|
|
11
|
+
L{GeoidKarney} is a transcoding of I{Charles Karney}'s C++ class U{Geoid
|
|
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
|
|
15
|
+
require those packages to be installed.
|
|
16
|
+
|
|
17
|
+
In addition, each geoid interpolator needs C{grid knots} (down)loaded from
|
|
18
|
+
a C{geoid} model file, I{specific to the interpolator}, more details below
|
|
19
|
+
and in the documentation of the interpolator class. For each interpolator,
|
|
20
|
+
there are several interpolation choices, like I{linear}, I{cubic}, etc.
|
|
21
|
+
|
|
22
|
+
Typical usage
|
|
23
|
+
=============
|
|
24
|
+
|
|
25
|
+
1. Choose one of the interpolator classes L{GeoidG2012B}, L{GeoidKarney}
|
|
26
|
+
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.
|
|
29
|
+
|
|
30
|
+
C{>>> from pygeodesy import GeoidG2012B # or -Karney or -PGM as GeoidXyz}
|
|
31
|
+
|
|
32
|
+
2. Instantiate an interpolator with the C{geoid} model file and use keyword
|
|
33
|
+
arguments to select different interpolation options
|
|
34
|
+
|
|
35
|
+
C{>>> ginterpolator = GeoidXyz(geoid_model_file, **options)}
|
|
36
|
+
|
|
37
|
+
3. Get the interpolated geoid height of other C{LatLon} location(s) with
|
|
38
|
+
|
|
39
|
+
C{>>> ll = LatLon(1, 2, ...)}
|
|
40
|
+
|
|
41
|
+
C{>>> h = ginterpolator(ll)}
|
|
42
|
+
|
|
43
|
+
or
|
|
44
|
+
|
|
45
|
+
C{>>> h0, h1, h2, ... = ginterpolator(ll0, ll1, ll2, ...)}
|
|
46
|
+
|
|
47
|
+
or a list, tuple, generator, etc. of C{LatLon}s
|
|
48
|
+
|
|
49
|
+
C{>>> hs = ginterpolator(lls)}
|
|
50
|
+
|
|
51
|
+
4. For separate lat- and longitudes invoke the C{.height} method as
|
|
52
|
+
|
|
53
|
+
C{>>> h = ginterpolator.height(lat, lon)}
|
|
54
|
+
|
|
55
|
+
or as 2 lists, 2 tuples, etc.
|
|
56
|
+
|
|
57
|
+
C{>>> hs = ginterpolator.height(lats, lons)}
|
|
58
|
+
|
|
59
|
+
5. An example is in U{issue #64<https://GitHub.com/mrJean1/PyGeodesy/issues/64>},
|
|
60
|
+
courtesy of SBFRF.
|
|
61
|
+
|
|
62
|
+
@note: Classes L{GeoidG2012B} and L{GeoidPGM} require both U{numpy
|
|
63
|
+
<https://PyPI.org/project/numpy>} and U{scipy<https://PyPI.org/project/scipy>}
|
|
64
|
+
to be installed.
|
|
65
|
+
|
|
66
|
+
@note: Errors from C{scipy} are raised as L{SciPyError}s. Warnings issued by
|
|
67
|
+
C{scipy} can be thrown as L{SciPyWarning} exceptions, provided Python
|
|
68
|
+
C{warnings} are filtered accordingly, see L{SciPyWarning}.
|
|
69
|
+
|
|
70
|
+
@see: I{Karney}'s U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/index.html>},
|
|
71
|
+
U{Geoid height<https://GeographicLib.SourceForge.io/C++/doc/geoid.html>} and U{Installing
|
|
72
|
+
the Geoid datasets<https://GeographicLib.SourceForge.io/C++/doc/geoid.html#geoidinst>},
|
|
73
|
+
U{SciPy<https://docs.SciPy.org/doc/scipy/reference/interpolate.html>} interpolation
|
|
74
|
+
U{RectBivariateSpline<https://docs.SciPy.org/doc/scipy/reference/generated/scipy.
|
|
75
|
+
interpolate.RectBivariateSpline.html>} and U{interp2d<https://docs.SciPy.org/doc/scipy/
|
|
76
|
+
reference/generated/scipy.interpolate.interp2d.html>}, functions L{elevations.elevation2}
|
|
77
|
+
and L{elevations.geoidHeight2}, U{I{Ellispoid vs Orthometric Elevations}<https://
|
|
78
|
+
www.YouTube.com/watch?v=dX6a6kCk3Po>} and U{I{Pitfalls Related to Ellipsoid Height
|
|
79
|
+
and Height Above Mean Sea Level (AMSL)}<https://Wiki.ROS.org/mavros#mavros.2FPlugins.
|
|
80
|
+
Avoiding_Pitfalls_Related_to_Ellipsoid_Height_and_Height_Above_Mean_Sea_Level>}.
|
|
81
|
+
'''
|
|
82
|
+
# make sure int/int division yields float quotient, see .basics
|
|
83
|
+
from __future__ import division as _; del _ # PYCHOK semicolon
|
|
84
|
+
|
|
85
|
+
from pygeodesy.basics import len2, map1, isodd, ub2str as _ub2str
|
|
86
|
+
from pygeodesy.constants import EPS, _float as _F, _0_0, _1_0, _180_0, _360_0
|
|
87
|
+
# from pygeodesy.datums import _ellipsoidal_datum # from .heights
|
|
88
|
+
# from pygeodesy.dms import parseDMS2 # _MODS
|
|
89
|
+
from pygeodesy.errors import _incompatible, LenError, RangeError, SciPyError, \
|
|
90
|
+
_SciPyIssue
|
|
91
|
+
from pygeodesy.fmath import favg, Fdot, fdot, Fhorner, frange
|
|
92
|
+
# from pygoedesy.formy import heightOrthometric # _MODS
|
|
93
|
+
from pygeodesy.heights import _as_llis2, _ascalar, _height_called, HeightError, \
|
|
94
|
+
_HeightsBase, _ellipsoidal_datum, _Wrap
|
|
95
|
+
from pygeodesy.interns import MISSING, NN, _4_, _COLONSPACE_, _COMMASPACE_, \
|
|
96
|
+
_cubic_, _E_, _height_, _in_, _kind_, _knots_, \
|
|
97
|
+
_lat_, _linear_, _lon_, _mean_, _N_, _n_a_, \
|
|
98
|
+
_not_, _numpy_, _on_, _outside_, _S_, _s_, \
|
|
99
|
+
_scipy_, _SPACE_, _stdev_, _supported_, _tbd_, \
|
|
100
|
+
_W_, _width_, _version2
|
|
101
|
+
from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _FOR_DOCS
|
|
102
|
+
from pygeodesy.named import _Named, _NamedTuple, notOverloaded
|
|
103
|
+
# from pygeodesy.namedTuples import LatLon3Tuple # _MODS
|
|
104
|
+
from pygeodesy.props import deprecated_method, Property_RO, property_RO
|
|
105
|
+
from pygeodesy.streprs import attrs, Fmt, fstr, pairs
|
|
106
|
+
from pygeodesy.units import Height, Int_, Lat, Lon
|
|
107
|
+
# from pygeodesy.utily import _Wrap # from .heights
|
|
108
|
+
|
|
109
|
+
from math import floor
|
|
110
|
+
import os.path as _os_path
|
|
111
|
+
from os import SEEK_CUR as _SEEK_CUR, SEEK_SET as _SEEK_SET
|
|
112
|
+
from struct import calcsize as _calcsize, unpack as _unpack
|
|
113
|
+
try:
|
|
114
|
+
from StringIO import StringIO as _BytesIO # reads bytes
|
|
115
|
+
_ub2str = str # PYCHOK convert bytes to str for egm*.pgm text
|
|
116
|
+
|
|
117
|
+
except ImportError: # Python 3+
|
|
118
|
+
from io import BytesIO as _BytesIO # PYCHOK expected
|
|
119
|
+
|
|
120
|
+
__all__ = _ALL_LAZY.geoids
|
|
121
|
+
__version__ = '24.03.22'
|
|
122
|
+
|
|
123
|
+
_assert_ = 'assert'
|
|
124
|
+
_bHASH_ = b'#'
|
|
125
|
+
_endian_ = 'endian'
|
|
126
|
+
_format_ = '%s %r'
|
|
127
|
+
_header_ = 'header'
|
|
128
|
+
# temporarily hold a single instance for each int value
|
|
129
|
+
_intCs = {}
|
|
130
|
+
_interp2d_ks = {-2: _linear_,
|
|
131
|
+
-3: _cubic_,
|
|
132
|
+
-5: 'quintic'}
|
|
133
|
+
_lli_ = 'lli'
|
|
134
|
+
_non_increasing_ = 'non-increasing'
|
|
135
|
+
_rb_ = 'rb'
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class _GeoidBase(_HeightsBase):
|
|
139
|
+
'''(INTERNAL) Base class for C{Geoid...}s.
|
|
140
|
+
'''
|
|
141
|
+
_cropped = None
|
|
142
|
+
# _datum = _WGS84 # from _HeightsBase
|
|
143
|
+
_egm = None # open C{egm*.pgm} geoid file
|
|
144
|
+
_endian = _tbd_
|
|
145
|
+
_geoid = _n_a_
|
|
146
|
+
_hs_y_x = None # numpy 2darray, row-major order
|
|
147
|
+
_interp2d = None # interp2d interpolation
|
|
148
|
+
_kind = 3 # order for interp2d, RectBivariateSpline
|
|
149
|
+
# _kmin = 2 # min number of knots
|
|
150
|
+
_knots = 0 # nlat * nlon
|
|
151
|
+
_mean = None # fixed in GeoidKarney
|
|
152
|
+
# _name = NN # _Named
|
|
153
|
+
_nBytes = 0 # numpy size in bytes, float64
|
|
154
|
+
_pgm = None # PGM attributes, C{_PGM} or C{None}
|
|
155
|
+
_sizeB = 0 # geoid file size in bytes
|
|
156
|
+
_smooth = 0 # used only for RectBivariateSpline
|
|
157
|
+
_stdev = None # fixed in GeoidKarney
|
|
158
|
+
_u2B = 0 # np.itemsize or undefined
|
|
159
|
+
|
|
160
|
+
_lat_d = _0_0 # increment, +tive
|
|
161
|
+
_lat_lo = _0_0 # lower lat, south
|
|
162
|
+
_lat_hi = _0_0 # upper lat, noth
|
|
163
|
+
_lon_d = _0_0 # increment, +tive
|
|
164
|
+
_lon_lo = _0_0 # left lon, west
|
|
165
|
+
_lon_hi = _0_0 # right lon, east
|
|
166
|
+
_lon_of = _0_0 # forward lon offset
|
|
167
|
+
_lon_og = _0_0 # reverse lon offset
|
|
168
|
+
|
|
169
|
+
_center = None # (lat, lon, height)
|
|
170
|
+
_yx_hits = None # cache hits, ala Karney
|
|
171
|
+
|
|
172
|
+
def __init__(self, hs, p):
|
|
173
|
+
'''(INTERNAL) Set up the grid axes, the C{SciPy} interpolator
|
|
174
|
+
and several internal geoid attributes.
|
|
175
|
+
|
|
176
|
+
@arg hs: Grid knots with known height (C{numpy 2darray}).
|
|
177
|
+
@arg p: The C{slat, wlon, nlat, nlon, dlat, dlon} and
|
|
178
|
+
other geoid parameters (C{INTERNAL}).
|
|
179
|
+
|
|
180
|
+
@raise GeoidError: Incompatible grid B{C{hs}} shape or
|
|
181
|
+
invalid B{C{kind}}.
|
|
182
|
+
|
|
183
|
+
@raise LenError: Mismatch grid B{C{hs}} axis.
|
|
184
|
+
|
|
185
|
+
@raise SciPyError: A C{scipy.interpolate.inter2d} or
|
|
186
|
+
C{-.RectBivariateSpline} issue.
|
|
187
|
+
|
|
188
|
+
@raise SciPyWarning: A C{scipy.interpolate.inter2d} or
|
|
189
|
+
C{-.RectBivariateSpline} warning as
|
|
190
|
+
exception.
|
|
191
|
+
|
|
192
|
+
@note: C{scipy.interpolate.interp2d} has been C{DEPRECATED},
|
|
193
|
+
specify keyword argument C{B{kind}=1..5} to use
|
|
194
|
+
C{scipy.interpolate.RectBivariateSpline}.
|
|
195
|
+
'''
|
|
196
|
+
spi = self.scipy_interpolate
|
|
197
|
+
# for 2d scipy.interpolate.interp2d(xs, ys, hs, ...) and
|
|
198
|
+
# scipy.interpolate.RectBivariateSpline(ys, xs, hs, ...)
|
|
199
|
+
# require the shape of hs to be (len(ys), len(xs)), note
|
|
200
|
+
# the different (xs, ys, ...) and (ys, xs, ...) orders
|
|
201
|
+
if (p.nlat, p.nlon) != hs.shape:
|
|
202
|
+
raise GeoidError(shape=hs.shape, txt=_incompatible((p.nlat, p.nlon)))
|
|
203
|
+
|
|
204
|
+
# both axes and bounding box
|
|
205
|
+
ys, self._lat_d = self._gaxis2(p.slat, p.dlat, p.nlat, _lat_ + _s_)
|
|
206
|
+
xs, self._lon_d = self._gaxis2(p.wlon, p.dlon, p.nlon, _lon_ + _s_)
|
|
207
|
+
|
|
208
|
+
bb = ys[0], ys[-1], xs[0], xs[-1] + p.dlon # fudge lon_hi
|
|
209
|
+
# geoid grids are typically stored in row-major order, some
|
|
210
|
+
# with rows (90..-90) reversed and columns (0..360) wrapped
|
|
211
|
+
# to Easten longitude, 0 <= east < 180 and 180 <= west < 360
|
|
212
|
+
k = self.kind
|
|
213
|
+
if k in _interp2d_ks: # .interp2d DEPRECATED since scipy 1.10
|
|
214
|
+
if self._scipy_version() < (1, 10):
|
|
215
|
+
self._interp2d = spi.interp2d(xs, ys, hs, kind=_interp2d_ks[k])
|
|
216
|
+
else: # call and overwrite the DEPRECATED .interp2d
|
|
217
|
+
self._interp2d = self._interp2d(xs, ys, hs, k)
|
|
218
|
+
elif 1 <= k <= 5:
|
|
219
|
+
self._ev = spi.RectBivariateSpline(ys, xs, hs, bbox=bb, ky=k, kx=k,
|
|
220
|
+
s=self._smooth).ev
|
|
221
|
+
else:
|
|
222
|
+
raise GeoidError(kind=k)
|
|
223
|
+
|
|
224
|
+
self._hs_y_x = hs # numpy 2darray, row-major
|
|
225
|
+
self._nBytes = hs.nbytes # numpy size in bytes
|
|
226
|
+
self._knots = p.knots # grid knots
|
|
227
|
+
self._lon_of = float(p.flon) # forward offset
|
|
228
|
+
self._lon_og = float(p.glon) # reverse offset
|
|
229
|
+
# shrink the box by 1 unit on every side
|
|
230
|
+
# bb += self._lat_d, -self._lat_d, self._lon_d, -self._lon_d
|
|
231
|
+
self._lat_lo = float(bb[0])
|
|
232
|
+
self._lat_hi = float(bb[1])
|
|
233
|
+
self._lon_lo = float(bb[2] - p.glon)
|
|
234
|
+
self._lon_hi = float(bb[3] - p.glon)
|
|
235
|
+
|
|
236
|
+
def __call__(self, *llis, **wrap_H):
|
|
237
|
+
'''Interpolate the geoid height for one or several locations.
|
|
238
|
+
|
|
239
|
+
@arg llis: One or more locations (C{LatLon}s), all positional.
|
|
240
|
+
@kwarg wrap_H: Keyword arguments C{B{wrap}=False, B{H}=False}.
|
|
241
|
+
If C{B{wrap} is True}, wrap or I{normalize} all
|
|
242
|
+
B{C{llis}} locations (C{bool}). If C{B{H} is True},
|
|
243
|
+
return the I{orthometric} height instead of the
|
|
244
|
+
I{geoid} height at each location (C{bool}).
|
|
245
|
+
|
|
246
|
+
@return: A single interpolated geoid (or orthometric) height
|
|
247
|
+
(C{float}) or a list or tuple of interpolated geoid
|
|
248
|
+
(or orthometric) heights (C{float}s).
|
|
249
|
+
|
|
250
|
+
@raise GeoidError: Insufficient number of B{C{llis}}, an
|
|
251
|
+
invalid B{C{lli}} or the C{egm*.pgm}
|
|
252
|
+
geoid file is closed.
|
|
253
|
+
|
|
254
|
+
@raise RangeError: An B{C{lli}} is outside this geoid's lat-
|
|
255
|
+
or longitude range.
|
|
256
|
+
|
|
257
|
+
@raise SciPyError: A C{scipy.interpolate.inter2d} or
|
|
258
|
+
C{-.RectBivariateSpline} issue.
|
|
259
|
+
|
|
260
|
+
@raise SciPyWarning: A C{scipy.interpolate.inter2d} or
|
|
261
|
+
C{-.RectBivariateSpline} warning as
|
|
262
|
+
exception.
|
|
263
|
+
|
|
264
|
+
@note: To obtain I{orthometric} heights, each B{C{llis}}
|
|
265
|
+
location must have an ellipsoid C{height} or C{h}
|
|
266
|
+
attribute, otherwise C{height=0} is used.
|
|
267
|
+
|
|
268
|
+
@see: Function L{pygeodesy.heightOrthometric}.
|
|
269
|
+
'''
|
|
270
|
+
return self._called(llis, True, **wrap_H)
|
|
271
|
+
|
|
272
|
+
def __enter__(self):
|
|
273
|
+
'''Open context.
|
|
274
|
+
'''
|
|
275
|
+
return self
|
|
276
|
+
|
|
277
|
+
def __exit__(self, *unused): # PYCHOK exc_type, exc_value, exc_traceback)
|
|
278
|
+
'''Close context.
|
|
279
|
+
'''
|
|
280
|
+
self.close()
|
|
281
|
+
# return None # XXX False
|
|
282
|
+
|
|
283
|
+
def __repr__(self):
|
|
284
|
+
return self.toStr()
|
|
285
|
+
|
|
286
|
+
def __str__(self):
|
|
287
|
+
return Fmt.PAREN(self.classname, repr(self.name))
|
|
288
|
+
|
|
289
|
+
def _called(self, llis, scipy, wrap=False, H=False):
|
|
290
|
+
# handle __call__
|
|
291
|
+
_H = _MODS.formy.heightOrthometric if H else None
|
|
292
|
+
_as, llis = _as_llis2(llis, Error=GeoidError)
|
|
293
|
+
hs, _w = [], _Wrap._latlonop(wrap)
|
|
294
|
+
_a, _h = hs.append, self._hGeoid
|
|
295
|
+
try:
|
|
296
|
+
for i, lli in enumerate(llis):
|
|
297
|
+
N = _h(*_w(lli.lat, lli.lon))
|
|
298
|
+
# orthometric or geoid height
|
|
299
|
+
_a(_H(lli, N) if _H else N)
|
|
300
|
+
return _as(hs)
|
|
301
|
+
|
|
302
|
+
except (GeoidError, RangeError) as x:
|
|
303
|
+
# XXX avoid str(LatLon()) degree symbols
|
|
304
|
+
t = _lli_ if _as is _ascalar else Fmt.INDEX(llis=i)
|
|
305
|
+
lli = fstr((lli.lat, lli.lon), strepr=repr)
|
|
306
|
+
raise type(x)(t, lli, wrap=wrap, H=H, cause=x)
|
|
307
|
+
except Exception as x:
|
|
308
|
+
if scipy and self.scipy:
|
|
309
|
+
raise _SciPyIssue(x)
|
|
310
|
+
else:
|
|
311
|
+
raise
|
|
312
|
+
|
|
313
|
+
@Property_RO
|
|
314
|
+
def _center(self):
|
|
315
|
+
''' Cache for method L{center}.
|
|
316
|
+
'''
|
|
317
|
+
return self._llh3(favg(self._lat_lo, self._lat_hi),
|
|
318
|
+
favg(self._lon_lo, self._lon_hi))
|
|
319
|
+
|
|
320
|
+
def center(self, LatLon=None):
|
|
321
|
+
'''Return the center location and height of this geoid.
|
|
322
|
+
|
|
323
|
+
@kwarg LatLon: Optional class to return the location and height
|
|
324
|
+
(C{LatLon}) or C{None}.
|
|
325
|
+
|
|
326
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
327
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
328
|
+
with the lat-, longitude and geoid height of the
|
|
329
|
+
center grid location.
|
|
330
|
+
'''
|
|
331
|
+
return self._llh3LL(self._center, LatLon)
|
|
332
|
+
|
|
333
|
+
def close(self):
|
|
334
|
+
'''Close the C{egm*.pgm} geoid file if open (and applicable).
|
|
335
|
+
'''
|
|
336
|
+
if not self.closed:
|
|
337
|
+
self._egm.close()
|
|
338
|
+
self._egm = None
|
|
339
|
+
|
|
340
|
+
@property_RO
|
|
341
|
+
def closed(self):
|
|
342
|
+
'''Get the C{egm*.pgm} geoid file status.
|
|
343
|
+
'''
|
|
344
|
+
return self._egm is None
|
|
345
|
+
|
|
346
|
+
@Property_RO
|
|
347
|
+
def cropped(self):
|
|
348
|
+
'''Is geoid cropped (C{bool} or C{None} if crop not supported).
|
|
349
|
+
'''
|
|
350
|
+
return self._cropped
|
|
351
|
+
|
|
352
|
+
@Property_RO
|
|
353
|
+
def dtype(self):
|
|
354
|
+
'''Get the grid C{scipy} U{dtype<https://docs.SciPy.org/doc/numpy/
|
|
355
|
+
reference/generated/numpy.ndarray.dtype.html>} (C{numpy.dtype}).
|
|
356
|
+
'''
|
|
357
|
+
return self._hs_y_x.dtype
|
|
358
|
+
|
|
359
|
+
@Property_RO
|
|
360
|
+
def endian(self):
|
|
361
|
+
'''Get the geoid endianess and U{dtype<https://docs.SciPy.org/
|
|
362
|
+
doc/numpy/reference/generated/numpy.dtype.html>} (C{str}).
|
|
363
|
+
'''
|
|
364
|
+
return self._endian
|
|
365
|
+
|
|
366
|
+
def _ev(self, y, x): # PYCHOK expected
|
|
367
|
+
# only used for .interpolate.interp2d, but
|
|
368
|
+
# overwritten for .RectBivariateSpline,
|
|
369
|
+
# note (y, x) must be flipped!
|
|
370
|
+
return self._interp2d(x, y)
|
|
371
|
+
|
|
372
|
+
def _gaxis2(self, lo, d, n, name):
|
|
373
|
+
# build grid axis, hi = lo + (n - 1) * d
|
|
374
|
+
m, a = len2(frange(lo, n, d))
|
|
375
|
+
if m != n:
|
|
376
|
+
raise LenError(self.__class__, grid=m, **{name: n})
|
|
377
|
+
if d < 0:
|
|
378
|
+
d, a = -d, list(reversed(a))
|
|
379
|
+
for i in range(1, m):
|
|
380
|
+
e = a[i] - a[i-1]
|
|
381
|
+
if e < EPS: # non-increasing axis
|
|
382
|
+
i = Fmt.INDEX(name, i)
|
|
383
|
+
raise GeoidError(i, e, txt=_non_increasing_)
|
|
384
|
+
return self.numpy.array(a), d
|
|
385
|
+
|
|
386
|
+
def _g2ll2(self, lat, lon): # PYCHOK no cover
|
|
387
|
+
'''(INTERNAL) I{Must be overloaded}.'''
|
|
388
|
+
notOverloaded(self, lat, lon)
|
|
389
|
+
|
|
390
|
+
def _gyx2g2(self, y, x):
|
|
391
|
+
# convert grid (y, x) indices to grid (lat, lon)
|
|
392
|
+
return ((self._lat_lo + self._lat_d * y),
|
|
393
|
+
(self._lon_lo + self._lon_of + self._lon_d * x))
|
|
394
|
+
|
|
395
|
+
def height(self, lats, lons, **wrap):
|
|
396
|
+
'''Interpolate the geoid height for one or several lat-/longitudes.
|
|
397
|
+
|
|
398
|
+
@arg lats: Latitude or latitudes (C{degrees} or C{degrees}s).
|
|
399
|
+
@arg lons: Longitude or longitudes (C{degrees} or C{degrees}s).
|
|
400
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} all B{C{lats}}
|
|
401
|
+
and B{C{lons}} locations (C{bool}).
|
|
402
|
+
|
|
403
|
+
@return: A single interpolated geoid height (C{float}) or a
|
|
404
|
+
list of interpolated geoid heights (C{float}s).
|
|
405
|
+
|
|
406
|
+
@raise GeoidError: Insufficient or non-matching number of
|
|
407
|
+
B{C{lats}} and B{C{lons}}.
|
|
408
|
+
|
|
409
|
+
@raise RangeError: A B{C{lat}} or B{C{lon}} is outside this
|
|
410
|
+
geoid's lat- or longitude range.
|
|
411
|
+
|
|
412
|
+
@raise SciPyError: A C{scipy.interpolate.inter2d} or
|
|
413
|
+
C{-.RectBivariateSpline} issue.
|
|
414
|
+
|
|
415
|
+
@raise SciPyWarning: A C{scipy.interpolate.inter2d} or
|
|
416
|
+
C{-.RectBivariateSpline} warning as
|
|
417
|
+
exception.
|
|
418
|
+
'''
|
|
419
|
+
return _height_called(self, lats, lons, Error=GeoidError, **wrap)
|
|
420
|
+
|
|
421
|
+
def _hGeoid(self, lat, lon):
|
|
422
|
+
out = self.outside(lat, lon)
|
|
423
|
+
if out:
|
|
424
|
+
lli = fstr((lat, lon), strepr=repr)
|
|
425
|
+
raise RangeError(lli=lli, txt=_SPACE_(_outside_, _on_, out))
|
|
426
|
+
return float(self._ev(*self._ll2g2(lat, lon)))
|
|
427
|
+
|
|
428
|
+
@Property_RO
|
|
429
|
+
def _highest(self):
|
|
430
|
+
'''(INTERNAL) Cache for L{highest} method.
|
|
431
|
+
'''
|
|
432
|
+
return self._llh3minmax(True)
|
|
433
|
+
|
|
434
|
+
def highest(self, LatLon=None, **unused):
|
|
435
|
+
'''Return the location and largest height of this geoid.
|
|
436
|
+
|
|
437
|
+
@kwarg LatLon: Optional class to return the location and height
|
|
438
|
+
(C{LatLon}) or C{None}.
|
|
439
|
+
|
|
440
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
441
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
442
|
+
with the lat-, longitude and geoid height of the
|
|
443
|
+
highest grid location.
|
|
444
|
+
'''
|
|
445
|
+
return self._llh3LL(self._highest, LatLon)
|
|
446
|
+
|
|
447
|
+
@Property_RO
|
|
448
|
+
def hits(self):
|
|
449
|
+
'''Get the number of cache hits (C{int} or C{None}).
|
|
450
|
+
'''
|
|
451
|
+
return self._yx_hits
|
|
452
|
+
|
|
453
|
+
@deprecated_method
|
|
454
|
+
def _interp2d(self, xs, ys, hs=(), k=0): # overwritten in .__init__ above
|
|
455
|
+
'''DEPRECATED on 23.01.06, use keyword argument C{B{kind}=1..5}.'''
|
|
456
|
+
# assert k in _interp2d_ks # and len(hs) == len(xs) == len(ys)
|
|
457
|
+
try:
|
|
458
|
+
return self.scipy_interpolate.interp2d(xs, ys, hs, kind=_interp2d_ks[k])
|
|
459
|
+
except AttributeError as x:
|
|
460
|
+
raise SciPyError(interp2d=MISSING, kind=k, cause=x)
|
|
461
|
+
|
|
462
|
+
@Property_RO
|
|
463
|
+
def kind(self):
|
|
464
|
+
'''Get the interpolator kind and order (C{int}).
|
|
465
|
+
'''
|
|
466
|
+
return self._kind
|
|
467
|
+
|
|
468
|
+
@Property_RO
|
|
469
|
+
def knots(self):
|
|
470
|
+
'''Get the number of grid knots (C{int}).
|
|
471
|
+
'''
|
|
472
|
+
return self._knots
|
|
473
|
+
|
|
474
|
+
def _ll2g2(self, lat, lon): # PYCHOK no cover
|
|
475
|
+
'''(INTERNAL) I{Must be overloaded}.'''
|
|
476
|
+
notOverloaded(self, lat, lon)
|
|
477
|
+
|
|
478
|
+
@property_RO
|
|
479
|
+
def _LL3T(self):
|
|
480
|
+
'''(INTERNAL) Get L{LatLon3Tuple}, I{once}.
|
|
481
|
+
'''
|
|
482
|
+
_GeoidBase._LL3T = T = _MODS.namedTuples.LatLon3Tuple # overwrite poperty_RO
|
|
483
|
+
return T
|
|
484
|
+
|
|
485
|
+
def _llh3(self, lat, lon):
|
|
486
|
+
return self._LL3T(lat, lon, self._hGeoid(lat, lon), name=self.name)
|
|
487
|
+
|
|
488
|
+
def _llh3LL(self, llh, LatLon):
|
|
489
|
+
return llh if LatLon is None else self._xnamed(LatLon(*llh))
|
|
490
|
+
|
|
491
|
+
def _llh3minmax(self, highest=True, *unused):
|
|
492
|
+
hs, np = self._hs_y_x, self.numpy
|
|
493
|
+
# <https://docs.SciPy.org/doc/numpy/reference/generated/
|
|
494
|
+
# numpy.argmin.html#numpy.argmin>
|
|
495
|
+
arg = np.argmax if highest else np.argmin
|
|
496
|
+
y, x = np.unravel_index(arg(hs, axis=None), hs.shape)
|
|
497
|
+
return self._g2ll2(*self._gyx2g2(y, x)) + (float(hs[y, x]),)
|
|
498
|
+
|
|
499
|
+
def _load(self, g, dtype, n, offset=0):
|
|
500
|
+
# numpy.fromfile, like .frombuffer
|
|
501
|
+
g.seek(offset, _SEEK_SET)
|
|
502
|
+
return self.numpy.fromfile(g, dtype, n)
|
|
503
|
+
|
|
504
|
+
@Property_RO
|
|
505
|
+
def _lowerleft(self):
|
|
506
|
+
'''(INTERNAL) Cache for L{lowerleft}.
|
|
507
|
+
'''
|
|
508
|
+
return self._llh3(self._lat_lo, self._lon_lo)
|
|
509
|
+
|
|
510
|
+
def lowerleft(self, LatLon=None):
|
|
511
|
+
'''Return the lower-left location and height of this geoid.
|
|
512
|
+
|
|
513
|
+
@kwarg LatLon: Optional class to return the location
|
|
514
|
+
(C{LatLon}) and height or C{None}.
|
|
515
|
+
|
|
516
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
517
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
518
|
+
with the lat-, longitude and geoid height of the
|
|
519
|
+
lower-left, SW grid corner.
|
|
520
|
+
'''
|
|
521
|
+
return self._llh3LL(self._lowerleft, LatLon)
|
|
522
|
+
|
|
523
|
+
@Property_RO
|
|
524
|
+
def _loweright(self):
|
|
525
|
+
'''(INTERNAL) Cache for L{loweright}.
|
|
526
|
+
'''
|
|
527
|
+
return self._llh3(self._lat_lo, self._lon_hi)
|
|
528
|
+
|
|
529
|
+
def loweright(self, LatLon=None):
|
|
530
|
+
'''Return the lower-right location and height of this geoid.
|
|
531
|
+
|
|
532
|
+
@kwarg LatLon: Optional class to return the location and height
|
|
533
|
+
(C{LatLon}) or C{None}.
|
|
534
|
+
|
|
535
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
536
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
537
|
+
with the lat-, longitude and geoid height of the
|
|
538
|
+
lower-right, SE grid corner.
|
|
539
|
+
'''
|
|
540
|
+
|
|
541
|
+
return self._llh3LL(self._loweright, LatLon)
|
|
542
|
+
|
|
543
|
+
lowerright = loweright # synonymous
|
|
544
|
+
|
|
545
|
+
@Property_RO
|
|
546
|
+
def _lowest(self):
|
|
547
|
+
'''(INTERNAL) Cache for L{lowest}.
|
|
548
|
+
'''
|
|
549
|
+
return self._llh3minmax(False)
|
|
550
|
+
|
|
551
|
+
def lowest(self, LatLon=None, **unused):
|
|
552
|
+
'''Return the location and lowest height of this geoid.
|
|
553
|
+
|
|
554
|
+
@kwarg LatLon: Optional class to return the location and height
|
|
555
|
+
(C{LatLon}) or C{None}.
|
|
556
|
+
|
|
557
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
558
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
559
|
+
with the lat-, longitude and geoid height of the
|
|
560
|
+
lowest grid location.
|
|
561
|
+
'''
|
|
562
|
+
return self._llh3LL(self._lowest, LatLon)
|
|
563
|
+
|
|
564
|
+
@Property_RO
|
|
565
|
+
def mean(self):
|
|
566
|
+
'''Get the mean of this geoid's heights (C{float}).
|
|
567
|
+
'''
|
|
568
|
+
if self._mean is None: # see GeoidKarney
|
|
569
|
+
self._mean = float(self.numpy.mean(self._hs_y_x))
|
|
570
|
+
return self._mean
|
|
571
|
+
|
|
572
|
+
@property_RO
|
|
573
|
+
def name(self):
|
|
574
|
+
'''Get the name of this geoid (C{str}).
|
|
575
|
+
'''
|
|
576
|
+
return _HeightsBase.name.fget(self) or self._geoid # recursion
|
|
577
|
+
|
|
578
|
+
@Property_RO
|
|
579
|
+
def nBytes(self):
|
|
580
|
+
'''Get the grid in-memory size in bytes (C{int}).
|
|
581
|
+
'''
|
|
582
|
+
return self._nBytes
|
|
583
|
+
|
|
584
|
+
def _open(self, geoid, datum, kind, name, smooth):
|
|
585
|
+
# open the geoid file
|
|
586
|
+
try:
|
|
587
|
+
self._geoid = _os_path.basename(geoid)
|
|
588
|
+
self._sizeB = _os_path.getsize(geoid)
|
|
589
|
+
g = open(geoid, _rb_)
|
|
590
|
+
except (IOError, OSError) as x:
|
|
591
|
+
raise GeoidError(geoid=geoid, cause=x)
|
|
592
|
+
|
|
593
|
+
if datum not in (None, self._datum):
|
|
594
|
+
self._datum = _ellipsoidal_datum(datum, name=name)
|
|
595
|
+
self._kind = int(kind)
|
|
596
|
+
if name:
|
|
597
|
+
_HeightsBase.name.fset(self, name) # rename
|
|
598
|
+
if smooth:
|
|
599
|
+
self._smooth = Int_(smooth=smooth, Error=GeoidError, low=0)
|
|
600
|
+
|
|
601
|
+
return g
|
|
602
|
+
|
|
603
|
+
def outside(self, lat, lon):
|
|
604
|
+
'''Check whether a location is outside this geoid's
|
|
605
|
+
lat-/longitude or crop range.
|
|
606
|
+
|
|
607
|
+
@arg lat: The latitude (C{degrees}).
|
|
608
|
+
@arg lon: The longitude (C{degrees}).
|
|
609
|
+
|
|
610
|
+
@return: A 1- or 2-character C{str} if outside or an
|
|
611
|
+
empty C{str} if inside.
|
|
612
|
+
'''
|
|
613
|
+
return (_S_ if lat < self._lat_lo else
|
|
614
|
+
(_N_ if lat > self._lat_hi else NN)) + \
|
|
615
|
+
(_W_ if lon < self._lon_lo else
|
|
616
|
+
(_E_ if lon > self._lon_hi else NN))
|
|
617
|
+
|
|
618
|
+
@Property_RO
|
|
619
|
+
def pgm(self):
|
|
620
|
+
'''Get the PGM attributes (C{_PGM} or C{None} if not available/applicable).
|
|
621
|
+
'''
|
|
622
|
+
return self._pgm
|
|
623
|
+
|
|
624
|
+
@Property_RO
|
|
625
|
+
def sizeB(self):
|
|
626
|
+
'''Get the geoid grid file size in bytes (C{int}).
|
|
627
|
+
'''
|
|
628
|
+
return self._sizeB
|
|
629
|
+
|
|
630
|
+
@Property_RO
|
|
631
|
+
def smooth(self):
|
|
632
|
+
'''Get the C{RectBivariateSpline} smoothing (C{int}).
|
|
633
|
+
'''
|
|
634
|
+
return self._smooth
|
|
635
|
+
|
|
636
|
+
@Property_RO
|
|
637
|
+
def stdev(self):
|
|
638
|
+
'''Get the standard deviation of this geoid's heights (C{float}) or C{None}.
|
|
639
|
+
'''
|
|
640
|
+
if self._stdev is None: # see GeoidKarney
|
|
641
|
+
self._stdev = float(self.numpy.std(self._hs_y_x))
|
|
642
|
+
return self._stdev
|
|
643
|
+
|
|
644
|
+
def _swne(self, crop):
|
|
645
|
+
# crop box to 4-tuple (s, w, n, e)
|
|
646
|
+
try:
|
|
647
|
+
if len(crop) == 2:
|
|
648
|
+
try: # sw, ne LatLons
|
|
649
|
+
swne = (crop[0].lat, crop[0].lon,
|
|
650
|
+
crop[1].lat, crop[1].lon)
|
|
651
|
+
except AttributeError: # (s, w), (n, e)
|
|
652
|
+
swne = tuple(crop[0]) + tuple(crop[1])
|
|
653
|
+
else: # (s, w, n, e)
|
|
654
|
+
swne = crop
|
|
655
|
+
if len(swne) == 4:
|
|
656
|
+
s, w, n, e = map(float, swne)
|
|
657
|
+
if -90 <= s <= (n - _1_0) <= 89 and \
|
|
658
|
+
-180 <= w <= (e - _1_0) <= 179:
|
|
659
|
+
return s, w, n, e
|
|
660
|
+
except (IndexError, TypeError, ValueError):
|
|
661
|
+
pass
|
|
662
|
+
raise GeoidError(crop=crop)
|
|
663
|
+
|
|
664
|
+
def toStr(self, prec=3, sep=_COMMASPACE_): # PYCHOK signature
|
|
665
|
+
'''This geoid and all geoid attributes as a string.
|
|
666
|
+
|
|
667
|
+
@kwarg prec: Number of decimal digits (0..9 or C{None} for
|
|
668
|
+
default). Trailing zero decimals are stripped
|
|
669
|
+
for B{C{prec}} values of 1 and above, but kept
|
|
670
|
+
for negative B{C{prec}} values.
|
|
671
|
+
@kwarg sep: Separator to join (C{str}).
|
|
672
|
+
|
|
673
|
+
@return: Geoid name and attributes (C{str}).
|
|
674
|
+
'''
|
|
675
|
+
s = 1 if self.kind < 0 else 2
|
|
676
|
+
t = tuple(Fmt.PAREN(m.__name__, fstr(m(), prec=prec)) for m in
|
|
677
|
+
(self.lowerleft, self.upperright,
|
|
678
|
+
self.center,
|
|
679
|
+
self.highest, self.lowest)) + \
|
|
680
|
+
attrs( _mean_, _stdev_, prec=prec, Nones=False) + \
|
|
681
|
+
attrs((_kind_, 'smooth')[:s], prec=prec, Nones=False) + \
|
|
682
|
+
attrs( 'cropped', 'dtype', _endian_, 'hits', _knots_, 'nBytes',
|
|
683
|
+
'sizeB', _scipy_, _numpy_, prec=prec, Nones=False)
|
|
684
|
+
return _COLONSPACE_(self, sep.join(t))
|
|
685
|
+
|
|
686
|
+
@Property_RO
|
|
687
|
+
def u2B(self):
|
|
688
|
+
'''Get the PGM itemsize in bytes (C{int}).
|
|
689
|
+
'''
|
|
690
|
+
return self._u2B
|
|
691
|
+
|
|
692
|
+
@Property_RO
|
|
693
|
+
def _upperleft(self):
|
|
694
|
+
'''(INTERNAL) Cache for method L{upperleft}.
|
|
695
|
+
'''
|
|
696
|
+
return self._llh3(self._lat_hi, self._lon_lo)
|
|
697
|
+
|
|
698
|
+
def upperleft(self, LatLon=None):
|
|
699
|
+
'''Return the upper-left location and height of this geoid.
|
|
700
|
+
|
|
701
|
+
@kwarg LatLon: Optional class to return the location and height
|
|
702
|
+
(C{LatLon}) or C{None}.
|
|
703
|
+
|
|
704
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
705
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
706
|
+
with the lat-, longitude and geoid height of the
|
|
707
|
+
upper-left, NW grid corner.
|
|
708
|
+
'''
|
|
709
|
+
return self._llh3LL(self._upperleft, LatLon)
|
|
710
|
+
|
|
711
|
+
@Property_RO
|
|
712
|
+
def _upperright(self):
|
|
713
|
+
'''(INTERNAL) Cache for method L{upperright}.
|
|
714
|
+
'''
|
|
715
|
+
return self._llh3(self._lat_hi, self._lon_hi)
|
|
716
|
+
|
|
717
|
+
def upperright(self, LatLon=None):
|
|
718
|
+
'''Return the upper-right location and height of this geoid.
|
|
719
|
+
|
|
720
|
+
@kwarg LatLon: Optional class to return the location and height
|
|
721
|
+
(C{LatLon}) or C{None}.
|
|
722
|
+
|
|
723
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
724
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
725
|
+
with the lat-, longitude and geoid height of the
|
|
726
|
+
upper-right, NE grid corner.
|
|
727
|
+
'''
|
|
728
|
+
return self._llh3LL(self._upperright, LatLon)
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
class GeoidError(HeightError):
|
|
732
|
+
'''Geoid interpolator C{Geoid...} or interpolation issue.
|
|
733
|
+
'''
|
|
734
|
+
pass
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
class GeoidG2012B(_GeoidBase):
|
|
738
|
+
'''Geoid height interpolator for U{GEOID12B Model
|
|
739
|
+
<https://www.NGS.NOAA.gov/GEOID/GEOID12B/>} grids U{CONUS
|
|
740
|
+
<https://www.NGS.NOAA.gov/GEOID/GEOID12B/GEOID12B_CONUS.shtml>},
|
|
741
|
+
U{Alaska<https://www.NGS.NOAA.gov/GEOID/GEOID12B/GEOID12B_AK.shtml>},
|
|
742
|
+
U{Hawaii<https://www.NGS.NOAA.gov/GEOID/GEOID12B/GEOID12B_HI.shtml>},
|
|
743
|
+
U{Guam and Northern Mariana Islands
|
|
744
|
+
<https://www.NGS.NOAA.gov/GEOID/GEOID12B/GEOID12B_GMNI.shtml>},
|
|
745
|
+
U{Puerto Rico and U.S. Virgin Islands
|
|
746
|
+
<https://www.NGS.NOAA.gov/GEOID/GEOID12B/GEOID12B_PRVI.shtml>} and
|
|
747
|
+
U{American Samoa<https://www.NGS.NOAA.gov/GEOID/GEOID12B/GEOID12B_AS.shtml>}
|
|
748
|
+
based on C{SciPy} U{RectBivariateSpline<https://docs.SciPy.org/doc/
|
|
749
|
+
scipy/reference/generated/scipy.interpolate.RectBivariateSpline.html>}
|
|
750
|
+
or U{interp2d<https://docs.SciPy.org/doc/scipy/reference/generated/
|
|
751
|
+
scipy.interpolate.interp2d.html>} interpolation.
|
|
752
|
+
|
|
753
|
+
Use any of the binary C{le} (little endian) or C{be} (big endian)
|
|
754
|
+
C{g2012b*.bin} grid files.
|
|
755
|
+
'''
|
|
756
|
+
def __init__(self, g2012b_bin, crop=None, datum=None, # NAD 83 Ellipsoid
|
|
757
|
+
kind=3, name=NN, smooth=0):
|
|
758
|
+
'''New L{GeoidG2012B} interpolator.
|
|
759
|
+
|
|
760
|
+
@arg g2012b_bin: A C{GEOID12B} grid file name (C{.bin}).
|
|
761
|
+
@kwarg crop: Optional crop box, not supported (C{None}).
|
|
762
|
+
@kwarg datum: Optional grid datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
|
|
763
|
+
or L{a_f2Tuple}), default C{WGS84}.
|
|
764
|
+
@kwarg kind: C{scipy.interpolate} order (C{int}), use 1..5 for
|
|
765
|
+
U{RectBivariateSpline<https://docs.SciPy.org/doc/scipy/
|
|
766
|
+
reference/generated/scipy.interpolate.RectBivariateSpline.html>},
|
|
767
|
+
-2 for U{interp2d linear<https://docs.SciPy.org/doc/scipy/
|
|
768
|
+
reference/generated/scipy.interpolate.interp2d.html>}, -3
|
|
769
|
+
for C{interp2d cubic} or -5 for C{interp2d quintic}.
|
|
770
|
+
@kwarg name: Optional geoid name (C{str}).
|
|
771
|
+
@kwarg smooth: Smoothing factor for U{RectBivariateSpline
|
|
772
|
+
<https://docs.SciPy.org/doc/scipy/reference/generated/
|
|
773
|
+
scipy.interpolate.RectBivariateSpline.html>}
|
|
774
|
+
only (C{int}).
|
|
775
|
+
|
|
776
|
+
@raise GeoidError: G2012B grid file B{C{g2012b_bin}} issue or invalid
|
|
777
|
+
B{C{crop}}, B{C{kind}} or B{C{smooth}}.
|
|
778
|
+
|
|
779
|
+
@raise ImportError: Package C{numpy} or C{scipy} not found or
|
|
780
|
+
not installed.
|
|
781
|
+
|
|
782
|
+
@raise LenError: Grid file B{C{g2012b_bin}} axis mismatch.
|
|
783
|
+
|
|
784
|
+
@raise SciPyError: A C{RectBivariateSpline} or C{inter2d} issue.
|
|
785
|
+
|
|
786
|
+
@raise SciPyWarning: A C{RectBivariateSpline} or C{inter2d}
|
|
787
|
+
warning as exception.
|
|
788
|
+
|
|
789
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
790
|
+
|
|
791
|
+
@note: C{scipy.interpolate.interp2d} has been C{DEPRECATED}, specify
|
|
792
|
+
C{B{kind}=1..5} for C{scipy.interpolate.RectBivariateSpline}.
|
|
793
|
+
'''
|
|
794
|
+
if crop is not None:
|
|
795
|
+
raise GeoidError(crop=crop, txt=_not_(_supported_))
|
|
796
|
+
|
|
797
|
+
g = self._open(g2012b_bin, datum, kind, name, smooth)
|
|
798
|
+
_ = self.numpy # import numpy for ._load and
|
|
799
|
+
|
|
800
|
+
try:
|
|
801
|
+
p = _Gpars()
|
|
802
|
+
n = (self.sizeB // 4) - 11 # number of f4 heights
|
|
803
|
+
# U{numpy dtype formats are different from Python struct formats
|
|
804
|
+
# <https://docs.SciPy.org/doc/numpy-1.15.0/reference/arrays.dtypes.html>}
|
|
805
|
+
for en_ in ('<', '>'):
|
|
806
|
+
# skip 4xf8, get 3xi4
|
|
807
|
+
p.nlat, p.nlon, ien = map(int, self._load(g, en_+'i4', 3, 32))
|
|
808
|
+
if ien == 1: # correct endian
|
|
809
|
+
p.knots = p.nlat * p.nlon
|
|
810
|
+
if p.knots == n and 1 < p.nlat < n \
|
|
811
|
+
and 1 < p.nlon < n:
|
|
812
|
+
self._endian = en_+'f4'
|
|
813
|
+
break
|
|
814
|
+
else: # couldn't validate endian
|
|
815
|
+
raise GeoidError(_endian_)
|
|
816
|
+
|
|
817
|
+
# get the first 4xf8
|
|
818
|
+
p.slat, p.wlon, p.dlat, p.dlon = map(float, self._load(g, en_+'f8', 4))
|
|
819
|
+
# read all f4 heights, ignoring the first 4xf8 and 3xi4
|
|
820
|
+
hs = self._load(g, self._endian, n, 44).reshape(p.nlat, p.nlon)
|
|
821
|
+
p.wlon -= _360_0 # western-most East longitude to earth (..., lon)
|
|
822
|
+
_GeoidBase.__init__(self, hs, p)
|
|
823
|
+
|
|
824
|
+
except Exception as x:
|
|
825
|
+
raise _SciPyIssue(x, _in_, repr(g2012b_bin))
|
|
826
|
+
finally:
|
|
827
|
+
g.close()
|
|
828
|
+
|
|
829
|
+
def _g2ll2(self, lat, lon):
|
|
830
|
+
# convert grid (lat, lon) to earth (lat, lon)
|
|
831
|
+
return lat, lon
|
|
832
|
+
|
|
833
|
+
def _ll2g2(self, lat, lon):
|
|
834
|
+
# convert earth (lat, lon) to grid (lat, lon)
|
|
835
|
+
return lat, lon
|
|
836
|
+
|
|
837
|
+
if _FOR_DOCS:
|
|
838
|
+
__call__ = _GeoidBase.__call__
|
|
839
|
+
height = _GeoidBase.height
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
class GeoidHeight5Tuple(_NamedTuple): # .geoids.py
|
|
843
|
+
'''5-Tuple C{(lat, lon, egm84, egm96, egm2008)} for U{GeoidHeights.dat
|
|
844
|
+
<https://SourceForge.net/projects/geographiclib/files/testdata/>}
|
|
845
|
+
tests with the heights for 3 different EGM grids at C{degrees90}
|
|
846
|
+
and C{degrees180} degrees (after converting C{lon} from original
|
|
847
|
+
C{0 <= EasterLon <= 360}).
|
|
848
|
+
'''
|
|
849
|
+
_Names_ = (_lat_, _lon_, 'egm84', 'egm96', 'egm2008')
|
|
850
|
+
_Units_ = ( Lat, Lon, Height, Height, Height)
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def _I(i):
|
|
854
|
+
'''(INTERNAL) Cache a single C{int} constant.
|
|
855
|
+
'''
|
|
856
|
+
return _intCs.setdefault(i, i) # PYCHOK undefined due to del _intCs
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def _T(*cs):
|
|
860
|
+
'''(INTERNAL) Cache a tuple of single C{int} constants.
|
|
861
|
+
'''
|
|
862
|
+
return map1(_I, *cs)
|
|
863
|
+
|
|
864
|
+
_T0s12 = (_I(0),) * 12 # PYCHOK _T(0, 0, ..., 0)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
class GeoidKarney(_GeoidBase):
|
|
868
|
+
'''Geoid height interpolator for I{Karney}'s U{GeographicLib Earth
|
|
869
|
+
Gravitational Model (EGM)<https://GeographicLib.SourceForge.io/C++/doc/
|
|
870
|
+
geoid.html>} geoid U{egm*.pgm<https://GeographicLib.SourceForge.io/
|
|
871
|
+
C++/doc/geoid.html#geoidinst>} datasets using bilinear or U{cubic
|
|
872
|
+
<https://dl.ACM.org/citation.cfm?id=368443>} interpolation and U{caching
|
|
873
|
+
<https://GeographicLib.SourceForge.io/C++/doc/geoid.html#geoidcache>}
|
|
874
|
+
in pure Python, transcoded from I{Karney}'s U{C++ class Geoid
|
|
875
|
+
<https://GeographicLib.SourceForge.io/C++/doc/geoid.html#geoidinterp>}.
|
|
876
|
+
|
|
877
|
+
Use any of the geoid U{egm84-, egm96- or egm2008-*.pgm
|
|
878
|
+
<https://GeographicLib.SourceForge.io/C++/doc/geoid.html#geoidinst>}
|
|
879
|
+
datasets.
|
|
880
|
+
'''
|
|
881
|
+
_C0 = _F(372), _F(240), _F(372) # n, _ and s common denominators
|
|
882
|
+
# matrices c3n_, c3, c3s_, transposed from GeographicLib/Geoid.cpp
|
|
883
|
+
_C3 = ((_T(0, 0, 62, 124, 124, 62, 0, 0, 0, 0, 0, 0),
|
|
884
|
+
_T0s12,
|
|
885
|
+
_T(-131, 7, -31, -62, -62, -31, 45, 216, 156, -45, -55, -7),
|
|
886
|
+
_T0s12,
|
|
887
|
+
_T(138, -138, 0, 0, 0, 0, -183, 33, 153, -3, 48, -48), # PYCHOK indent
|
|
888
|
+
_T(144, 42, -62, -124, -124, -62, -9, 87, 99, 9, 42, -42),
|
|
889
|
+
_T0s12,
|
|
890
|
+
_T(0, 0, 0, 0, 0, 0, 93, -93, -93, 93, 0, 0),
|
|
891
|
+
_T(-102, 102, 0, 0, 0, 0, 18, 12, -12, -18, -84, 84),
|
|
892
|
+
_T(-31, -31, 31, 62, 62, 31, 0, -93, -93, 0, 31, 31)), # PYCHOK indent
|
|
893
|
+
|
|
894
|
+
(_T(9, -9, 9, 186, 54, -9, -9, 54, -54, 9, -9, 9),
|
|
895
|
+
_T(-18, 18, -88, -42, 162, -32, 8, -78, 78, -8, 18, -18),
|
|
896
|
+
_T(-88, 8, -18, -42, -78, 18, 18, 162, 78, -18, -32, -8),
|
|
897
|
+
_T(0, 0, 90, -150, 30, 30, 30, -90, 90, -30, 0, 0),
|
|
898
|
+
_T(96, -96, 96, -96, -24, 24, -96, -24, 144, -24, 24, -24), # PYCHOK indent
|
|
899
|
+
_T(90, 30, 0, -150, -90, 0, 0, 30, 90, 0, 30, -30),
|
|
900
|
+
_T(0, 0, -20, 60, -60, 20, -20, 60, -60, 20, 0, 0),
|
|
901
|
+
_T(0, 0, -60, 60, 60, -60, 60, -60, -60, 60, 0, 0),
|
|
902
|
+
_T(-60, 60, 0, 60, -60, 0, 0, 60, -60, 0, -60, 60),
|
|
903
|
+
_T(-20, -20, 0, 60, 60, 0, 0, -60, -60, 0, 20, 20)),
|
|
904
|
+
|
|
905
|
+
(_T(18, -18, 36, 210, 162, -36, 0, 0, 0, 0, -18, 18), # PYCHOK indent
|
|
906
|
+
_T(-36, 36, -165, 45, 141, -21, 0, 0, 0, 0, 36, -36),
|
|
907
|
+
_T(-122, -2, -27, -111, -75, 27, 62, 124, 124, 62, -64, 2),
|
|
908
|
+
_T(0, 0, 93, -93, -93, 93, 0, 0, 0, 0, 0, 0),
|
|
909
|
+
_T(120, -120, 147, -57, -129, 39, 0, 0, 0, 0, 66, -66), # PYCHOK indent
|
|
910
|
+
_T(135, 51, -9, -192, -180, 9, 31, 62, 62, 31, 51, -51),
|
|
911
|
+
_T0s12,
|
|
912
|
+
_T(0, 0, -93, 93, 93, -93, 0, 0, 0, 0, 0, 0),
|
|
913
|
+
_T(-84, 84, 18, 12, -12, -18, 0, 0, 0, 0, -102, 102),
|
|
914
|
+
_T(-31, -31, 0, 93, 93, 0, -31, -62, -62, -31, 31, 31)))
|
|
915
|
+
|
|
916
|
+
_BT = (_T(0, 0), # bilinear 4-tuple [i, j] indices
|
|
917
|
+
_T(1, 0),
|
|
918
|
+
_T(0, 1),
|
|
919
|
+
_T(1, 1))
|
|
920
|
+
|
|
921
|
+
_CM = (_T( 0, -1), # 10x12 cubic matrix [i, j] indices
|
|
922
|
+
_T( 1, -1),
|
|
923
|
+
_T(-1, 0),
|
|
924
|
+
_T( 0, 0),
|
|
925
|
+
_T( 1, 0),
|
|
926
|
+
_T( 2, 0),
|
|
927
|
+
_T(-1, 1),
|
|
928
|
+
_T( 0, 1),
|
|
929
|
+
_T( 1, 1),
|
|
930
|
+
_T( 2, 1),
|
|
931
|
+
_T( 0, 2),
|
|
932
|
+
_T( 1, 2))
|
|
933
|
+
|
|
934
|
+
_endian = '>H' # struct.unpack 1 ushort (big endian, unsigned short)
|
|
935
|
+
_4endian = '>4H' # struct.unpack 4 ushorts
|
|
936
|
+
_Rendian = NN # struct.unpack a row of ushorts
|
|
937
|
+
# _highest = (-8.4, 147.367, 85.839) if egm2008-1.pgm else (
|
|
938
|
+
# (-8.167, 147.25, 85.422) if egm96-5.pgm else
|
|
939
|
+
# (-4.5, 148.75, 81.33)) # egm84-15.pgm
|
|
940
|
+
# _lowest = (4.7, 78.767, -106.911) if egm2008-1.pgm else (
|
|
941
|
+
# (4.667, 78.833, -107.043) if egm96-5.pgm else
|
|
942
|
+
# (4.75, 79.25, -107.34)) # egm84-15.pgm
|
|
943
|
+
_mean = _F(-1.317) # from egm2008-1, -1.438 egm96-5, -0.855 egm84-15
|
|
944
|
+
_nBytes = None # not applicable
|
|
945
|
+
_nterms = len(_C3[0]) # columns length, number of row
|
|
946
|
+
_smooth = None # not applicable
|
|
947
|
+
_stdev = _F(29.244) # from egm2008-1, 29.227 egm96-5, 29.183 egm84-15
|
|
948
|
+
_u2B = _calcsize(_endian) # pixelsize_ in bytes
|
|
949
|
+
_4u2B = _calcsize(_4endian) # 4 pixelsize_s in bytes
|
|
950
|
+
_Ru2B = 0 # row of pixelsize_s in bytes
|
|
951
|
+
_yxH = () # cache (y, x) indices
|
|
952
|
+
_yxHt = () # cached 4- or 10-tuple for _ev2H resp. _ev3H
|
|
953
|
+
_yx_hits = 0 # cache hits
|
|
954
|
+
|
|
955
|
+
def __init__(self, egm_pgm, crop=None, datum=None, # WGS84
|
|
956
|
+
kind=3, name=NN, smooth=None):
|
|
957
|
+
'''New L{GeoidKarney} interpolator.
|
|
958
|
+
|
|
959
|
+
@arg egm_pgm: An U{EGM geoid dataset<https://GeographicLib.SourceForge.io/
|
|
960
|
+
C++/doc/geoid.html#geoidinst>} file name (C{egm*.pgm}), see
|
|
961
|
+
note below.
|
|
962
|
+
@kwarg crop: Optional box to limit geoid locations, a 4-tuple (C{south,
|
|
963
|
+
west, north, east}), 2-tuple (C{(south, west), (north,
|
|
964
|
+
east)}) or 2, in C{degrees90} lat- and C{degrees180}
|
|
965
|
+
longitudes or a 2-tuple (C{LatLonSW, LatLonNE}) of
|
|
966
|
+
C{LatLon} instances.
|
|
967
|
+
@kwarg datum: Optional grid datum (C{Datum}, L{Ellipsoid}, L{Ellipsoid2}
|
|
968
|
+
or L{a_f2Tuple}), default C{WGS84}.
|
|
969
|
+
@kwarg kind: Interpolation order (C{int}), 2 for C{bilinear} or 3
|
|
970
|
+
for C{cubic}.
|
|
971
|
+
@kwarg name: Optional geoid name (C{str}).
|
|
972
|
+
@kwarg smooth: Smoothing factor, unsupported (C{None}).
|
|
973
|
+
|
|
974
|
+
@raise GeoidError: EGM dataset B{C{egm_pgm}} issue or invalid
|
|
975
|
+
B{C{crop}}, B{C{kind}} or B{C{smooth}}.
|
|
976
|
+
|
|
977
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
978
|
+
|
|
979
|
+
@see: Class L{GeoidPGM} and function L{egmGeoidHeights}.
|
|
980
|
+
|
|
981
|
+
@note: Geoid file B{C{egm_pgm}} remains open and must be closed
|
|
982
|
+
by calling the C{close} method or by using this instance
|
|
983
|
+
in a C{with B{GeoidKarney}(...) as ...} context.
|
|
984
|
+
'''
|
|
985
|
+
if smooth is not None:
|
|
986
|
+
raise GeoidError(smooth=smooth, txt=_not_(_supported_))
|
|
987
|
+
|
|
988
|
+
if kind in (2,):
|
|
989
|
+
self._evH = self._ev2H
|
|
990
|
+
elif kind not in (3,):
|
|
991
|
+
raise GeoidError(kind=kind)
|
|
992
|
+
|
|
993
|
+
self._egm = g = self._open(egm_pgm, datum, kind, name, smooth)
|
|
994
|
+
self._pgm = p = _PGM(g, pgm=egm_pgm, itemsize=self.u2B, sizeB=self.sizeB)
|
|
995
|
+
|
|
996
|
+
self._Rendian = self._4endian.replace(_4_, str(p.nlon))
|
|
997
|
+
self._Ru2B = _calcsize(self._Rendian)
|
|
998
|
+
|
|
999
|
+
self._knots = p.knots # grid knots
|
|
1000
|
+
self._lon_of = float(p.flon) # forward offset
|
|
1001
|
+
self._lon_og = float(p.glon) # reverse offset
|
|
1002
|
+
# set earth (lat, lon) limits (s, w, n, e)
|
|
1003
|
+
self._lat_lo, self._lon_lo, \
|
|
1004
|
+
self._lat_hi, self._lon_hi = self._swne(crop if crop else p.crop4)
|
|
1005
|
+
self._cropped = True if crop else False
|
|
1006
|
+
|
|
1007
|
+
def __call__(self, *llis, **wrap_H):
|
|
1008
|
+
'''Interpolate the geoid height for one or several locations.
|
|
1009
|
+
|
|
1010
|
+
@arg llis: One or more locations (C{LatLon}s), all positional.
|
|
1011
|
+
@kwarg wrap_H: Keyword arguments C{B{wrap}=False, B{H}=False}.
|
|
1012
|
+
If C{B{wrap} is True}, wrap or I{normalize} all
|
|
1013
|
+
B{C{llis}} locations (C{bool}). If C{B{H} is True},
|
|
1014
|
+
return the I{orthometric} height instead of the
|
|
1015
|
+
I{geoid} height at each location (C{bool}).
|
|
1016
|
+
|
|
1017
|
+
@return: A single interpolated geoid (or orthometric) height
|
|
1018
|
+
(C{float}) or a list or tuple of interpolated geoid
|
|
1019
|
+
(or orthometric) heights (C{float}s).
|
|
1020
|
+
|
|
1021
|
+
@raise GeoidError: Insufficient number of B{C{llis}}, an
|
|
1022
|
+
invalid B{C{lli}} or the C{egm*.pgm}
|
|
1023
|
+
geoid file is closed.
|
|
1024
|
+
|
|
1025
|
+
@raise RangeError: An B{C{lli}} is outside this geoid's lat-
|
|
1026
|
+
or longitude range.
|
|
1027
|
+
|
|
1028
|
+
@note: To obtain I{orthometric} heights, each B{C{llis}}
|
|
1029
|
+
location must have an ellipsoid C{height} or C{h}
|
|
1030
|
+
attribute, otherwise C{height=0} is used.
|
|
1031
|
+
|
|
1032
|
+
@see: Function L{pygeodesy.heightOrthometric}.
|
|
1033
|
+
'''
|
|
1034
|
+
return self._called(llis, False, **wrap_H)
|
|
1035
|
+
|
|
1036
|
+
def _c0c3v(self, y, x):
|
|
1037
|
+
# get the common denominator, the 10x12 cubic matrix and
|
|
1038
|
+
# the 12 cubic v-coefficients around geoid index (y, x)
|
|
1039
|
+
p = self._pgm
|
|
1040
|
+
if 0 < x < (p.nlon - 2) and 0 < y < (p.nlat - 2):
|
|
1041
|
+
# read 4x4 ushorts, drop the 4 corners
|
|
1042
|
+
g = self._egm
|
|
1043
|
+
e = self._4endian
|
|
1044
|
+
n = self._4u2B
|
|
1045
|
+
R = self._Ru2B
|
|
1046
|
+
|
|
1047
|
+
b = self._seek(y - 1, x - 1)
|
|
1048
|
+
v = _unpack(e, g.read(n))[1:3]
|
|
1049
|
+
b += R
|
|
1050
|
+
g.seek(b, _SEEK_SET)
|
|
1051
|
+
v += _unpack(e, g.read(n))
|
|
1052
|
+
b += R
|
|
1053
|
+
g.seek(b, _SEEK_SET)
|
|
1054
|
+
v += _unpack(e, g.read(n))
|
|
1055
|
+
b += R
|
|
1056
|
+
g.seek(b, _SEEK_SET)
|
|
1057
|
+
v += _unpack(e, g.read(n))[1:3]
|
|
1058
|
+
j = 1
|
|
1059
|
+
|
|
1060
|
+
else: # likely some wrapped y and/or x's
|
|
1061
|
+
v = self._raws(y, x, GeoidKarney._CM)
|
|
1062
|
+
j = 0 if y < 1 else (1 if y < (p.nlat - 2) else 2)
|
|
1063
|
+
|
|
1064
|
+
return GeoidKarney._C0[j], GeoidKarney._C3[j], v
|
|
1065
|
+
|
|
1066
|
+
@Property_RO
|
|
1067
|
+
def dtype(self):
|
|
1068
|
+
'''Get the geoid's grid data type (C{str}).
|
|
1069
|
+
'''
|
|
1070
|
+
return 'ushort'
|
|
1071
|
+
|
|
1072
|
+
def _ev(self, lat, lon): # PYCHOK expected
|
|
1073
|
+
# interpolate the geoid height at grid (lat, lon)
|
|
1074
|
+
fy, fx = self._g2yx2(lat, lon)
|
|
1075
|
+
y, x = int(floor(fy)), int(floor(fx))
|
|
1076
|
+
fy -= y
|
|
1077
|
+
fx -= x
|
|
1078
|
+
H = self._evH(fy, fx, y, x) # ._ev3H or ._ev2H
|
|
1079
|
+
H *= self._pgm.Scale # H.fmul(self._pgm.Scale)
|
|
1080
|
+
H += self._pgm.Offset # H.fadd(self._pgm.Offset)
|
|
1081
|
+
return H.fsum()
|
|
1082
|
+
|
|
1083
|
+
def _ev2H(self, fy, fx, *yx):
|
|
1084
|
+
# compute the bilinear 4-tuple and interpolate raw H
|
|
1085
|
+
if self._yxH == yx:
|
|
1086
|
+
t = self._yxHt
|
|
1087
|
+
self._yx_hits += 1
|
|
1088
|
+
else:
|
|
1089
|
+
y, x = self._yxH = yx
|
|
1090
|
+
self._yxHt = t = self._raws(y, x, GeoidKarney._BT)
|
|
1091
|
+
v = _1_0, -fx, fx
|
|
1092
|
+
H = Fdot(v, t[0], t[0], t[1]).fmul(_1_0 - fy) # c = a * (1 - fy)
|
|
1093
|
+
H += Fdot(v, t[2], t[2], t[3]).fmul(fy) # c += b * fy
|
|
1094
|
+
return H
|
|
1095
|
+
|
|
1096
|
+
def _ev3H(self, fy, fx, *yx):
|
|
1097
|
+
# compute the cubic 10-tuple and interpolate raw H
|
|
1098
|
+
if self._yxH == yx:
|
|
1099
|
+
t = self._yxHt
|
|
1100
|
+
self._yx_hits += 1
|
|
1101
|
+
else:
|
|
1102
|
+
self._yxH = yx
|
|
1103
|
+
c0, c3, v = self._c0c3v(*yx)
|
|
1104
|
+
t = [fdot(v, *c3[i]) / c0 for i in range(self._nterms)]
|
|
1105
|
+
self._yxHt = t = tuple(t)
|
|
1106
|
+
# GeographicLib/Geoid.cpp Geoid::height(lat, lon) ...
|
|
1107
|
+
# real h = t[0] + fx * (t[1] + fx * (t[3] + fx * t[6])) +
|
|
1108
|
+
# fy * (t[2] + fx * (t[4] + fx * t[7]) +
|
|
1109
|
+
# fy * (t[5] + fx * t[8] + fy * t[9]));
|
|
1110
|
+
v = _1_0, fx, fy
|
|
1111
|
+
H = Fdot(v, t[5], t[8], t[9])
|
|
1112
|
+
H *= fy
|
|
1113
|
+
H += Fhorner(fx, t[2], t[4], t[7])
|
|
1114
|
+
H *= fy
|
|
1115
|
+
H += Fhorner(fx, t[0], t[1], t[3], t[6])
|
|
1116
|
+
return H
|
|
1117
|
+
|
|
1118
|
+
_evH = _ev3H # overriden for kind == 2
|
|
1119
|
+
|
|
1120
|
+
def _g2ll2(self, lat, lon):
|
|
1121
|
+
# convert grid (lat, lon) to earth (lat, lon), uncropped
|
|
1122
|
+
while lon > _180_0:
|
|
1123
|
+
lon -= _360_0
|
|
1124
|
+
return lat, lon
|
|
1125
|
+
|
|
1126
|
+
def _g2yx2(self, lat, lon):
|
|
1127
|
+
# convert grid (lat, lon) to grid (y, x) indices
|
|
1128
|
+
p = self._pgm
|
|
1129
|
+
# note, slat = +90, rlat < 0 makes y >=0
|
|
1130
|
+
return ((lat - p.slat) * p.rlat), ((lon - p.wlon) * p.rlon)
|
|
1131
|
+
|
|
1132
|
+
def _gyx2g2(self, y, x):
|
|
1133
|
+
# convert grid (y, x) indices to grid (lat, lon)
|
|
1134
|
+
p = self._pgm
|
|
1135
|
+
return (p.slat + p.dlat * y), (p.wlon + p.dlon * x)
|
|
1136
|
+
|
|
1137
|
+
def height(self, lats, lons, **wrap):
|
|
1138
|
+
'''Interpolate the geoid height for one or several lat-/longitudes.
|
|
1139
|
+
|
|
1140
|
+
@arg lats: Latitude or latitudes (C{degrees} or C{degrees}s).
|
|
1141
|
+
@arg lons: Longitude or longitudes (C{degrees} or C{degrees}s).
|
|
1142
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} all B{C{lats}}
|
|
1143
|
+
and B{C{lons}} locations (C{bool}).
|
|
1144
|
+
|
|
1145
|
+
@return: A single interpolated geoid height (C{float}) or a
|
|
1146
|
+
list of interpolated geoid heights (C{float}s).
|
|
1147
|
+
|
|
1148
|
+
@raise GeoidError: Insufficient or non-matching number of
|
|
1149
|
+
B{C{lats}} and B{C{lons}} or the C{egm*.pgm}
|
|
1150
|
+
geoid file is closed.
|
|
1151
|
+
|
|
1152
|
+
@raise RangeError: A B{C{lat}} or B{C{lon}} is outside this
|
|
1153
|
+
geoid's lat- or longitude range.
|
|
1154
|
+
'''
|
|
1155
|
+
return _height_called(self, lats, lons, Error=GeoidError, **wrap)
|
|
1156
|
+
|
|
1157
|
+
@Property_RO
|
|
1158
|
+
def _highest_ltd(self):
|
|
1159
|
+
'''(INTERNAL) Cache for L{highest} mesthod.
|
|
1160
|
+
'''
|
|
1161
|
+
return self._llh3minmax(True, -12, -4)
|
|
1162
|
+
|
|
1163
|
+
def highest(self, LatLon=None, full=False): # PYCHOK full
|
|
1164
|
+
'''Return the location and largest height of this geoid.
|
|
1165
|
+
|
|
1166
|
+
@kwarg LatLon: Optional class to return the location and height
|
|
1167
|
+
(C{LatLon}) or C{None}.
|
|
1168
|
+
@kwarg full: Search the full or limited latitude range (C{bool}).
|
|
1169
|
+
|
|
1170
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
1171
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
1172
|
+
with the lat-, longitude and geoid height at the
|
|
1173
|
+
highest grid location.
|
|
1174
|
+
'''
|
|
1175
|
+
llh = self._highest if full or self.cropped else self._highest_ltd
|
|
1176
|
+
return self._llh3LL(llh, LatLon)
|
|
1177
|
+
|
|
1178
|
+
def _lat2y2(self, lat2):
|
|
1179
|
+
# convert earth lat(s) to min and max grid y indices
|
|
1180
|
+
ys, m = [], self._pgm.nlat - 1
|
|
1181
|
+
for lat in lat2:
|
|
1182
|
+
y, _ = self._g2yx2(*self._ll2g2(lat, 0))
|
|
1183
|
+
ys.append(max(min(int(y), m), 0))
|
|
1184
|
+
return min(ys), max(ys) + 1
|
|
1185
|
+
|
|
1186
|
+
def _ll2g2(self, lat, lon):
|
|
1187
|
+
# convert earth (lat, lon) to grid (lat, lon), uncropped
|
|
1188
|
+
while lon < 0:
|
|
1189
|
+
lon += _360_0
|
|
1190
|
+
return lat, lon
|
|
1191
|
+
|
|
1192
|
+
def _llh3minmax(self, highest=True, *lat2):
|
|
1193
|
+
# find highest or lowest, takes 10+ secs for egm2008-1.pgm geoid
|
|
1194
|
+
# (Python 2.7.16, macOS 10.13.6 High Sierra, iMac 3 GHz Core i3)
|
|
1195
|
+
y = x = 0
|
|
1196
|
+
h = self._raw(y, x)
|
|
1197
|
+
if highest:
|
|
1198
|
+
for j, r in self._raw2(*lat2):
|
|
1199
|
+
m = max(r)
|
|
1200
|
+
if m > h:
|
|
1201
|
+
h, y, x = m, j, r.index(m)
|
|
1202
|
+
else: # lowest
|
|
1203
|
+
for j, r in self._raw2(*lat2):
|
|
1204
|
+
m = min(r)
|
|
1205
|
+
if m < h:
|
|
1206
|
+
h, y, x = m, j, r.index(m)
|
|
1207
|
+
h *= self._pgm.Scale
|
|
1208
|
+
h += self._pgm.Offset
|
|
1209
|
+
return self._g2ll2(*self._gyx2g2(y, x)) + (h,)
|
|
1210
|
+
|
|
1211
|
+
@Property_RO
|
|
1212
|
+
def _lowest_ltd(self):
|
|
1213
|
+
'''(INTERNAL) Cache for L{lowest}.
|
|
1214
|
+
'''
|
|
1215
|
+
return self._llh3minmax(False, 0, 8)
|
|
1216
|
+
|
|
1217
|
+
def lowest(self, LatLon=None, full=False): # PYCHOK full
|
|
1218
|
+
'''Return the location and lowest height of this geoid.
|
|
1219
|
+
|
|
1220
|
+
@kwarg LatLon: Optional class to return the location and height
|
|
1221
|
+
(C{LatLon}) or C{None}.
|
|
1222
|
+
@kwarg full: Search the full or limited latitude range (C{bool}).
|
|
1223
|
+
|
|
1224
|
+
@return: If B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat,
|
|
1225
|
+
lon, height)} otherwise a B{C{LatLon}} instance
|
|
1226
|
+
with the lat-, longitude and geoid height of the
|
|
1227
|
+
lowest grid location.
|
|
1228
|
+
'''
|
|
1229
|
+
llh = self._lowest if full or self.cropped else self._lowest_ltd
|
|
1230
|
+
return self._llh3LL(llh, LatLon)
|
|
1231
|
+
|
|
1232
|
+
def _raw(self, y, x):
|
|
1233
|
+
# get the ushort geoid height at geoid index (y, x),
|
|
1234
|
+
# like GeographicLib/Geoid.hpp real rawval(is, iy)
|
|
1235
|
+
p = self._pgm
|
|
1236
|
+
if x < 0:
|
|
1237
|
+
x += p.nlon
|
|
1238
|
+
elif x >= p.nlon:
|
|
1239
|
+
x -= p.nlon
|
|
1240
|
+
h = p.nlon // 2
|
|
1241
|
+
if y < 0:
|
|
1242
|
+
y = -y
|
|
1243
|
+
elif y >= p.nlat:
|
|
1244
|
+
y = (p.nlat - 1) * 2 - y
|
|
1245
|
+
else:
|
|
1246
|
+
h = 0
|
|
1247
|
+
x += h if x < h else -h
|
|
1248
|
+
self._seek(y, x)
|
|
1249
|
+
h = _unpack(self._endian, self._egm.read(self._u2B))
|
|
1250
|
+
return h[0]
|
|
1251
|
+
|
|
1252
|
+
def _raws(self, y, x, ijs):
|
|
1253
|
+
# get bilinear 4-tuple or 10x12 cubic matrix
|
|
1254
|
+
return tuple(self._raw(y + j, x + i) for i, j in ijs)
|
|
1255
|
+
|
|
1256
|
+
def _raw2(self, *lat2):
|
|
1257
|
+
# yield a 2-tuple (y, ushorts) for each row or for
|
|
1258
|
+
# the rows between two (or more) earth lat values
|
|
1259
|
+
p = self._pgm
|
|
1260
|
+
g = self._egm
|
|
1261
|
+
e = self._Rendian
|
|
1262
|
+
n = self._Ru2B
|
|
1263
|
+
# min(lat2) <= lat <= max(lat2) or 0 <= y < p.nlat
|
|
1264
|
+
s, t = self._lat2y2(lat2) if lat2 else (0, p.nlat)
|
|
1265
|
+
self._seek(s, 0) # to start of row s
|
|
1266
|
+
for y in range(s, t):
|
|
1267
|
+
yield y, _unpack(e, g.read(n))
|
|
1268
|
+
|
|
1269
|
+
def _seek(self, y, x):
|
|
1270
|
+
# position geoid to grid index (y, x)
|
|
1271
|
+
p, g = self._pgm, self._egm
|
|
1272
|
+
if g:
|
|
1273
|
+
b = p.skip + (y * p.nlon + x) * self._u2B
|
|
1274
|
+
g.seek(b, _SEEK_SET)
|
|
1275
|
+
return b # position
|
|
1276
|
+
raise GeoidError('closed file', txt=repr(p.egm)) # IOError
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
class GeoidPGM(_GeoidBase):
|
|
1280
|
+
'''Geoid height interpolator for I{Karney}'s U{GeographicLib Earth
|
|
1281
|
+
Gravitational Model (EGM)<https://GeographicLib.SourceForge.io/C++/doc/
|
|
1282
|
+
geoid.html>} geoid U{egm*.pgm<https://GeographicLib.SourceForge.io/
|
|
1283
|
+
C++/doc/geoid.html#geoidinst>} datasets but based on C{SciPy}
|
|
1284
|
+
U{RectBivariateSpline<https://docs.SciPy.org/doc/scipy/reference/
|
|
1285
|
+
generated/scipy.interpolate.RectBivariateSpline.html>} or
|
|
1286
|
+
U{interp2d<https://docs.SciPy.org/doc/scipy/reference/generated/
|
|
1287
|
+
scipy.interpolate.interp2d.html>} interpolation.
|
|
1288
|
+
|
|
1289
|
+
Use any of the U{egm84-, egm96- or egm2008-*.pgm
|
|
1290
|
+
<https://GeographicLib.SourceForge.io/C++/doc/geoid.html#geoidinst>}
|
|
1291
|
+
datasets. However, unless cropped, an entire C{egm*.pgm} dataset
|
|
1292
|
+
is loaded into the C{SciPy} U{RectBivariateSpline<https://docs.SciPy.org/
|
|
1293
|
+
doc/scipy/reference/generated/scipy.interpolate.RectBivariateSpline.html>}
|
|
1294
|
+
or U{interp2d<https://docs.SciPy.org/doc/scipy/reference/generated/
|
|
1295
|
+
scipy.interpolate.interp2d.html>} interpolator and converted from
|
|
1296
|
+
2-byte C{int} to 8-byte C{dtype float64}. Therefore, internal memory
|
|
1297
|
+
usage is 4x the U{egm*.pgm<https://GeographicLib.SourceForge.io/C++/doc/
|
|
1298
|
+
geoid.html#geoidinst>} file size and may exceed the available memory,
|
|
1299
|
+
especially with 32-bit Python, see properties C{.nBytes} and C{.sizeB}.
|
|
1300
|
+
'''
|
|
1301
|
+
_cropped = False
|
|
1302
|
+
_endian = '>u2'
|
|
1303
|
+
|
|
1304
|
+
def __init__(self, egm_pgm, crop=None, datum=None, # WGS84
|
|
1305
|
+
kind=3, name=NN, smooth=0):
|
|
1306
|
+
'''New L{GeoidPGM} interpolator.
|
|
1307
|
+
|
|
1308
|
+
@arg egm_pgm: An U{EGM geoid dataset<https://GeographicLib.SourceForge.io/
|
|
1309
|
+
C++/doc/geoid.html#geoidinst>} file name (C{egm*.pgm}).
|
|
1310
|
+
@kwarg crop: Optional box to crop B{C{egm_pgm}}, a 4-tuple (C{south, west,
|
|
1311
|
+
north, east}) or 2-tuple (C{(south, west), (north, east)}),
|
|
1312
|
+
in C{degrees90} lat- and C{degrees180} longitudes or a
|
|
1313
|
+
2-tuple (C{LatLonSW, LatLonNE}) of C{LatLon} instances.
|
|
1314
|
+
@kwarg datum: Optional grid datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
|
|
1315
|
+
or L{a_f2Tuple}), default C{WGS84}.
|
|
1316
|
+
@kwarg kind: C{scipy.interpolate} order (C{int}), use 1..5 for
|
|
1317
|
+
U{RectBivariateSpline<https://docs.SciPy.org/doc/scipy/
|
|
1318
|
+
reference/generated/scipy.interpolate.RectBivariateSpline.html>},
|
|
1319
|
+
-2 for U{interp2d linear<https://docs.SciPy.org/doc/scipy/
|
|
1320
|
+
reference/generated/scipy.interpolate.interp2d.html>}, -3
|
|
1321
|
+
for C{interp2d cubic} or -5 for C{interp2d quintic}.
|
|
1322
|
+
@kwarg name: Optional geoid name (C{str}).
|
|
1323
|
+
@kwarg smooth: Smoothing factor for U{RectBivariateSpline
|
|
1324
|
+
<https://docs.SciPy.org/doc/scipy/reference/generated/
|
|
1325
|
+
scipy.interpolate.RectBivariateSpline.html>}
|
|
1326
|
+
only (C{int}).
|
|
1327
|
+
|
|
1328
|
+
@raise GeoidError: EGM dataset B{C{egm_pgm}} issue or invalid B{C{crop}},
|
|
1329
|
+
B{C{kind}} or B{C{smooth}}.
|
|
1330
|
+
|
|
1331
|
+
@raise ImportError: Package C{numpy} or C{scipy} not found or not installed.
|
|
1332
|
+
|
|
1333
|
+
@raise LenError: EGM dataset B{C{egm_pgm}} axis mismatch.
|
|
1334
|
+
|
|
1335
|
+
@raise SciPyError: A C{RectBivariateSpline} or C{inter2d} issue.
|
|
1336
|
+
|
|
1337
|
+
@raise SciPyWarning: A C{RectBivariateSpline} or C{inter2d}
|
|
1338
|
+
warning as exception.
|
|
1339
|
+
|
|
1340
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
1341
|
+
|
|
1342
|
+
@note: C{scipy.interpolate.interp2d} has been C{DEPRECATED}, specify
|
|
1343
|
+
C{B{kind}=1..5} for C{scipy.interpolate.RectBivariateSpline}.
|
|
1344
|
+
|
|
1345
|
+
@note: The U{GeographicLib egm*.pgm<https://GeographicLib.SourceForge.io/
|
|
1346
|
+
C++/doc/geoid.html#geoidinst>} file sizes are based on a 2-byte
|
|
1347
|
+
C{int} height converted to 8-byte C{dtype float64} for C{scipy}
|
|
1348
|
+
interpolators. Therefore, internal memory usage is 4 times the
|
|
1349
|
+
C{egm*.pgm} file size and may exceed the available memory,
|
|
1350
|
+
especially with 32-bit Python. To reduce memory usage, set
|
|
1351
|
+
keyword argument B{C{crop}} to the region of interest. For example
|
|
1352
|
+
C{B{crop}=(20, -125, 50, -65)} covers the U{conterminous US<https://
|
|
1353
|
+
www.NGS.NOAA.gov/GEOID/GEOID12B/maps/GEOID12B_CONUS_grids.png>}
|
|
1354
|
+
(CONUS), less than 3% of the entire C{egm2008-1.pgm} dataset.
|
|
1355
|
+
|
|
1356
|
+
@see: Class L{GeoidKarney} and function L{egmGeoidHeights}.
|
|
1357
|
+
'''
|
|
1358
|
+
np = self.numpy
|
|
1359
|
+
self._u2B = np.dtype(self.endian).itemsize
|
|
1360
|
+
|
|
1361
|
+
g = self._open(egm_pgm, datum, kind, name, smooth)
|
|
1362
|
+
self._pgm = p = _PGM(g, pgm=egm_pgm, itemsize=self.u2B, sizeB=self.sizeB)
|
|
1363
|
+
if crop:
|
|
1364
|
+
g = p._cropped(g, abs(kind) + 1, *self._swne(crop))
|
|
1365
|
+
if _version2(np.__version__) < (1, 9):
|
|
1366
|
+
g = open(g.name, _rb_) # reopen tempfile for numpy 1.8.0-
|
|
1367
|
+
self._cropped = True
|
|
1368
|
+
try:
|
|
1369
|
+
# U{numpy dtype formats are different from Python struct formats
|
|
1370
|
+
# <https://docs.SciPy.org/doc/numpy-1.15.0/reference/arrays.dtypes.html>}
|
|
1371
|
+
# read all heights, skipping the PGM header lines, converted to float
|
|
1372
|
+
hs = self._load(g, self.endian, p.knots, p.skip).reshape(p.nlat, p.nlon) * p.Scale
|
|
1373
|
+
if p.Offset: # offset
|
|
1374
|
+
hs = p.Offset + hs
|
|
1375
|
+
if p.dlat < 0: # flip the rows
|
|
1376
|
+
hs = np.flipud(hs)
|
|
1377
|
+
_GeoidBase.__init__(self, hs, p)
|
|
1378
|
+
except Exception as x:
|
|
1379
|
+
raise _SciPyIssue(x, _in_, repr(egm_pgm))
|
|
1380
|
+
finally:
|
|
1381
|
+
g.close()
|
|
1382
|
+
|
|
1383
|
+
def _g2ll2(self, lat, lon):
|
|
1384
|
+
# convert grid (lat, lon) to earth (lat, lon), un-/cropped
|
|
1385
|
+
if self._cropped:
|
|
1386
|
+
lon -= self._lon_of
|
|
1387
|
+
else:
|
|
1388
|
+
while lon > _180_0:
|
|
1389
|
+
lon -= _360_0
|
|
1390
|
+
return lat, lon
|
|
1391
|
+
|
|
1392
|
+
def _ll2g2(self, lat, lon):
|
|
1393
|
+
# convert earth (lat, lon) to grid (lat, lon), un-/cropped
|
|
1394
|
+
if self._cropped:
|
|
1395
|
+
lon += self._lon_of
|
|
1396
|
+
else:
|
|
1397
|
+
while lon < 0:
|
|
1398
|
+
lon += _360_0
|
|
1399
|
+
return lat, lon
|
|
1400
|
+
|
|
1401
|
+
if _FOR_DOCS:
|
|
1402
|
+
__call__ = _GeoidBase.__call__
|
|
1403
|
+
height = _GeoidBase.height
|
|
1404
|
+
|
|
1405
|
+
|
|
1406
|
+
class _Gpars(_Named):
|
|
1407
|
+
'''(INTERNAL) Basic geoid parameters.
|
|
1408
|
+
'''
|
|
1409
|
+
# interpolator parameters
|
|
1410
|
+
dlat = 0 # +/- latitude resolution in C{degrees}
|
|
1411
|
+
dlon = 0 # longitude resolution in C{degrees}
|
|
1412
|
+
nlat = 1 # number of latitude knots (C{int})
|
|
1413
|
+
nlon = 0 # number of longitude knots (C{int})
|
|
1414
|
+
rlat = 0 # +/- latitude resolution in C{float}, 1 / .dlat
|
|
1415
|
+
rlon = 0 # longitude resolution in C{float}, 1 / .dlon
|
|
1416
|
+
slat = 0 # nothern- or southern most latitude (C{degrees90})
|
|
1417
|
+
wlon = 0 # western-most longitude in Eastern lon (C{degrees360})
|
|
1418
|
+
|
|
1419
|
+
flon = 0 # forward, earth to grid longitude offset
|
|
1420
|
+
glon = 0 # reverse, grid to earth longitude offset
|
|
1421
|
+
|
|
1422
|
+
knots = 0 # number of knots, nlat * nlon (C{int})
|
|
1423
|
+
skip = 0 # header bytes to skip (C{int})
|
|
1424
|
+
|
|
1425
|
+
def __repr__(self):
|
|
1426
|
+
t = _COMMASPACE_.join(pairs((a, getattr(self, a)) for
|
|
1427
|
+
a in dir(self.__class__)
|
|
1428
|
+
if a[:1].isupper()))
|
|
1429
|
+
return _COLONSPACE_(self, t)
|
|
1430
|
+
|
|
1431
|
+
def __str__(self):
|
|
1432
|
+
return Fmt.PAREN(self.classname, repr(self.name))
|
|
1433
|
+
|
|
1434
|
+
|
|
1435
|
+
class _PGM(_Gpars):
|
|
1436
|
+
'''(INTERNAL) Parse an C{egm*.pgm} geoid dataset file.
|
|
1437
|
+
|
|
1438
|
+
# Geoid file in PGM format for the GeographicLib::Geoid class
|
|
1439
|
+
# Description WGS84 EGM96, 5-minute grid
|
|
1440
|
+
# URL https://Earth-Info.NGA.mil/GandG/wgs84/gravitymod/egm96/egm96.html
|
|
1441
|
+
# DateTime 2009-08-29 18:45:03
|
|
1442
|
+
# MaxBilinearError 0.140
|
|
1443
|
+
# RMSBilinearError 0.005
|
|
1444
|
+
# MaxCubicError 0.003
|
|
1445
|
+
# RMSCubicError 0.001
|
|
1446
|
+
# Offset -108
|
|
1447
|
+
# Scale 0.003
|
|
1448
|
+
# Origin 90N 0E
|
|
1449
|
+
# AREA_OR_POINT Point
|
|
1450
|
+
# Vertical_Datum WGS84
|
|
1451
|
+
<width> <height>
|
|
1452
|
+
<pixel>
|
|
1453
|
+
...
|
|
1454
|
+
'''
|
|
1455
|
+
crop4 = () # 4-tuple (C{south, west, north, east}).
|
|
1456
|
+
egm = None
|
|
1457
|
+
glon = 180 # reverse offset, uncropped
|
|
1458
|
+
# pgm = NN # name
|
|
1459
|
+
sizeB = 0
|
|
1460
|
+
u2B = 2 # item size of grid height (C{int}).
|
|
1461
|
+
|
|
1462
|
+
@staticmethod
|
|
1463
|
+
def _llstr2floats(latlon):
|
|
1464
|
+
# llstr to (lat, lon) floats
|
|
1465
|
+
lat, lon = latlon.split()
|
|
1466
|
+
return _MODS.dms.parseDMS2(lat, lon)
|
|
1467
|
+
|
|
1468
|
+
# PGM file attributes, CamelCase but not .istitle()
|
|
1469
|
+
AREA_OR_POINT = str
|
|
1470
|
+
DateTime = str
|
|
1471
|
+
Description = str # 'WGS84 EGM96, 5-minute grid'
|
|
1472
|
+
Geoid = str # 'file in PGM format for the GeographicLib::Geoid class'
|
|
1473
|
+
MaxBilinearError = float
|
|
1474
|
+
MaxCubicError = float
|
|
1475
|
+
Offset = float
|
|
1476
|
+
Origin = _llstr2floats
|
|
1477
|
+
Pixel = 0
|
|
1478
|
+
RMSBilinearError = float
|
|
1479
|
+
RMSCubicError = float
|
|
1480
|
+
Scale = float
|
|
1481
|
+
URL = str # 'https://Earth-Info.NGA.mil/GandG/wgs84/...'
|
|
1482
|
+
Vertical_Datum = str
|
|
1483
|
+
|
|
1484
|
+
def __init__(self, g, pgm=NN, itemsize=0, sizeB=0): # MCCABE 22
|
|
1485
|
+
'''(INTERNAL) New C{_PGM} parsed C{egm*.pgm} geoid dataset.
|
|
1486
|
+
'''
|
|
1487
|
+
self.name = pgm # geoid file name
|
|
1488
|
+
if itemsize:
|
|
1489
|
+
self._u2B = itemsize
|
|
1490
|
+
if sizeB:
|
|
1491
|
+
self.sizeB = sizeB
|
|
1492
|
+
|
|
1493
|
+
t = g.readline() # make sure newline == '\n'
|
|
1494
|
+
if t != b'P5\n' and t.strip() != b'P5':
|
|
1495
|
+
raise self._Errorf(_format_, _header_, t)
|
|
1496
|
+
|
|
1497
|
+
while True: # read all # Attr ... lines,
|
|
1498
|
+
try: # ignore empty ones or comments
|
|
1499
|
+
t = g.readline().strip()
|
|
1500
|
+
if t.startswith(_bHASH_):
|
|
1501
|
+
t = t.lstrip(_bHASH_).lstrip()
|
|
1502
|
+
a, v = map(_ub2str, t.split(None, 1))
|
|
1503
|
+
f = getattr(_PGM, a, None)
|
|
1504
|
+
if callable(f) and a[:1].isupper():
|
|
1505
|
+
setattr(self, a, f(v))
|
|
1506
|
+
elif t:
|
|
1507
|
+
break
|
|
1508
|
+
except (TypeError, ValueError):
|
|
1509
|
+
raise self._Errorf(_format_, 'Attr', t)
|
|
1510
|
+
else: # should never get here
|
|
1511
|
+
raise self._Errorf(_format_, _header_, g.tell())
|
|
1512
|
+
|
|
1513
|
+
try: # must be (even) width and (odd) height
|
|
1514
|
+
nlon, nlat = map(int, t.split())
|
|
1515
|
+
if nlon < 2 or nlon > (360 * 60) or isodd(nlon) or \
|
|
1516
|
+
nlat < 2 or nlat > (181 * 60) or not isodd(nlat):
|
|
1517
|
+
raise ValueError
|
|
1518
|
+
except (TypeError, ValueError):
|
|
1519
|
+
raise self._Errorf(_format_, _SPACE_(_width_, _height_), t)
|
|
1520
|
+
|
|
1521
|
+
try: # must be 16 bit pixel height
|
|
1522
|
+
t = g.readline().strip()
|
|
1523
|
+
self.Pixel = int(t)
|
|
1524
|
+
if not 255 < self.Pixel < 65536: # >u2 or >H only
|
|
1525
|
+
raise ValueError
|
|
1526
|
+
except (TypeError, ValueError):
|
|
1527
|
+
raise self._Errorf(_format_, 'pixel', t)
|
|
1528
|
+
|
|
1529
|
+
for a in dir(_PGM): # set undefined # Attr ... to None
|
|
1530
|
+
if a[:1].isupper() and callable(getattr(self, a)):
|
|
1531
|
+
setattr(self, a, None)
|
|
1532
|
+
|
|
1533
|
+
if self.Origin is None:
|
|
1534
|
+
raise self._Errorf(_format_, 'Origin', self.Origin)
|
|
1535
|
+
if self.Offset is None or self.Offset > 0:
|
|
1536
|
+
raise self._Errorf(_format_, 'Offset', self.Offset)
|
|
1537
|
+
if self.Scale is None or self.Scale < EPS:
|
|
1538
|
+
raise self._Errorf(_format_, 'Scale', self.Scale)
|
|
1539
|
+
|
|
1540
|
+
self.skip = g.tell()
|
|
1541
|
+
self.knots = nlat * nlon
|
|
1542
|
+
|
|
1543
|
+
self.nlat, self.nlon = nlat, nlon
|
|
1544
|
+
self.slat, self.wlon = self.Origin
|
|
1545
|
+
# note, negative .dlat and .rlat since rows
|
|
1546
|
+
# are from .slat 90N down in decreasing lat
|
|
1547
|
+
self.dlat, self.dlon = _180_0 / (1 - nlat), _360_0 / nlon
|
|
1548
|
+
self.rlat, self.rlon = (1 - nlat) / _180_0, nlon / _360_0
|
|
1549
|
+
|
|
1550
|
+
# grid corners in earth (lat, lon), .slat = 90, .dlat < 0
|
|
1551
|
+
n = float(self.slat)
|
|
1552
|
+
s = n + self.dlat * (nlat - 1)
|
|
1553
|
+
w = self.wlon - self.glon
|
|
1554
|
+
e = w + self.dlon * nlon
|
|
1555
|
+
self.crop4 = s, w, n, e
|
|
1556
|
+
|
|
1557
|
+
n = self.sizeB - self.skip
|
|
1558
|
+
if n > 0 and n != (self.knots * self.u2B):
|
|
1559
|
+
raise self._Errorf('%s(%s x %s != %s)', _assert_, nlat, nlon, n)
|
|
1560
|
+
|
|
1561
|
+
def _cropped(self, g, k1, south, west, north, east): # MCCABE 15
|
|
1562
|
+
'''Crop the geoid to (south, west, north, east) box.
|
|
1563
|
+
'''
|
|
1564
|
+
# flon offset for both west and east
|
|
1565
|
+
f = 360 if west < 0 else 0
|
|
1566
|
+
# earth (lat, lon) to grid indices (y, x),
|
|
1567
|
+
# note y is decreasing, i.e. n < s
|
|
1568
|
+
s, w = self._lle2yx2(south, west, f)
|
|
1569
|
+
n, e = self._lle2yx2(north, east, f)
|
|
1570
|
+
s += 1 # s > n
|
|
1571
|
+
e += 1 # e > w
|
|
1572
|
+
|
|
1573
|
+
hi, wi = self.nlat, self.nlon
|
|
1574
|
+
# handle special cases
|
|
1575
|
+
if (s - n) > hi:
|
|
1576
|
+
n, s = 0, hi # entire lat range
|
|
1577
|
+
if (e - w) > wi:
|
|
1578
|
+
w, e, f = 0, wi, 180 # entire lon range
|
|
1579
|
+
if s == hi and w == n == 0 and e == wi:
|
|
1580
|
+
return g # use entire geoid as-is
|
|
1581
|
+
|
|
1582
|
+
if (e - w) < k1 or (s - n) < (k1 + 1):
|
|
1583
|
+
raise self._Errorf(_format_, 'swne', (north - south, east - west))
|
|
1584
|
+
|
|
1585
|
+
if e > wi > w: # wrap around
|
|
1586
|
+
# read w..wi and 0..e
|
|
1587
|
+
r, p = (wi - w), (e - wi)
|
|
1588
|
+
elif e > w:
|
|
1589
|
+
r, p = (e - w), 0
|
|
1590
|
+
else:
|
|
1591
|
+
raise self._Errorf('%s(%s < %s)', _assert_, w, e)
|
|
1592
|
+
|
|
1593
|
+
# convert to bytes
|
|
1594
|
+
r *= self.u2B
|
|
1595
|
+
p *= self.u2B
|
|
1596
|
+
q = wi * self.u2B # stride
|
|
1597
|
+
# number of rows and cols to skip from
|
|
1598
|
+
# the original (.slat, .wlon) origin
|
|
1599
|
+
z = self.skip + (n * wi + w) * self.u2B
|
|
1600
|
+
# sanity check
|
|
1601
|
+
if r < 2 or p < 0 or q < 2 or z < self.skip \
|
|
1602
|
+
or z > self.sizeB:
|
|
1603
|
+
raise self._Errorf(_format_, _assert_, (r, p, q, z))
|
|
1604
|
+
|
|
1605
|
+
# can't use _BytesIO since numpy
|
|
1606
|
+
# needs .fileno attr in .fromfile
|
|
1607
|
+
t, c = 0, self._tmpfile()
|
|
1608
|
+
# reading (s - n) rows, forward
|
|
1609
|
+
for y in range(n, s): # PYCHOK y unused
|
|
1610
|
+
g.seek(z, _SEEK_SET)
|
|
1611
|
+
# Python 2 tmpfile.write returns None
|
|
1612
|
+
t += c.write(g.read(r)) or r
|
|
1613
|
+
if p: # wrap around to start of row
|
|
1614
|
+
g.seek(-q, _SEEK_CUR)
|
|
1615
|
+
# assert(g.tell() == (z - w * self.u2B))
|
|
1616
|
+
# Python 2 tmpfile.write returns None
|
|
1617
|
+
t += c.write(g.read(p)) or p
|
|
1618
|
+
z += q
|
|
1619
|
+
c.flush()
|
|
1620
|
+
g.close()
|
|
1621
|
+
|
|
1622
|
+
s -= n # nlat
|
|
1623
|
+
e -= w # nlon
|
|
1624
|
+
k = s * e # knots
|
|
1625
|
+
z = k * self.u2B
|
|
1626
|
+
if t != z:
|
|
1627
|
+
raise self._Errorf('%s(%s != %s) %s', _assert_, t, z, self)
|
|
1628
|
+
|
|
1629
|
+
# update the _Gpars accordingly, note attributes
|
|
1630
|
+
# .dlat, .dlon, .rlat and .rlon remain unchanged
|
|
1631
|
+
self.slat += n * self.dlat
|
|
1632
|
+
self.wlon += w * self.dlon
|
|
1633
|
+
self.nlat = s
|
|
1634
|
+
self.nlon = e
|
|
1635
|
+
self.flon = self.glon = f
|
|
1636
|
+
|
|
1637
|
+
self.crop4 = south, west, north, east
|
|
1638
|
+
self.knots = k
|
|
1639
|
+
self.skip = 0 # no header lines in c
|
|
1640
|
+
|
|
1641
|
+
c.seek(0, _SEEK_SET)
|
|
1642
|
+
# c = open(c.name, _rb_) # reopen for numpy 1.8.0-
|
|
1643
|
+
return c
|
|
1644
|
+
|
|
1645
|
+
def _Errorf(self, fmt, *args): # PYCHOK no cover
|
|
1646
|
+
t = fmt % args
|
|
1647
|
+
e = self.pgm or NN
|
|
1648
|
+
if e:
|
|
1649
|
+
t = _SPACE_(t, _in_, repr(e))
|
|
1650
|
+
return PGMError(t)
|
|
1651
|
+
|
|
1652
|
+
def _lle2yx2(self, lat, lon, flon):
|
|
1653
|
+
# earth (lat, lon) to grid indices (y, x)
|
|
1654
|
+
# with .dlat decreasing from 90N .slat
|
|
1655
|
+
lat -= self.slat
|
|
1656
|
+
lon += flon - self.wlon
|
|
1657
|
+
return (min(self.nlat - 1, max(0, int(lat * self.rlat))),
|
|
1658
|
+
max(0, int(lon * self.rlon)))
|
|
1659
|
+
|
|
1660
|
+
def _tmpfile(self):
|
|
1661
|
+
# create a tmpfile to hold the cropped geoid grid
|
|
1662
|
+
try:
|
|
1663
|
+
from tempfile import NamedTemporaryFile as tmpfile
|
|
1664
|
+
except ImportError: # Python 2.7.16-
|
|
1665
|
+
from os import tmpfile
|
|
1666
|
+
t = _os_path.splitext(_os_path.basename(self.pgm))[0]
|
|
1667
|
+
f = tmpfile(mode='w+b', prefix=t or 'egm')
|
|
1668
|
+
f.seek(0, _SEEK_SET) # force overwrite
|
|
1669
|
+
return f
|
|
1670
|
+
|
|
1671
|
+
@Property_RO
|
|
1672
|
+
def pgm(self):
|
|
1673
|
+
'''Get the geoid file name (C{str}).
|
|
1674
|
+
'''
|
|
1675
|
+
return self.name
|
|
1676
|
+
|
|
1677
|
+
|
|
1678
|
+
class PGMError(GeoidError):
|
|
1679
|
+
'''Issue parsing or cropping an C{egm*.pgm} geoid dataset.
|
|
1680
|
+
'''
|
|
1681
|
+
pass
|
|
1682
|
+
|
|
1683
|
+
|
|
1684
|
+
def egmGeoidHeights(GeoidHeights_dat):
|
|
1685
|
+
'''Generate geoid U{egm*.pgm<https://GeographicLib.SourceForge.io/
|
|
1686
|
+
C++/doc/geoid.html#geoidinst>} height tests from U{GeoidHeights.dat
|
|
1687
|
+
<https://SourceForge.net/projects/geographiclib/files/testdata/>}
|
|
1688
|
+
U{Test data for Geoids<https://GeographicLib.SourceForge.io/C++/doc/
|
|
1689
|
+
geoid.html#testgeoid>}.
|
|
1690
|
+
|
|
1691
|
+
@arg GeoidHeights_dat: The un-gz-ed C{GeoidHeights.dat} file
|
|
1692
|
+
(C{str} or C{file} handle).
|
|
1693
|
+
|
|
1694
|
+
@return: For each test, yield a L{GeoidHeight5Tuple}C{(lat, lon,
|
|
1695
|
+
egm84, egm96, egm2008)}.
|
|
1696
|
+
|
|
1697
|
+
@raise GeoidError: Invalid B{C{GeoidHeights_dat}}.
|
|
1698
|
+
|
|
1699
|
+
@note: Function L{egmGeoidHeights} is used to test the geoids
|
|
1700
|
+
L{GeoidKarney} and L{GeoidPGM}, see PyGeodesy module
|
|
1701
|
+
C{test/testGeoids.py}.
|
|
1702
|
+
'''
|
|
1703
|
+
dat = GeoidHeights_dat
|
|
1704
|
+
if isinstance(dat, bytes):
|
|
1705
|
+
dat = _BytesIO(dat)
|
|
1706
|
+
|
|
1707
|
+
try:
|
|
1708
|
+
dat.seek(0, _SEEK_SET) # reset
|
|
1709
|
+
except AttributeError as x:
|
|
1710
|
+
raise GeoidError(GeoidHeights_dat=type(dat), cause=x)
|
|
1711
|
+
|
|
1712
|
+
for t in dat.readlines():
|
|
1713
|
+
t = t.strip()
|
|
1714
|
+
if t and not t.startswith(_bHASH_):
|
|
1715
|
+
lat, lon, egm84, egm96, egm2008 = map(float, t.split())
|
|
1716
|
+
while lon > _180_0: # EasternLon to earth lon
|
|
1717
|
+
lon -= _360_0
|
|
1718
|
+
yield GeoidHeight5Tuple(lat, lon, egm84, egm96, egm2008)
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
__all__ += _ALL_DOCS(_GeoidBase)
|
|
1722
|
+
|
|
1723
|
+
if __name__ == '__main__':
|
|
1724
|
+
|
|
1725
|
+
from pygeodesy.lazily import printf, _sys
|
|
1726
|
+
|
|
1727
|
+
_crop = ()
|
|
1728
|
+
_GeoidEGM = GeoidKarney
|
|
1729
|
+
_kind = 3
|
|
1730
|
+
|
|
1731
|
+
geoids = _sys.argv[1:]
|
|
1732
|
+
while geoids:
|
|
1733
|
+
geoid = geoids.pop(0)
|
|
1734
|
+
|
|
1735
|
+
if '-crop'.startswith(geoid.lower()):
|
|
1736
|
+
_crop = 20, -125, 50, -65 # CONUS
|
|
1737
|
+
|
|
1738
|
+
elif '-karney'.startswith(geoid.lower()):
|
|
1739
|
+
_GeoidEGM = GeoidKarney
|
|
1740
|
+
|
|
1741
|
+
elif '-kind'.startswith(geoid.lower()):
|
|
1742
|
+
_kind = int(geoids.pop(0))
|
|
1743
|
+
|
|
1744
|
+
elif '-pgm'.startswith(geoid.lower()):
|
|
1745
|
+
_GeoidEGM = GeoidPGM
|
|
1746
|
+
|
|
1747
|
+
elif geoid[-4:].lower() in ('.pgm',):
|
|
1748
|
+
g = _GeoidEGM(geoid, crop=_crop, kind=_kind)
|
|
1749
|
+
printf(g.toStr(), nt=1, nl=1)
|
|
1750
|
+
printf(repr(g.pgm), nt=1)
|
|
1751
|
+
# <https://GeographicLib.SourceForge.io/cgi-bin/GeoidEval>:
|
|
1752
|
+
# The height of the EGM96 geoid at Timbuktu
|
|
1753
|
+
# echo 16:46:33N 3:00:34W | GeoidEval
|
|
1754
|
+
# => 28.7068 -0.02e-6 -1.73e-6
|
|
1755
|
+
# The 1st number is the height of the geoid, the 2nd and
|
|
1756
|
+
# 3rd are its slopes in northerly and easterly direction
|
|
1757
|
+
t = 'Timbuktu %s' % (g,)
|
|
1758
|
+
k = {'egm84-15.pgm': '31.2979',
|
|
1759
|
+
'egm96-5.pgm': '28.7067',
|
|
1760
|
+
'egm2008-1.pgm': '28.7880'}.get(g.name.lower(), '28.7880')
|
|
1761
|
+
ll = _MODS.dms.parseDMS2('16:46:33N', '3:00:34W', sep=':')
|
|
1762
|
+
for ll in (ll, (16.776, -3.009),):
|
|
1763
|
+
try:
|
|
1764
|
+
h, ll = g.height(*ll), fstr(ll, prec=6)
|
|
1765
|
+
printf('%s.height(%s): %.4F vs %s', t, ll, h, k)
|
|
1766
|
+
except (GeoidError, RangeError) as x:
|
|
1767
|
+
printf(_COLONSPACE_(t, str(x)))
|
|
1768
|
+
|
|
1769
|
+
elif geoid[-4:].lower() in ('.bin',):
|
|
1770
|
+
g = GeoidG2012B(geoid, kind=_kind)
|
|
1771
|
+
printf(g.toStr())
|
|
1772
|
+
|
|
1773
|
+
else:
|
|
1774
|
+
raise GeoidError(grid=repr(geoid))
|
|
1775
|
+
|
|
1776
|
+
_I = int # PYCHOK unused _I
|
|
1777
|
+
del _intCs # trash ints cache
|
|
1778
|
+
|
|
1779
|
+
# **) MIT License
|
|
1780
|
+
#
|
|
1781
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
1782
|
+
#
|
|
1783
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
1784
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
1785
|
+
# to deal in the Software without restriction, including without limitation
|
|
1786
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
1787
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
1788
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
1789
|
+
#
|
|
1790
|
+
# The above copyright notice and this permission notice shall be included
|
|
1791
|
+
# in all copies or substantial portions of the Software.
|
|
1792
|
+
#
|
|
1793
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
1794
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1795
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
1796
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
1797
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
1798
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
1799
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
|
1800
|
+
|
|
1801
|
+
# <https://GeographicLib.SourceForge.io/cgi-bin/GeoidEval>
|
|
1802
|
+
# _lowerleft = -90, -179, -30.1500 # egm2008-1.pgm
|
|
1803
|
+
# _lowerleft = -90, -179, -29.5350 # egm96-5.pgm
|
|
1804
|
+
# _lowerleft = -90, -179, -29.7120 # egm84-15.pgm
|
|
1805
|
+
|
|
1806
|
+
# _center = 0, 0, 17.2260 # egm2008-1.pgm
|
|
1807
|
+
# _center = 0, 0, 17.1630 # egm96-5.pgm
|
|
1808
|
+
# _center = 0, 0, 18.3296 # egm84-15.pgm
|
|
1809
|
+
|
|
1810
|
+
# _upperright = 90, 180, 14.8980 # egm2008-1.pgm
|
|
1811
|
+
# _upperright = 90, 180, 13.6050 # egm96-5.pgm
|
|
1812
|
+
# _upperright = 90, 180, 13.0980 # egm84-15.pgm
|
|
1813
|
+
|
|
1814
|
+
|
|
1815
|
+
# % python3 -m pygeodesy.geoids [-Karney] ../testGeoids/egm*.pgm
|
|
1816
|
+
#
|
|
1817
|
+
# 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)
|
|
1818
|
+
#
|
|
1819
|
+
# _PGM('../testGeoids/egm2008-1.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-31 06:54:00', Description='WGS84 EGM2008, 1-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.025, MaxCubicError=0.003, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.001, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008', Vertical_Datum='WGS84'
|
|
1820
|
+
#
|
|
1821
|
+
# Timbuktu GeoidKarney('egm2008-1.pgm').height(16.775833, -3.009444): 28.7881 vs 28.7880
|
|
1822
|
+
# Timbuktu GeoidKarney('egm2008-1.pgm').height(16.776, -3.009): 28.7880 vs 28.7880
|
|
1823
|
+
#
|
|
1824
|
+
# GeoidKarney('egm84-15.pgm'): lowerleft(-90.0, -180.0, -29.712), upperright(90.0, 180.0, 13.098), center(0.0, 0.0, 18.33), highest(-4.5, 148.75, 81.33), lowest(4.75, 79.25, -107.34)
|
|
1825
|
+
#
|
|
1826
|
+
# _PGM('../testGeoids/egm84-15.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-29 18:45:02', Description='WGS84 EGM84, 15-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.413, MaxCubicError=0.02, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.018, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/wgs84_180/wgs84_180.html', Vertical_Datum='WGS84'
|
|
1827
|
+
#
|
|
1828
|
+
# Timbuktu GeoidKarney('egm84-15.pgm').height(16.775833, -3.009444): 31.2983 vs 31.2979
|
|
1829
|
+
# Timbuktu GeoidKarney('egm84-15.pgm').height(16.776, -3.009): 31.2979 vs 31.2979
|
|
1830
|
+
#
|
|
1831
|
+
# GeoidKarney('egm96-5.pgm'): lowerleft(-90.0, -180.0, -29.535), upperright(90.0, 180.0, 13.605), center(0.0, 0.0, 17.163), highest(-8.167, 147.25, 85.422), lowest(4.667, 78.833, -107.043)
|
|
1832
|
+
#
|
|
1833
|
+
# _PGM('../testGeoids/egm96-5.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-29 18:45:03', Description='WGS84 EGM96, 5-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.14, MaxCubicError=0.003, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.005, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/egm96.html', Vertical_Datum='WGS84'
|
|
1834
|
+
#
|
|
1835
|
+
# Timbuktu GeoidKarney('egm96-5.pgm').height(16.775833, -3.009444): 28.7068 vs 28.7067
|
|
1836
|
+
# Timbuktu GeoidKarney('egm96-5.pgm').height(16.776, -3.009): 28.7067 vs 28.7067
|
|
1837
|
+
|
|
1838
|
+
|
|
1839
|
+
# % python3 -m pygeodesy.geoids -Karney ../testGeoids/egm*.pgm
|
|
1840
|
+
#
|
|
1841
|
+
# 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)
|
|
1842
|
+
#
|
|
1843
|
+
# _PGM('../testGeoids/egm2008-1.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-31 06:54:00', Description='WGS84 EGM2008, 1-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.025, MaxCubicError=0.003, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.001, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008', Vertical_Datum='WGS84'
|
|
1844
|
+
#
|
|
1845
|
+
# Timbuktu GeoidKarney('egm2008-1.pgm').height(16.775833, -3.009444): 28.7881 vs 28.7880
|
|
1846
|
+
# Timbuktu GeoidKarney('egm2008-1.pgm').height(16.776, -3.009): 28.7880 vs 28.7880
|
|
1847
|
+
#
|
|
1848
|
+
# GeoidKarney('egm84-15.pgm'): lowerleft(-90.0, -180.0, -29.712), upperright(90.0, 180.0, 13.098), center(0.0, 0.0, 18.33), highest(-4.5, 148.75, 81.33), lowest(4.75, 79.25, -107.34)
|
|
1849
|
+
#
|
|
1850
|
+
# _PGM('../testGeoids/egm84-15.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-29 18:45:02', Description='WGS84 EGM84, 15-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.413, MaxCubicError=0.02, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.018, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/wgs84_180/wgs84_180.html', Vertical_Datum='WGS84'
|
|
1851
|
+
#
|
|
1852
|
+
# Timbuktu GeoidKarney('egm84-15.pgm').height(16.775833, -3.009444): 31.2983 vs 31.2979
|
|
1853
|
+
# Timbuktu GeoidKarney('egm84-15.pgm').height(16.776, -3.009): 31.2979 vs 31.2979
|
|
1854
|
+
#
|
|
1855
|
+
# GeoidKarney('egm96-5.pgm'): lowerleft(-90.0, -180.0, -29.535), upperright(90.0, 180.0, 13.605), center(0.0, 0.0, 17.163), highest(-8.167, 147.25, 85.422), lowest(4.667, 78.833, -107.043)
|
|
1856
|
+
#
|
|
1857
|
+
# _PGM('../testGeoids/egm96-5.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-29 18:45:03', Description='WGS84 EGM96, 5-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.14, MaxCubicError=0.003, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.005, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/egm96.html', Vertical_Datum='WGS84'
|
|
1858
|
+
#
|
|
1859
|
+
# Timbuktu GeoidKarney('egm96-5.pgm').height(16.775833, -3.009444): 28.7068 vs 28.7067
|
|
1860
|
+
# Timbuktu GeoidKarney('egm96-5.pgm').height(16.776, -3.009): 28.7067 vs 28.7067
|
|
1861
|
+
|
|
1862
|
+
|
|
1863
|
+
# % python2 -m pygeodesy.geoids -PGM ../testGeoids/egm*.pgm
|
|
1864
|
+
#
|
|
1865
|
+
# GeoidPGM('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, -32.633, 85.839), lowest(4.683, -101.25, -106.911)
|
|
1866
|
+
#
|
|
1867
|
+
# _PGM('../testGeoids/egm2008-1.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-31 06:54:00', Description='WGS84 EGM2008, 1-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.025, MaxCubicError=0.003, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.001, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008', Vertical_Datum='WGS84'
|
|
1868
|
+
#
|
|
1869
|
+
# Timbuktu GeoidPGM('egm2008-1.pgm').height(16.775833, -3.009444): 28.7881 vs 28.7880
|
|
1870
|
+
# Timbuktu GeoidPGM('egm2008-1.pgm').height(16.776, -3.009): 28.7880 vs 28.7880
|
|
1871
|
+
#
|
|
1872
|
+
# GeoidPGM('egm84-15.pgm'): lowerleft(-90.0, -180.0, -29.712), upperright(90.0, 180.0, 13.098), center(0.0, 0.0, 18.33), highest(-4.5, -31.25, 81.33), lowest(4.75, -100.75, -107.34)
|
|
1873
|
+
#
|
|
1874
|
+
# _PGM('../testGeoids/egm84-15.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-29 18:45:02', Description='WGS84 EGM84, 15-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.413, MaxCubicError=0.02, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.018, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/wgs84_180/wgs84_180.html', Vertical_Datum='WGS84'
|
|
1875
|
+
#
|
|
1876
|
+
# Timbuktu GeoidPGM('egm84-15.pgm').height(16.775833, -3.009444): 31.2979 vs 31.2979
|
|
1877
|
+
# Timbuktu GeoidPGM('egm84-15.pgm').height(16.776, -3.009): 31.2975 vs 31.2979
|
|
1878
|
+
#
|
|
1879
|
+
# GeoidPGM('egm96-5.pgm'): lowerleft(-90.0, -180.0, -29.535), upperright(90.0, 180.0, 13.605), center(0.0, -0.0, 17.179), highest(-8.167, -32.75, 85.422), lowest(4.667, -101.167, -107.043)
|
|
1880
|
+
#
|
|
1881
|
+
# _PGM('../testGeoids/egm96-5.pgm'): AREA_OR_POINT='Point', DateTime='2009-08-29 18:45:03', Description='WGS84 EGM96, 5-minute grid', Geoid='file in PGM format for the GeographicLib::Geoid class', MaxBilinearError=0.14, MaxCubicError=0.003, Offset=-108.0, Origin=LatLon2Tuple(lat=90.0, lon=0.0), Pixel=65535, RMSBilinearError=0.005, RMSCubicError=0.001, Scale=0.003, URL='http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/egm96.html', Vertical_Datum='WGS84'
|
|
1882
|
+
#
|
|
1883
|
+
# Timbuktu GeoidPGM('egm96-5.pgm').height(16.775833, -3.009444): 28.7065 vs 28.7067
|
|
1884
|
+
# Timbuktu GeoidPGM('egm96-5.pgm').height(16.776, -3.009): 28.7064 vs 28.7067
|