pyTMD 2.2.5__tar.gz → 2.2.6__tar.gz
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.
- {pytmd-2.2.5 → pytmd-2.2.6}/CITATION.cff +2 -2
- {pytmd-2.2.5 → pytmd-2.2.6}/PKG-INFO +1 -1
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/astro.py +3 -3
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/compute.py +14 -7
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/crs.py +1 -1
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/GOT.py +8 -8
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/model.py +1 -1
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/predict.py +20 -12
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/utilities.py +2 -598
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/PKG-INFO +1 -1
- {pytmd-2.2.5 → pytmd-2.2.6}/scripts/aviso_fes_tides.py +16 -4
- {pytmd-2.2.5 → pytmd-2.2.6}/scripts/gsfc_got_tides.py +6 -6
- pytmd-2.2.6/version.txt +1 -0
- pytmd-2.2.5/version.txt +0 -1
- {pytmd-2.2.5 → pytmd-2.2.6}/.gitignore +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/CODE_OF_CONDUCT.rst +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/CONTRIBUTORS.rst +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/LICENSE +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/MANIFEST.in +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/README.rst +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/AVISO.json +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/ESR.json +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/GSFC.json +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/README.rst +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/TPXO.json +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/_model_to_database.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/_providers_to_database.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/_update_providers.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/providers/providers.json +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/__init__.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/arguments.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/compute_tide_corrections.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/ce1973_tab1.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/ct1971_tab5.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/d1921_tab.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/database.json +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/doodson.json +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/opoleloadcoefcmcor.txt.gz +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/tab5.2e.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/tab5.3a.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/tab5.3b.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/ellipse.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/interpolate.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/ATLAS.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/FES.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/IERS.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/OTIS.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/__init__.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/constituents.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/math.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/solve/__init__.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/solve/constants.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/spatial.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/tools.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/version.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/SOURCES.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/dependency_links.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/requires.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/top_level.txt +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/pyproject.toml +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/scripts/arcticdata_tides.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/scripts/reduce_OTIS_files.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/scripts/verify_box_tpxo.py +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/setup.cfg +0 -0
- {pytmd-2.2.5 → pytmd-2.2.6}/setup.py +0 -0
|
@@ -36,8 +36,8 @@ url: 'https://pytmd.readthedocs.io'
|
|
|
36
36
|
repository: 'https://pypi.org/project/pyTMD'
|
|
37
37
|
repository-artifact: 'https://anaconda.org/conda-forge/pytmd'
|
|
38
38
|
doi: "10.5281/zenodo.5555395"
|
|
39
|
-
version: "2.2.
|
|
40
|
-
date-released: "2025-
|
|
39
|
+
version: "2.2.6"
|
|
40
|
+
date-released: "2025-07-24"
|
|
41
41
|
keywords:
|
|
42
42
|
- Ocean Tides
|
|
43
43
|
- Load Tides
|
|
@@ -49,7 +49,7 @@ UPDATE HISTORY:
|
|
|
49
49
|
Updated 08/2020: change time variable names to not overwrite functions
|
|
50
50
|
Updated 07/2020: added function docstrings
|
|
51
51
|
Updated 07/2018: added option ASTRO5 to use coefficients from Richard Ray
|
|
52
|
-
for use with the
|
|
52
|
+
for use with the Goddard Ocean Tides (GOT) model
|
|
53
53
|
added longitude of solar perigee (Ps) as an additional output
|
|
54
54
|
Updated 09/2017: added option MEEUS to use additional coefficients
|
|
55
55
|
from Meeus Astronomical Algorithms to calculate mean longitudes
|
|
@@ -503,7 +503,7 @@ def mean_obliquity(MJD: np.ndarray):
|
|
|
503
503
|
return atr*polynomial_sum(epsilon0, T)
|
|
504
504
|
|
|
505
505
|
def equation_of_time(MJD: np.ndarray):
|
|
506
|
-
"""Approximate
|
|
506
|
+
"""Approximate calculation of the difference between apparent and
|
|
507
507
|
mean solar times :cite:p:`Meeus:1991vh,Urban:2013vl`
|
|
508
508
|
|
|
509
509
|
Parameters
|
|
@@ -975,7 +975,7 @@ def _icrs_rotation_matrix(
|
|
|
975
975
|
include_polar_motion: bool = True
|
|
976
976
|
):
|
|
977
977
|
"""
|
|
978
|
-
Rotation matrix for
|
|
978
|
+
Rotation matrix for transforming from the
|
|
979
979
|
International Celestial Reference System (ICRS)
|
|
980
980
|
to the International Terrestrial Reference System (ITRS)
|
|
981
981
|
:cite:p:`Capitaine:2003fx,Capitaine:2003fw,Petit:2010tp`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
u"""
|
|
3
3
|
compute.py
|
|
4
|
-
Written by Tyler Sutterley (
|
|
4
|
+
Written by Tyler Sutterley (07/2025)
|
|
5
5
|
Calculates tidal elevations for correcting elevation or imagery data
|
|
6
6
|
Calculates tidal currents at locations and times
|
|
7
7
|
|
|
@@ -62,6 +62,7 @@ PROGRAM DEPENDENCIES:
|
|
|
62
62
|
interpolate.py: interpolation routines for spatial data
|
|
63
63
|
|
|
64
64
|
UPDATE HISTORY:
|
|
65
|
+
Updated 07/2025: mask mean pole values prior to valid epoch of convention
|
|
65
66
|
Updated 05/2025: added option to select constituents to read from model
|
|
66
67
|
Updated 12/2024: moved check points function as compute.tide_masks
|
|
67
68
|
Updated 11/2024: expose buffer distance for cropping tide model data
|
|
@@ -1064,9 +1065,10 @@ def LPT_displacements(
|
|
|
1064
1065
|
)
|
|
1065
1066
|
# calculate components of load pole tides
|
|
1066
1067
|
S = np.einsum('ti...,tji...->tj...', dxi, R)
|
|
1068
|
+
smask = np.reshape(np.any(dxi.mask, axis=1), (ny,nx))
|
|
1067
1069
|
# reshape to output dimensions
|
|
1068
1070
|
Srad.data[:,:,i] = np.reshape(S[:,2], (ny,nx))
|
|
1069
|
-
Srad.mask[:,:,i] = np.isnan(Srad.data[:,:,i])
|
|
1071
|
+
Srad.mask[:,:,i] = np.isnan(Srad.data[:,:,i]) | smask
|
|
1070
1072
|
elif (TYPE == 'drift'):
|
|
1071
1073
|
# calculate load pole tides in cartesian coordinates
|
|
1072
1074
|
XYZ = np.c_[X, Y, Z]
|
|
@@ -1080,10 +1082,11 @@ def LPT_displacements(
|
|
|
1080
1082
|
)
|
|
1081
1083
|
# calculate components of load pole tides
|
|
1082
1084
|
S = np.einsum('ti...,tji...->tj...', dxi, R)
|
|
1085
|
+
smask = np.any(dxi.mask, axis=1)
|
|
1083
1086
|
# reshape to output dimensions
|
|
1084
1087
|
Srad = np.ma.zeros((nt), fill_value=FILL_VALUE)
|
|
1085
1088
|
Srad.data[:] = S[:,2].copy()
|
|
1086
|
-
Srad.mask = np.isnan(Srad.data)
|
|
1089
|
+
Srad.mask = np.isnan(Srad.data) | smask
|
|
1087
1090
|
elif (TYPE == 'time series'):
|
|
1088
1091
|
nstation = len(x)
|
|
1089
1092
|
Srad = np.ma.zeros((nstation,nt), fill_value=FILL_VALUE)
|
|
@@ -1102,9 +1105,10 @@ def LPT_displacements(
|
|
|
1102
1105
|
)
|
|
1103
1106
|
# calculate components of load pole tides
|
|
1104
1107
|
S = np.einsum('ti...,ji...->tj...', dxi, R[s,:,:])
|
|
1108
|
+
smask = np.any(dxi.mask, axis=1)
|
|
1105
1109
|
# reshape to output dimensions
|
|
1106
1110
|
Srad.data[s,:] = S[:,2].copy()
|
|
1107
|
-
Srad.mask[s,:] = np.isnan(Srad.data[s,:])
|
|
1111
|
+
Srad.mask[s,:] = np.isnan(Srad.data[s,:]) | smask
|
|
1108
1112
|
|
|
1109
1113
|
# replace invalid data with fill values
|
|
1110
1114
|
Srad.data[Srad.mask] = Srad.fill_value
|
|
@@ -1288,9 +1292,10 @@ def OPT_displacements(
|
|
|
1288
1292
|
)
|
|
1289
1293
|
# calculate components of ocean pole tides
|
|
1290
1294
|
U = np.einsum('ti...,tji...->tj...', dxi, Rinv)
|
|
1295
|
+
umask = np.reshape(np.any(dxi.mask, axis=1), (ny,nx))
|
|
1291
1296
|
# reshape to output dimensions
|
|
1292
1297
|
Urad.data[:,:,i] = np.reshape(U[:,2], (ny,nx))
|
|
1293
|
-
Urad.mask[:,:,i] = np.isnan(Urad.data[:,:,i])
|
|
1298
|
+
Urad.mask[:,:,i] = np.isnan(Urad.data[:,:,i]) | umask
|
|
1294
1299
|
elif (TYPE == 'drift'):
|
|
1295
1300
|
# calculate ocean pole tides in cartesian coordinates
|
|
1296
1301
|
XYZ = np.c_[X, Y, Z]
|
|
@@ -1306,10 +1311,11 @@ def OPT_displacements(
|
|
|
1306
1311
|
)
|
|
1307
1312
|
# calculate components of ocean pole tides
|
|
1308
1313
|
U = np.einsum('ti...,tji...->tj...', dxi, Rinv)
|
|
1314
|
+
umask = np.any(dxi.mask, axis=1)
|
|
1309
1315
|
# convert to masked array
|
|
1310
1316
|
Urad = np.ma.zeros((nt), fill_value=FILL_VALUE)
|
|
1311
1317
|
Urad.data[:] = U[:,2].copy()
|
|
1312
|
-
Urad.mask = np.isnan(Urad.data)
|
|
1318
|
+
Urad.mask = np.isnan(Urad.data) | umask
|
|
1313
1319
|
elif (TYPE == 'time series'):
|
|
1314
1320
|
nstation = len(x)
|
|
1315
1321
|
Urad = np.ma.zeros((nstation,nt), fill_value=FILL_VALUE)
|
|
@@ -1331,9 +1337,10 @@ def OPT_displacements(
|
|
|
1331
1337
|
)
|
|
1332
1338
|
# calculate components of ocean pole tides
|
|
1333
1339
|
U = np.einsum('ti...,ji...->tj...', dxi, Rinv[s,:,:])
|
|
1340
|
+
umask = np.any(dxi.mask, axis=1)
|
|
1334
1341
|
# reshape to output dimensions
|
|
1335
1342
|
Urad.data[s,:] = U[:,2].copy()
|
|
1336
|
-
Urad.mask[s,:] = np.isnan(Urad.data[s,:])
|
|
1343
|
+
Urad.mask[s,:] = np.isnan(Urad.data[s,:]) | umask
|
|
1337
1344
|
|
|
1338
1345
|
# replace invalid data with fill values
|
|
1339
1346
|
Urad.data[Urad.mask] = Urad.fill_value
|
|
@@ -189,7 +189,7 @@ class crs:
|
|
|
189
189
|
PROJECTION: int, str or dict
|
|
190
190
|
Coordinate Reference System
|
|
191
191
|
"""
|
|
192
|
-
# coordinate reference system
|
|
192
|
+
# coordinate reference system dictionary
|
|
193
193
|
try:
|
|
194
194
|
CRS = pyproj.CRS.from_user_input(PROJECTION)
|
|
195
195
|
except (ValueError, pyproj.exceptions.CRSError):
|
|
@@ -3,10 +3,10 @@ u"""
|
|
|
3
3
|
GOT.py
|
|
4
4
|
Written by Tyler Sutterley (11/2024)
|
|
5
5
|
|
|
6
|
-
Reads files for Richard Ray's
|
|
7
|
-
calculations to run the tide program
|
|
8
|
-
Includes functions to extract tidal harmonic constants out of a tidal
|
|
9
|
-
given locations
|
|
6
|
+
Reads files for Richard Ray's Goddard Ocean Tide (GOT) models and makes
|
|
7
|
+
initial calculations to run the tide program
|
|
8
|
+
Includes functions to extract tidal harmonic constants out of a tidal
|
|
9
|
+
model for given locations
|
|
10
10
|
|
|
11
11
|
INPUTS:
|
|
12
12
|
ilon: longitude to interpolate
|
|
@@ -123,7 +123,7 @@ def extract_constants(
|
|
|
123
123
|
**kwargs
|
|
124
124
|
):
|
|
125
125
|
"""
|
|
126
|
-
Reads files for Richard Ray's
|
|
126
|
+
Reads files for Richard Ray's Goddard Ocean Tide (GOT) models
|
|
127
127
|
|
|
128
128
|
Makes initial calculations to run the tide program
|
|
129
129
|
|
|
@@ -315,7 +315,7 @@ def read_constants(
|
|
|
315
315
|
**kwargs
|
|
316
316
|
):
|
|
317
317
|
"""
|
|
318
|
-
Reads files for Richard Ray's
|
|
318
|
+
Reads files for Richard Ray's Goddard Ocean Tide (GOT) models
|
|
319
319
|
|
|
320
320
|
Parameters
|
|
321
321
|
----------
|
|
@@ -540,7 +540,7 @@ def read_ascii_file(
|
|
|
540
540
|
**kwargs
|
|
541
541
|
):
|
|
542
542
|
"""
|
|
543
|
-
Read Richard Ray's
|
|
543
|
+
Read Richard Ray's Goddard Ocean Tide (GOT) model file
|
|
544
544
|
|
|
545
545
|
Parameters
|
|
546
546
|
----------
|
|
@@ -625,7 +625,7 @@ def read_netcdf_file(
|
|
|
625
625
|
**kwargs
|
|
626
626
|
):
|
|
627
627
|
"""
|
|
628
|
-
Read Richard Ray's
|
|
628
|
+
Read Richard Ray's Goddard Ocean Tide (GOT) netCDF4 model file
|
|
629
629
|
|
|
630
630
|
Parameters
|
|
631
631
|
----------
|
|
@@ -54,7 +54,7 @@ UPDATE HISTORY:
|
|
|
54
54
|
Updated 12/2022: moved to io and added deprecation warning to old
|
|
55
55
|
Updated 11/2022: use f-strings for formatting verbose or ascii output
|
|
56
56
|
Updated 06/2022: added Greenland 1km model (Gr1kmTM) to list of models
|
|
57
|
-
updated citation url for
|
|
57
|
+
updated citation url for Goddard Ocean Tide (GOT) models
|
|
58
58
|
Updated 05/2022: added ESR CATS2022 to list of models
|
|
59
59
|
added attribute for flexure fields being available for model
|
|
60
60
|
Updated 04/2022: updated docstrings to numpy documentation format
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
u"""
|
|
3
3
|
predict.py
|
|
4
|
-
Written by Tyler Sutterley (
|
|
4
|
+
Written by Tyler Sutterley (07/2025)
|
|
5
5
|
Prediction routines for ocean, load, equilibrium and solid earth tides
|
|
6
6
|
|
|
7
7
|
REFERENCES:
|
|
@@ -22,6 +22,9 @@ PROGRAM DEPENDENCIES:
|
|
|
22
22
|
spatial.py: utilities for working with geospatial data
|
|
23
23
|
|
|
24
24
|
UPDATE HISTORY:
|
|
25
|
+
Updated 07/2025: revert free-to-mean conversion to April 2023 version
|
|
26
|
+
revert load pole tide to IERS 1996 convention definitions
|
|
27
|
+
mask mean pole values prior to valid epoch of convention
|
|
25
28
|
Updated 05/2025: pass keyword arguments to nodal corrections functions
|
|
26
29
|
Updated 03/2025: changed argument for method calculating mean longitudes
|
|
27
30
|
Updated 02/2025: verify dimensions of harmonic constants
|
|
@@ -169,7 +172,7 @@ def map(t: float | np.ndarray,
|
|
|
169
172
|
# return the tidal elevation after removing singleton dimensions
|
|
170
173
|
return np.squeeze(ht)
|
|
171
174
|
|
|
172
|
-
# PURPOSE: Predict tides at drift
|
|
175
|
+
# PURPOSE: Predict tides at drift buoys or altimetry points
|
|
173
176
|
def drift(t: float | np.ndarray,
|
|
174
177
|
hc: np.ndarray,
|
|
175
178
|
constituents: list | np.ndarray,
|
|
@@ -1116,7 +1119,7 @@ def load_pole_tide(
|
|
|
1116
1119
|
# degrees and arcseconds to radians
|
|
1117
1120
|
dtr = np.pi/180.0
|
|
1118
1121
|
atr = np.pi/648000.0
|
|
1119
|
-
# convert time to
|
|
1122
|
+
# convert time to Terrestrial Time (TT)
|
|
1120
1123
|
tt = t + _jd_tide + deltat
|
|
1121
1124
|
# convert time to Modified Julian Days (MJD)
|
|
1122
1125
|
MJD = tt - _jd_mjd
|
|
@@ -1156,9 +1159,9 @@ def load_pole_tide(
|
|
|
1156
1159
|
# calculate pole tide displacements (meters)
|
|
1157
1160
|
S = np.zeros((n, 3))
|
|
1158
1161
|
# pole tide displacements in latitude, longitude, and radial directions
|
|
1159
|
-
S[:,0] = dfactor[:,0]*np.cos(2.0*theta)*(mx*np.cos(phi)
|
|
1160
|
-
S[:,1] = dfactor[:,1]*np.cos(theta)*(mx*np.sin(phi)
|
|
1161
|
-
S[:,2] = dfactor[:,2]*np.sin(2.0*theta)*(mx*np.cos(phi)
|
|
1162
|
+
S[:,0] = dfactor[:,0]*np.cos(2.0*theta)*(mx*np.cos(phi) - my*np.sin(phi))
|
|
1163
|
+
S[:,1] = dfactor[:,1]*np.cos(theta)*(mx*np.sin(phi) + my*np.cos(phi))
|
|
1164
|
+
S[:,2] = dfactor[:,2]*np.sin(2.0*theta)*(mx*np.cos(phi) - my*np.sin(phi))
|
|
1162
1165
|
|
|
1163
1166
|
# rotation matrix
|
|
1164
1167
|
R = np.zeros((3, 3, n))
|
|
@@ -1171,7 +1174,10 @@ def load_pole_tide(
|
|
|
1171
1174
|
R[2,0,:] = -np.sin(theta)
|
|
1172
1175
|
R[2,2,:] = np.cos(theta)
|
|
1173
1176
|
# rotate displacements to ECEF coordinates
|
|
1174
|
-
dxt = np.
|
|
1177
|
+
dxt = np.ma.zeros((n, 3))
|
|
1178
|
+
dxt[:,:] = np.einsum('ti...,jit...->tj...', S, R)
|
|
1179
|
+
# use mask from mean pole estimates
|
|
1180
|
+
dxt.mask = np.broadcast_to(np.logical_not(fl[:,None]), (n,3))
|
|
1175
1181
|
|
|
1176
1182
|
# return the pole tide displacements
|
|
1177
1183
|
# in Cartesian coordinates
|
|
@@ -1233,7 +1239,7 @@ def ocean_pole_tide(
|
|
|
1233
1239
|
# degrees and arcseconds to radians
|
|
1234
1240
|
dtr = np.pi/180.0
|
|
1235
1241
|
atr = np.pi/648000.0
|
|
1236
|
-
# convert time to
|
|
1242
|
+
# convert time to Terrestrial Time (TT)
|
|
1237
1243
|
tt = t + _jd_tide + deltat
|
|
1238
1244
|
# convert time to Modified Julian Days (MJD)
|
|
1239
1245
|
MJD = tt - _jd_mjd
|
|
@@ -1271,7 +1277,9 @@ def ocean_pole_tide(
|
|
|
1271
1277
|
# number of points
|
|
1272
1278
|
n = np.maximum(len(time_decimal), len(theta))
|
|
1273
1279
|
# calculate ocean pole tide displacements (meters)
|
|
1274
|
-
dxt = np.zeros((n, 3))
|
|
1280
|
+
dxt = np.ma.zeros((n, 3))
|
|
1281
|
+
# use mask from mean pole estimates
|
|
1282
|
+
dxt.mask = np.broadcast_to(np.logical_not(fl[:,None]), (n,3))
|
|
1275
1283
|
for i in range(3):
|
|
1276
1284
|
dxt[:,i] = K*atr*np.real(
|
|
1277
1285
|
(mx*g2.real + my*g2.imag)*UXYZ.real[:,i] +
|
|
@@ -1757,8 +1765,8 @@ def _free_to_mean(
|
|
|
1757
1765
|
dr = dR0*(3.0/2.0*sinphi**2 - 1.0/2.0)
|
|
1758
1766
|
dn = 2.0*dN0*cosphi*sinphi
|
|
1759
1767
|
# compute as an additive correction (Mathews et al. 1997)
|
|
1760
|
-
DX = dr*cosla*cosphi
|
|
1761
|
-
DY = dr*sinla*cosphi
|
|
1762
|
-
DZ = dr*sinphi
|
|
1768
|
+
DX = -dr*cosla*cosphi + dn*cosla*sinphi
|
|
1769
|
+
DY = -dr*sinla*cosphi + dn*sinla*sinphi
|
|
1770
|
+
DZ = -dr*sinphi - dn*cosphi
|
|
1763
1771
|
# return the corrections
|
|
1764
1772
|
return np.c_[DX, DY, DZ]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
u"""
|
|
3
3
|
utilities.py
|
|
4
|
-
Written by Tyler Sutterley (
|
|
4
|
+
Written by Tyler Sutterley (07/2025)
|
|
5
5
|
Download and management utilities for syncing time and auxiliary files
|
|
6
6
|
|
|
7
7
|
PYTHON DEPENDENCIES:
|
|
@@ -9,6 +9,7 @@ PYTHON DEPENDENCIES:
|
|
|
9
9
|
https://pypi.python.org/pypi/lxml
|
|
10
10
|
|
|
11
11
|
UPDATE HISTORY:
|
|
12
|
+
Updated 07/2025: removed (now) unused functions that were moved to timescale
|
|
12
13
|
Updated 01/2025: added function to list a directory from the UHSLC
|
|
13
14
|
Updated 08/2024: generalize hash function to use any available algorithm
|
|
14
15
|
Updated 07/2024: added function to parse JSON responses from https
|
|
@@ -51,17 +52,13 @@ import re
|
|
|
51
52
|
import io
|
|
52
53
|
import ssl
|
|
53
54
|
import json
|
|
54
|
-
import netrc
|
|
55
55
|
import ftplib
|
|
56
56
|
import shutil
|
|
57
|
-
import base64
|
|
58
57
|
import socket
|
|
59
|
-
import getpass
|
|
60
58
|
import inspect
|
|
61
59
|
import hashlib
|
|
62
60
|
import logging
|
|
63
61
|
import pathlib
|
|
64
|
-
import builtins
|
|
65
62
|
import warnings
|
|
66
63
|
import importlib
|
|
67
64
|
import posixpath
|
|
@@ -88,11 +85,8 @@ __all__ = [
|
|
|
88
85
|
"url_split",
|
|
89
86
|
"convert_arg_line_to_args",
|
|
90
87
|
"build_logger",
|
|
91
|
-
"roman_to_int",
|
|
92
88
|
"get_unix_time",
|
|
93
|
-
"isoformat",
|
|
94
89
|
"even",
|
|
95
|
-
"ceil",
|
|
96
90
|
"copy",
|
|
97
91
|
"check_ftp_connection",
|
|
98
92
|
"ftp_list",
|
|
@@ -104,14 +98,6 @@ __all__ = [
|
|
|
104
98
|
"http_list",
|
|
105
99
|
"from_http",
|
|
106
100
|
"from_json",
|
|
107
|
-
"attempt_login",
|
|
108
|
-
"build_opener",
|
|
109
|
-
"get_token",
|
|
110
|
-
"list_tokens",
|
|
111
|
-
"revoke_token",
|
|
112
|
-
"check_credentials",
|
|
113
|
-
"cddis_list",
|
|
114
|
-
"from_cddis",
|
|
115
101
|
"iers_list",
|
|
116
102
|
"from_jpl_ssd",
|
|
117
103
|
"uhslc_list"
|
|
@@ -358,30 +344,6 @@ def build_logger(name: str, **kwargs):
|
|
|
358
344
|
logger.addHandler(handler)
|
|
359
345
|
return logger
|
|
360
346
|
|
|
361
|
-
# PURPOSE: convert Roman numerals to (Arabic) integers
|
|
362
|
-
def roman_to_int(roman: str):
|
|
363
|
-
"""
|
|
364
|
-
Converts a string from Roman numerals into an integer (Arabic)
|
|
365
|
-
|
|
366
|
-
Parameters
|
|
367
|
-
----------
|
|
368
|
-
roman: str
|
|
369
|
-
Roman numeral string
|
|
370
|
-
"""
|
|
371
|
-
# mapping between Roman and Arabic numerals
|
|
372
|
-
roman_map = {'i':1, 'v':5, 'x':10, 'l':50, 'c':100, 'd':500, 'm':1000}
|
|
373
|
-
# verify case
|
|
374
|
-
roman = roman.lower()
|
|
375
|
-
output = 0
|
|
376
|
-
# iterate through roman numerals in string and calculate total
|
|
377
|
-
for i,s in enumerate(roman):
|
|
378
|
-
if (i > 0) and (roman_map[s] > roman_map[roman[i-1]]):
|
|
379
|
-
output += roman_map[s] - 2*roman_map[roman[i-1]]
|
|
380
|
-
else:
|
|
381
|
-
output += roman_map[s]
|
|
382
|
-
# return the integer value
|
|
383
|
-
return output
|
|
384
|
-
|
|
385
347
|
# PURPOSE: returns the Unix timestamp value for a formatted date string
|
|
386
348
|
def get_unix_time(
|
|
387
349
|
time_string: str,
|
|
@@ -411,24 +373,6 @@ def get_unix_time(
|
|
|
411
373
|
else:
|
|
412
374
|
return parsed_time.timestamp()
|
|
413
375
|
|
|
414
|
-
# PURPOSE: output a time string in isoformat
|
|
415
|
-
def isoformat(time_string: str):
|
|
416
|
-
"""
|
|
417
|
-
Reformat a date string to ISO formatting
|
|
418
|
-
|
|
419
|
-
Parameters
|
|
420
|
-
----------
|
|
421
|
-
time_string: str
|
|
422
|
-
formatted time string to parse
|
|
423
|
-
"""
|
|
424
|
-
# try parsing with dateutil
|
|
425
|
-
try:
|
|
426
|
-
parsed_time = dateutil.parser.parse(time_string.rstrip())
|
|
427
|
-
except (TypeError, ValueError):
|
|
428
|
-
return None
|
|
429
|
-
else:
|
|
430
|
-
return parsed_time.isoformat()
|
|
431
|
-
|
|
432
376
|
# PURPOSE: rounds a number to an even number less than or equal to original
|
|
433
377
|
def even(value: float):
|
|
434
378
|
"""
|
|
@@ -441,18 +385,6 @@ def even(value: float):
|
|
|
441
385
|
"""
|
|
442
386
|
return 2*int(value//2)
|
|
443
387
|
|
|
444
|
-
# PURPOSE: rounds a number upward to its nearest integer
|
|
445
|
-
def ceil(value: float):
|
|
446
|
-
"""
|
|
447
|
-
Rounds a number upward to its nearest integer
|
|
448
|
-
|
|
449
|
-
Parameters
|
|
450
|
-
----------
|
|
451
|
-
value: float
|
|
452
|
-
number to be rounded upward
|
|
453
|
-
"""
|
|
454
|
-
return -int(-value//1)
|
|
455
|
-
|
|
456
388
|
# PURPOSE: make a copy of a file with all system information
|
|
457
389
|
def copy(
|
|
458
390
|
source: str | pathlib.Path,
|
|
@@ -940,534 +872,6 @@ def from_json(
|
|
|
940
872
|
# load JSON response
|
|
941
873
|
return json.loads(response.read())
|
|
942
874
|
|
|
943
|
-
# PURPOSE: attempt to build an opener with netrc
|
|
944
|
-
def attempt_login(
|
|
945
|
-
urs: str,
|
|
946
|
-
context=_default_ssl_context,
|
|
947
|
-
password_manager: bool = True,
|
|
948
|
-
get_ca_certs: bool = True,
|
|
949
|
-
redirect: bool = True,
|
|
950
|
-
authorization_header: bool = False,
|
|
951
|
-
**kwargs
|
|
952
|
-
):
|
|
953
|
-
"""
|
|
954
|
-
attempt to build a urllib opener for NASA Earthdata
|
|
955
|
-
|
|
956
|
-
Parameters
|
|
957
|
-
----------
|
|
958
|
-
urs: str
|
|
959
|
-
Earthdata login URS 3 host
|
|
960
|
-
context: obj, default pyTMD.utilities._default_ssl_context
|
|
961
|
-
SSL context for ``urllib`` opener object
|
|
962
|
-
password_manager: bool, default True
|
|
963
|
-
Create password manager context using default realm
|
|
964
|
-
get_ca_certs: bool, default True
|
|
965
|
-
Get list of loaded “certification authority” certificates
|
|
966
|
-
redirect: bool, default True
|
|
967
|
-
Create redirect handler object
|
|
968
|
-
authorization_header: bool, default False
|
|
969
|
-
Add base64 encoded authorization header to opener
|
|
970
|
-
username: str, default from environmental variable
|
|
971
|
-
NASA Earthdata username
|
|
972
|
-
password: str, default from environmental variable
|
|
973
|
-
NASA Earthdata password
|
|
974
|
-
retries: int, default 5
|
|
975
|
-
number of retry attempts
|
|
976
|
-
netrc: str, default ~/.netrc
|
|
977
|
-
path to .netrc file for authentication
|
|
978
|
-
|
|
979
|
-
Returns
|
|
980
|
-
-------
|
|
981
|
-
opener: obj
|
|
982
|
-
OpenerDirector instance
|
|
983
|
-
"""
|
|
984
|
-
# set default keyword arguments
|
|
985
|
-
kwargs.setdefault('username', os.environ.get('EARTHDATA_USERNAME'))
|
|
986
|
-
kwargs.setdefault('password', os.environ.get('EARTHDATA_PASSWORD'))
|
|
987
|
-
kwargs.setdefault('retries', 5)
|
|
988
|
-
kwargs.setdefault('netrc', os.path.expanduser('~/.netrc'))
|
|
989
|
-
try:
|
|
990
|
-
# only necessary on jupyterhub
|
|
991
|
-
os.chmod(kwargs['netrc'], 0o600)
|
|
992
|
-
# try retrieving credentials from netrc
|
|
993
|
-
username, _, password = netrc.netrc(kwargs['netrc']).authenticators(urs)
|
|
994
|
-
except Exception as exc:
|
|
995
|
-
# try retrieving credentials from environmental variables
|
|
996
|
-
username, password = (kwargs['username'], kwargs['password'])
|
|
997
|
-
pass
|
|
998
|
-
# if username or password are not available
|
|
999
|
-
if not username:
|
|
1000
|
-
username = builtins.input(f'Username for {urs}: ')
|
|
1001
|
-
if not password:
|
|
1002
|
-
prompt = f'Password for {username}@{urs}: '
|
|
1003
|
-
password = getpass.getpass(prompt=prompt)
|
|
1004
|
-
# for each retry
|
|
1005
|
-
for retry in range(kwargs['retries']):
|
|
1006
|
-
# build an opener for urs with credentials
|
|
1007
|
-
opener = build_opener(username, password,
|
|
1008
|
-
context=context,
|
|
1009
|
-
password_manager=password_manager,
|
|
1010
|
-
get_ca_certs=get_ca_certs,
|
|
1011
|
-
redirect=redirect,
|
|
1012
|
-
authorization_header=authorization_header,
|
|
1013
|
-
urs=urs)
|
|
1014
|
-
# try logging in by check credentials
|
|
1015
|
-
try:
|
|
1016
|
-
check_credentials()
|
|
1017
|
-
except Exception as exc:
|
|
1018
|
-
pass
|
|
1019
|
-
else:
|
|
1020
|
-
return opener
|
|
1021
|
-
# reattempt login
|
|
1022
|
-
username = builtins.input(f'Username for {urs}: ')
|
|
1023
|
-
password = getpass.getpass(prompt=prompt)
|
|
1024
|
-
# reached end of available retries
|
|
1025
|
-
raise RuntimeError('End of Retries: Check NASA Earthdata credentials')
|
|
1026
|
-
|
|
1027
|
-
# PURPOSE: "login" to NASA Earthdata with supplied credentials
|
|
1028
|
-
def build_opener(
|
|
1029
|
-
username: str,
|
|
1030
|
-
password: str,
|
|
1031
|
-
context=_default_ssl_context,
|
|
1032
|
-
password_manager: bool = True,
|
|
1033
|
-
get_ca_certs: bool = True,
|
|
1034
|
-
redirect: bool = True,
|
|
1035
|
-
authorization_header: bool = False,
|
|
1036
|
-
urs: str = 'https://urs.earthdata.nasa.gov'
|
|
1037
|
-
):
|
|
1038
|
-
"""
|
|
1039
|
-
Build ``urllib`` opener for NASA Earthdata with supplied credentials
|
|
1040
|
-
|
|
1041
|
-
Parameters
|
|
1042
|
-
----------
|
|
1043
|
-
username: str or NoneType, default None
|
|
1044
|
-
NASA Earthdata username
|
|
1045
|
-
password: str or NoneType, default None
|
|
1046
|
-
NASA Earthdata password
|
|
1047
|
-
context: obj, default pyTMD.utilities._default_ssl_context
|
|
1048
|
-
SSL context for ``urllib`` opener object
|
|
1049
|
-
password_manager: bool, default True
|
|
1050
|
-
Create password manager context using default realm
|
|
1051
|
-
get_ca_certs: bool, default True
|
|
1052
|
-
Get list of loaded “certification authority” certificates
|
|
1053
|
-
redirect: bool, default True
|
|
1054
|
-
Create redirect handler object
|
|
1055
|
-
authorization_header: bool, default False
|
|
1056
|
-
Add base64 encoded authorization header to opener
|
|
1057
|
-
urs: str, default 'https://urs.earthdata.nasa.gov'
|
|
1058
|
-
Earthdata login URS 3 host
|
|
1059
|
-
|
|
1060
|
-
Returns
|
|
1061
|
-
-------
|
|
1062
|
-
opener: obj
|
|
1063
|
-
``OpenerDirector`` instance
|
|
1064
|
-
"""
|
|
1065
|
-
# https://docs.python.org/3/howto/urllib2.html#id5
|
|
1066
|
-
handler = []
|
|
1067
|
-
# create a password manager
|
|
1068
|
-
if password_manager:
|
|
1069
|
-
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
|
1070
|
-
# Add the username and password for NASA Earthdata Login system
|
|
1071
|
-
password_mgr.add_password(None, urs, username, password)
|
|
1072
|
-
handler.append(urllib2.HTTPBasicAuthHandler(password_mgr))
|
|
1073
|
-
# Create cookie jar for storing cookies. This is used to store and return
|
|
1074
|
-
# the session cookie given to use by the data server (otherwise will just
|
|
1075
|
-
# keep sending us back to Earthdata Login to authenticate).
|
|
1076
|
-
cookie_jar = CookieJar()
|
|
1077
|
-
handler.append(urllib2.HTTPCookieProcessor(cookie_jar))
|
|
1078
|
-
# SSL context handler
|
|
1079
|
-
if get_ca_certs:
|
|
1080
|
-
context.get_ca_certs()
|
|
1081
|
-
handler.append(urllib2.HTTPSHandler(context=context))
|
|
1082
|
-
# redirect handler
|
|
1083
|
-
if redirect:
|
|
1084
|
-
handler.append(urllib2.HTTPRedirectHandler())
|
|
1085
|
-
# create "opener" (OpenerDirector instance)
|
|
1086
|
-
opener = urllib2.build_opener(*handler)
|
|
1087
|
-
# Encode username/password for request authorization headers
|
|
1088
|
-
# add Authorization header to opener
|
|
1089
|
-
if authorization_header:
|
|
1090
|
-
b64 = base64.b64encode(f'{username}:{password}'.encode())
|
|
1091
|
-
opener.addheaders = [("Authorization", f"Basic {b64.decode()}")]
|
|
1092
|
-
# Now all calls to urllib2.urlopen use our opener.
|
|
1093
|
-
urllib2.install_opener(opener)
|
|
1094
|
-
# All calls to urllib2.urlopen will now use handler
|
|
1095
|
-
# Make sure not to include the protocol in with the URL, or
|
|
1096
|
-
# HTTPPasswordMgrWithDefaultRealm will be confused.
|
|
1097
|
-
return opener
|
|
1098
|
-
|
|
1099
|
-
# PURPOSE: generate a NASA Earthdata user token
|
|
1100
|
-
def get_token(
|
|
1101
|
-
HOST: str = 'https://urs.earthdata.nasa.gov/api/users/token',
|
|
1102
|
-
username: str | None = None,
|
|
1103
|
-
password: str | None = None,
|
|
1104
|
-
build: bool = True,
|
|
1105
|
-
urs: str = 'urs.earthdata.nasa.gov',
|
|
1106
|
-
):
|
|
1107
|
-
"""
|
|
1108
|
-
Generate a NASA Earthdata User Token
|
|
1109
|
-
|
|
1110
|
-
Parameters
|
|
1111
|
-
----------
|
|
1112
|
-
HOST: str or list
|
|
1113
|
-
NASA Earthdata token API host
|
|
1114
|
-
username: str or NoneType, default None
|
|
1115
|
-
NASA Earthdata username
|
|
1116
|
-
password: str or NoneType, default None
|
|
1117
|
-
NASA Earthdata password
|
|
1118
|
-
build: bool, default True
|
|
1119
|
-
Build opener and check WebDAV credentials
|
|
1120
|
-
timeout: int or NoneType, default None
|
|
1121
|
-
timeout in seconds for blocking operations
|
|
1122
|
-
urs: str, default 'urs.earthdata.nasa.gov'
|
|
1123
|
-
NASA Earthdata URS 3 host
|
|
1124
|
-
|
|
1125
|
-
Returns
|
|
1126
|
-
-------
|
|
1127
|
-
token: dict
|
|
1128
|
-
JSON response with NASA Earthdata User Token
|
|
1129
|
-
"""
|
|
1130
|
-
# attempt to build urllib2 opener and check credentials
|
|
1131
|
-
if build:
|
|
1132
|
-
attempt_login(urs,
|
|
1133
|
-
username=username,
|
|
1134
|
-
password=password,
|
|
1135
|
-
password_manager=False,
|
|
1136
|
-
get_ca_certs=False,
|
|
1137
|
-
redirect=False,
|
|
1138
|
-
authorization_header=True)
|
|
1139
|
-
# create post response with Earthdata token API
|
|
1140
|
-
try:
|
|
1141
|
-
request = urllib2.Request(HOST, method='POST')
|
|
1142
|
-
response = urllib2.urlopen(request)
|
|
1143
|
-
except urllib2.HTTPError as exc:
|
|
1144
|
-
logging.debug(exc.code)
|
|
1145
|
-
raise RuntimeError(exc.reason) from exc
|
|
1146
|
-
except urllib2.URLError as exc:
|
|
1147
|
-
logging.debug(exc.reason)
|
|
1148
|
-
raise RuntimeError('Check internet connection') from exc
|
|
1149
|
-
# read and return JSON response
|
|
1150
|
-
return json.loads(response.read())
|
|
1151
|
-
|
|
1152
|
-
# PURPOSE: generate a NASA Earthdata user token
|
|
1153
|
-
def list_tokens(
|
|
1154
|
-
HOST: str = 'https://urs.earthdata.nasa.gov/api/users/tokens',
|
|
1155
|
-
username: str | None = None,
|
|
1156
|
-
password: str | None = None,
|
|
1157
|
-
build: bool = True,
|
|
1158
|
-
urs: str = 'urs.earthdata.nasa.gov',
|
|
1159
|
-
):
|
|
1160
|
-
"""
|
|
1161
|
-
List the current associated NASA Earthdata User Tokens
|
|
1162
|
-
|
|
1163
|
-
Parameters
|
|
1164
|
-
----------
|
|
1165
|
-
HOST: str
|
|
1166
|
-
NASA Earthdata list token API host
|
|
1167
|
-
username: str or NoneType, default None
|
|
1168
|
-
NASA Earthdata username
|
|
1169
|
-
password: str or NoneType, default None
|
|
1170
|
-
NASA Earthdata password
|
|
1171
|
-
build: bool, default True
|
|
1172
|
-
Build opener and check WebDAV credentials
|
|
1173
|
-
timeout: int or NoneType, default None
|
|
1174
|
-
timeout in seconds for blocking operations
|
|
1175
|
-
urs: str, default 'urs.earthdata.nasa.gov'
|
|
1176
|
-
NASA Earthdata URS 3 host
|
|
1177
|
-
|
|
1178
|
-
Returns
|
|
1179
|
-
-------
|
|
1180
|
-
tokens: list
|
|
1181
|
-
JSON response with NASA Earthdata User Tokens
|
|
1182
|
-
"""
|
|
1183
|
-
# attempt to build urllib2 opener and check credentials
|
|
1184
|
-
if build:
|
|
1185
|
-
attempt_login(urs,
|
|
1186
|
-
username=username,
|
|
1187
|
-
password=password,
|
|
1188
|
-
password_manager=False,
|
|
1189
|
-
get_ca_certs=False,
|
|
1190
|
-
redirect=False,
|
|
1191
|
-
authorization_header=True)
|
|
1192
|
-
# create get response with Earthdata list tokens API
|
|
1193
|
-
try:
|
|
1194
|
-
request = urllib2.Request(HOST)
|
|
1195
|
-
response = urllib2.urlopen(request)
|
|
1196
|
-
except urllib2.HTTPError as exc:
|
|
1197
|
-
logging.debug(exc.code)
|
|
1198
|
-
raise RuntimeError(exc.reason) from exc
|
|
1199
|
-
except urllib2.URLError as exc:
|
|
1200
|
-
logging.debug(exc.reason)
|
|
1201
|
-
raise RuntimeError('Check internet connection') from exc
|
|
1202
|
-
# read and return JSON response
|
|
1203
|
-
return json.loads(response.read())
|
|
1204
|
-
|
|
1205
|
-
# PURPOSE: revoke a NASA Earthdata user token
|
|
1206
|
-
def revoke_token(
|
|
1207
|
-
token: str,
|
|
1208
|
-
HOST: str = f'https://urs.earthdata.nasa.gov/api/users/revoke_token',
|
|
1209
|
-
username: str | None = None,
|
|
1210
|
-
password: str | None = None,
|
|
1211
|
-
build: bool = True,
|
|
1212
|
-
urs: str = 'urs.earthdata.nasa.gov',
|
|
1213
|
-
):
|
|
1214
|
-
"""
|
|
1215
|
-
Generate a NASA Earthdata User Token
|
|
1216
|
-
|
|
1217
|
-
Parameters
|
|
1218
|
-
----------
|
|
1219
|
-
token: str
|
|
1220
|
-
NASA Earthdata token to be revoked
|
|
1221
|
-
HOST: str
|
|
1222
|
-
NASA Earthdata revoke token API host
|
|
1223
|
-
username: str or NoneType, default None
|
|
1224
|
-
NASA Earthdata username
|
|
1225
|
-
password: str or NoneType, default None
|
|
1226
|
-
NASA Earthdata password
|
|
1227
|
-
build: bool, default True
|
|
1228
|
-
Build opener and check WebDAV credentials
|
|
1229
|
-
timeout: int or NoneType, default None
|
|
1230
|
-
timeout in seconds for blocking operations
|
|
1231
|
-
urs: str, default 'urs.earthdata.nasa.gov'
|
|
1232
|
-
NASA Earthdata URS 3 host
|
|
1233
|
-
"""
|
|
1234
|
-
# attempt to build urllib2 opener and check credentials
|
|
1235
|
-
if build:
|
|
1236
|
-
attempt_login(urs,
|
|
1237
|
-
username=username,
|
|
1238
|
-
password=password,
|
|
1239
|
-
password_manager=False,
|
|
1240
|
-
get_ca_certs=False,
|
|
1241
|
-
redirect=False,
|
|
1242
|
-
authorization_header=True)
|
|
1243
|
-
# full path for NASA Earthdata revoke token API
|
|
1244
|
-
url = f'{HOST}?token={token}'
|
|
1245
|
-
# create post response with Earthdata revoke tokens API
|
|
1246
|
-
try:
|
|
1247
|
-
request = urllib2.Request(url, method='POST')
|
|
1248
|
-
response = urllib2.urlopen(request)
|
|
1249
|
-
except urllib2.HTTPError as exc:
|
|
1250
|
-
logging.debug(exc.code)
|
|
1251
|
-
raise RuntimeError(exc.reason) from exc
|
|
1252
|
-
except urllib2.URLError as exc:
|
|
1253
|
-
logging.debug(exc.reason)
|
|
1254
|
-
raise RuntimeError('Check internet connection') from exc
|
|
1255
|
-
# verbose response
|
|
1256
|
-
logging.debug(f'Token Revoked: {token}')
|
|
1257
|
-
|
|
1258
|
-
# PURPOSE: check that entered NASA Earthdata credentials are valid
|
|
1259
|
-
def check_credentials():
|
|
1260
|
-
"""
|
|
1261
|
-
Check that entered NASA Earthdata credentials are valid
|
|
1262
|
-
"""
|
|
1263
|
-
try:
|
|
1264
|
-
remote_path = posixpath.join('https://cddis.nasa.gov','archive')
|
|
1265
|
-
request = urllib2.Request(url=remote_path)
|
|
1266
|
-
response = urllib2.urlopen(request, timeout=20)
|
|
1267
|
-
except urllib2.HTTPError as exc:
|
|
1268
|
-
logging.debug(exc.code)
|
|
1269
|
-
raise RuntimeError(exc.reason) from exc
|
|
1270
|
-
except urllib2.URLError as exc:
|
|
1271
|
-
logging.debug(exc.reason)
|
|
1272
|
-
raise RuntimeError('Check internet connection') from exc
|
|
1273
|
-
else:
|
|
1274
|
-
return True
|
|
1275
|
-
|
|
1276
|
-
# PURPOSE: list a directory on GSFC CDDIS https server
|
|
1277
|
-
def cddis_list(
|
|
1278
|
-
HOST: str | list,
|
|
1279
|
-
username: str | None = None,
|
|
1280
|
-
password: str | None = None,
|
|
1281
|
-
build: bool = True,
|
|
1282
|
-
timeout: int | None = None,
|
|
1283
|
-
urs: str = 'urs.earthdata.nasa.gov',
|
|
1284
|
-
parser=lxml.etree.HTMLParser(),
|
|
1285
|
-
pattern: str = '',
|
|
1286
|
-
sort: bool = False
|
|
1287
|
-
):
|
|
1288
|
-
"""
|
|
1289
|
-
List a directory on GSFC CDDIS archive server
|
|
1290
|
-
|
|
1291
|
-
Parameters
|
|
1292
|
-
----------
|
|
1293
|
-
HOST: str or list
|
|
1294
|
-
remote https host
|
|
1295
|
-
username: str or NoneType, default None
|
|
1296
|
-
NASA Earthdata username
|
|
1297
|
-
password: str or NoneType, default None
|
|
1298
|
-
NASA Earthdata password
|
|
1299
|
-
build: bool, default True
|
|
1300
|
-
Build opener and check Earthdata credentials
|
|
1301
|
-
timeout: int or NoneType, default None
|
|
1302
|
-
timeout in seconds for blocking operations
|
|
1303
|
-
urs: str, default 'urs.earthdata.nasa.gov'
|
|
1304
|
-
NASA Earthdata URS 3 host
|
|
1305
|
-
parser: obj, default lxml.etree.HTMLParser()
|
|
1306
|
-
HTML parser for ``lxml``
|
|
1307
|
-
pattern: str, default ''
|
|
1308
|
-
regular expression pattern for reducing list
|
|
1309
|
-
sort: bool, default False
|
|
1310
|
-
sort output list
|
|
1311
|
-
|
|
1312
|
-
Returns
|
|
1313
|
-
-------
|
|
1314
|
-
colnames: list
|
|
1315
|
-
column names in a directory
|
|
1316
|
-
collastmod: list
|
|
1317
|
-
last modification times for items in the directory
|
|
1318
|
-
"""
|
|
1319
|
-
# use netrc credentials
|
|
1320
|
-
if build and not (username or password):
|
|
1321
|
-
username,_,password = netrc.netrc().authenticators(urs)
|
|
1322
|
-
# build urllib2 opener and check credentials
|
|
1323
|
-
if build:
|
|
1324
|
-
# build urllib2 opener with credentials
|
|
1325
|
-
build_opener(username, password)
|
|
1326
|
-
# check credentials
|
|
1327
|
-
check_credentials()
|
|
1328
|
-
# verify inputs for remote https host
|
|
1329
|
-
if isinstance(HOST, str):
|
|
1330
|
-
HOST = url_split(HOST)
|
|
1331
|
-
# Encode username/password for request authorization headers
|
|
1332
|
-
b64 = base64.b64encode(f'{username}:{password}'.encode())
|
|
1333
|
-
authorization_header = f"Basic {b64.decode()}"
|
|
1334
|
-
# try listing from https
|
|
1335
|
-
try:
|
|
1336
|
-
# Create and submit request.
|
|
1337
|
-
request = urllib2.Request(posixpath.join(*HOST))
|
|
1338
|
-
request.add_header("Authorization", authorization_header)
|
|
1339
|
-
tree = lxml.etree.parse(urllib2.urlopen(request, timeout=timeout), parser)
|
|
1340
|
-
except:
|
|
1341
|
-
raise Exception('List error from {0}'.format(posixpath.join(*HOST)))
|
|
1342
|
-
else:
|
|
1343
|
-
# read and parse request for files (column names and modified times)
|
|
1344
|
-
# find directories
|
|
1345
|
-
colnames = tree.xpath('//div[@class="archiveDir"]/div/a/text()')
|
|
1346
|
-
collastmod = [None]*(len(colnames))
|
|
1347
|
-
# find files
|
|
1348
|
-
colnames.extend(tree.xpath('//div[@class="archiveItem"]/div/a/text()'))
|
|
1349
|
-
# get the Unix timestamp value for a modification time
|
|
1350
|
-
collastmod.extend([get_unix_time(i[:19], format='%Y:%m:%d %H:%M:%S')
|
|
1351
|
-
for i in tree.xpath('//div[@class="archiveItem"]/div/span/text()')])
|
|
1352
|
-
# reduce using regular expression pattern
|
|
1353
|
-
if pattern:
|
|
1354
|
-
i = [i for i,f in enumerate(colnames) if re.search(pattern, f)]
|
|
1355
|
-
# reduce list of column names and last modified times
|
|
1356
|
-
colnames = [colnames[indice] for indice in i]
|
|
1357
|
-
collastmod = [collastmod[indice] for indice in i]
|
|
1358
|
-
# sort the list
|
|
1359
|
-
if sort:
|
|
1360
|
-
i = [i for i,j in sorted(enumerate(colnames), key=lambda i: i[1])]
|
|
1361
|
-
# sort list of column names and last modified times
|
|
1362
|
-
colnames = [colnames[indice] for indice in i]
|
|
1363
|
-
collastmod = [collastmod[indice] for indice in i]
|
|
1364
|
-
# return the list of column names and last modified times
|
|
1365
|
-
return (colnames, collastmod)
|
|
1366
|
-
|
|
1367
|
-
# PURPOSE: download a file from a GSFC CDDIS https server
|
|
1368
|
-
def from_cddis(
|
|
1369
|
-
HOST: str | list,
|
|
1370
|
-
username: str | None = None,
|
|
1371
|
-
password: str | None = None,
|
|
1372
|
-
build: bool = True,
|
|
1373
|
-
timeout: int | None = None,
|
|
1374
|
-
urs: str = 'urs.earthdata.nasa.gov',
|
|
1375
|
-
local: str | pathlib.Path | None = None,
|
|
1376
|
-
hash: str = '',
|
|
1377
|
-
chunk: int = 16384,
|
|
1378
|
-
verbose: bool = False,
|
|
1379
|
-
fid=sys.stdout,
|
|
1380
|
-
mode: oct = 0o775
|
|
1381
|
-
):
|
|
1382
|
-
"""
|
|
1383
|
-
Download a file from GSFC CDDIS archive server
|
|
1384
|
-
|
|
1385
|
-
Parameters
|
|
1386
|
-
----------
|
|
1387
|
-
HOST: str or list
|
|
1388
|
-
remote https host
|
|
1389
|
-
username: str or NoneType, default None
|
|
1390
|
-
NASA Earthdata username
|
|
1391
|
-
password: str or NoneType, default None
|
|
1392
|
-
NASA Earthdata password
|
|
1393
|
-
build: bool, default True
|
|
1394
|
-
Build opener and check Earthdata credentials
|
|
1395
|
-
timeout: int or NoneType, default None
|
|
1396
|
-
timeout in seconds for blocking operations
|
|
1397
|
-
urs: str, default 'urs.earthdata.nasa.gov'
|
|
1398
|
-
NASA Earthdata URS 3 host
|
|
1399
|
-
local: str, pathlib.Path or NoneType, default None
|
|
1400
|
-
path to local file
|
|
1401
|
-
hash: str, default ''
|
|
1402
|
-
MD5 hash of local file
|
|
1403
|
-
chunk: int, default 16384
|
|
1404
|
-
chunk size for transfer encoding
|
|
1405
|
-
verbose: bool, default False
|
|
1406
|
-
print file transfer information
|
|
1407
|
-
fid: obj, default sys.stdout
|
|
1408
|
-
open file object to print if verbose
|
|
1409
|
-
mode: oct, default 0o775
|
|
1410
|
-
permissions mode of output local file
|
|
1411
|
-
|
|
1412
|
-
Returns
|
|
1413
|
-
-------
|
|
1414
|
-
remote_buffer: obj
|
|
1415
|
-
BytesIO representation of file
|
|
1416
|
-
"""
|
|
1417
|
-
# create logger
|
|
1418
|
-
loglevel = logging.INFO if verbose else logging.CRITICAL
|
|
1419
|
-
logging.basicConfig(stream=fid, level=loglevel)
|
|
1420
|
-
# use netrc credentials
|
|
1421
|
-
if build and not (username or password):
|
|
1422
|
-
username,_,password = netrc.netrc().authenticators(urs)
|
|
1423
|
-
# build urllib2 opener and check credentials
|
|
1424
|
-
if build:
|
|
1425
|
-
# build urllib2 opener with credentials
|
|
1426
|
-
build_opener(username, password)
|
|
1427
|
-
# check credentials
|
|
1428
|
-
check_credentials()
|
|
1429
|
-
# verify inputs for remote https host
|
|
1430
|
-
if isinstance(HOST, str):
|
|
1431
|
-
HOST = url_split(HOST)
|
|
1432
|
-
# Encode username/password for request authorization headers
|
|
1433
|
-
b64 = base64.b64encode(f'{username}:{password}'.encode())
|
|
1434
|
-
authorization_header = f"Basic {b64.decode()}"
|
|
1435
|
-
# try downloading from https
|
|
1436
|
-
try:
|
|
1437
|
-
# Create and submit request.
|
|
1438
|
-
request = urllib2.Request(posixpath.join(*HOST))
|
|
1439
|
-
request.add_header("Authorization", authorization_header)
|
|
1440
|
-
response = urllib2.urlopen(request, timeout=timeout)
|
|
1441
|
-
except:
|
|
1442
|
-
raise Exception('Download error from {0}'.format(posixpath.join(*HOST)))
|
|
1443
|
-
else:
|
|
1444
|
-
# copy remote file contents to bytesIO object
|
|
1445
|
-
remote_buffer = io.BytesIO()
|
|
1446
|
-
shutil.copyfileobj(response, remote_buffer, chunk)
|
|
1447
|
-
remote_buffer.seek(0)
|
|
1448
|
-
# save file basename with bytesIO object
|
|
1449
|
-
remote_buffer.filename = HOST[-1]
|
|
1450
|
-
# generate checksum hash for remote file
|
|
1451
|
-
remote_hash = hashlib.md5(remote_buffer.getvalue()).hexdigest()
|
|
1452
|
-
# compare checksums
|
|
1453
|
-
if local and (hash != remote_hash):
|
|
1454
|
-
# convert to absolute path
|
|
1455
|
-
local = pathlib.Path(local).expanduser().absolute()
|
|
1456
|
-
# create directory if non-existent
|
|
1457
|
-
local.parent.mkdir(mode=mode, parents=True, exist_ok=True)
|
|
1458
|
-
# print file information
|
|
1459
|
-
args = (posixpath.join(*HOST), str(local))
|
|
1460
|
-
logging.info('{0} -->\n\t{1}'.format(*args))
|
|
1461
|
-
# store bytes to file using chunked transfer encoding
|
|
1462
|
-
remote_buffer.seek(0)
|
|
1463
|
-
with local.open(mode='wb') as f:
|
|
1464
|
-
shutil.copyfileobj(remote_buffer, f, chunk)
|
|
1465
|
-
# change the permissions mode
|
|
1466
|
-
local.chmod(mode)
|
|
1467
|
-
# return the bytesIO object
|
|
1468
|
-
remote_buffer.seek(0)
|
|
1469
|
-
return remote_buffer
|
|
1470
|
-
|
|
1471
875
|
# PURPOSE: list a directory on IERS https Server
|
|
1472
876
|
def iers_list(
|
|
1473
877
|
HOST: str | list,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
u"""
|
|
3
3
|
aviso_fes_tides.py
|
|
4
|
-
Written by Tyler Sutterley (
|
|
4
|
+
Written by Tyler Sutterley (07/2025)
|
|
5
5
|
Downloads the FES (Finite Element Solution) global tide model from AVISO
|
|
6
6
|
Decompresses the model tar files into the constituent files and auxiliary files
|
|
7
7
|
https://www.aviso.altimetry.fr/data/products/auxiliary-products/
|
|
@@ -24,9 +24,12 @@ COMMAND LINE OPTIONS:
|
|
|
24
24
|
FES2012
|
|
25
25
|
FES2014
|
|
26
26
|
FES2022
|
|
27
|
-
--load: download load tide model outputs
|
|
28
|
-
|
|
29
|
-
--
|
|
27
|
+
--load: download load tide model outputs
|
|
28
|
+
(FES2014)
|
|
29
|
+
--currents: download tide model current outputs
|
|
30
|
+
(FES2012 and FES2014)
|
|
31
|
+
--extrapolated: Download extrapolated tide model outputs
|
|
32
|
+
(FES2014 and FES2022)
|
|
30
33
|
-G, --gzip: compress output ascii and netCDF4 tide files
|
|
31
34
|
-t X, --timeout X: timeout in seconds for blocking operations
|
|
32
35
|
--log: output log of files downloaded
|
|
@@ -40,6 +43,7 @@ PROGRAM DEPENDENCIES:
|
|
|
40
43
|
utilities.py: download and management utilities for syncing files
|
|
41
44
|
|
|
42
45
|
UPDATE HISTORY:
|
|
46
|
+
Updated 07/2025: added extrapolation option for FES2014 tide model
|
|
43
47
|
Updated 01/2025: new ocean tide directory for latest FES2022 version
|
|
44
48
|
scrubbed use of pathlib.os to just use os directly
|
|
45
49
|
Updated 07/2024: added list and download for FES2022 tide model
|
|
@@ -119,6 +123,7 @@ def aviso_fes_tides(MODEL: str,
|
|
|
119
123
|
DIRECTORY=DIRECTORY,
|
|
120
124
|
LOAD=LOAD,
|
|
121
125
|
CURRENTS=CURRENTS,
|
|
126
|
+
EXTRAPOLATED=EXTRAPOLATED,
|
|
122
127
|
GZIP=GZIP,
|
|
123
128
|
MODE=MODE)
|
|
124
129
|
elif MODEL in ('FES2022',):
|
|
@@ -142,6 +147,7 @@ def aviso_fes_tar(MODEL, f, logger,
|
|
|
142
147
|
DIRECTORY: str | pathlib.Path | None = None,
|
|
143
148
|
LOAD: bool = False,
|
|
144
149
|
CURRENTS: bool = False,
|
|
150
|
+
EXTRAPOLATED: bool = False,
|
|
145
151
|
GZIP: bool = False,
|
|
146
152
|
MODE: oct = 0o775
|
|
147
153
|
):
|
|
@@ -201,6 +207,12 @@ def aviso_fes_tar(MODEL, f, logger,
|
|
|
201
207
|
'fes2014a_loadtide','load_tide.tar.xz'])
|
|
202
208
|
TAR['FES2014'].extend(['r'])
|
|
203
209
|
FLATTEN['FES2014'].extend([False])
|
|
210
|
+
if EXTRAPOLATED:
|
|
211
|
+
FES['FES2014'].append(['fes2014_elevations_and_load',
|
|
212
|
+
'fes2014b_elevations_extrapolated',
|
|
213
|
+
'ocean_tide_extrapolated.tar.xz'])
|
|
214
|
+
TAR['FES2014'].extend(['r'])
|
|
215
|
+
FLATTEN['FES2014'].extend([False])
|
|
204
216
|
if CURRENTS:
|
|
205
217
|
subdir = 'fes2014a_currents'
|
|
206
218
|
FES['FES2014'].append([subdir,'readme_fes2014_currents_v1.2.txt'])
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
u"""
|
|
3
3
|
gsfc_got_tides.py
|
|
4
4
|
Written by Tyler Sutterley (01/2025)
|
|
5
|
-
Download
|
|
5
|
+
Download Goddard Ocean Tide (GOT) models
|
|
6
6
|
|
|
7
7
|
CALLING SEQUENCE:
|
|
8
8
|
python gsfc_got_tides.py --tide=GOT5.6
|
|
@@ -132,7 +132,7 @@ def newer(t1: int, t2: int) -> bool:
|
|
|
132
132
|
# PURPOSE: create argument parser
|
|
133
133
|
def arguments():
|
|
134
134
|
parser = argparse.ArgumentParser(
|
|
135
|
-
description="""Download
|
|
135
|
+
description="""Download Goddard Tide models from NASA
|
|
136
136
|
Goddard Space Flight Center (GSFC)
|
|
137
137
|
""",
|
|
138
138
|
fromfile_prefix_chars="@"
|
|
@@ -143,16 +143,16 @@ def arguments():
|
|
|
143
143
|
parser.add_argument('--directory','-D',
|
|
144
144
|
type=pathlib.Path, default=pathlib.Path.cwd(),
|
|
145
145
|
help='Working data directory')
|
|
146
|
-
#
|
|
146
|
+
# Goddard Ocean Tide model to download
|
|
147
147
|
parser.add_argument('--tide','-T',
|
|
148
148
|
metavar='TIDE', type=str, nargs='+', default=['GOT5.5'],
|
|
149
149
|
choices=('GOT4.8','GOT4.10','GOT5.5','GOT5.5D','GOT5.6','RE14'),
|
|
150
|
-
help='
|
|
151
|
-
#
|
|
150
|
+
help='Goddard Ocean Tide model to download')
|
|
151
|
+
# Goddard Ocean Tide model format to download
|
|
152
152
|
parser.add_argument('--format',
|
|
153
153
|
type=str, default='netcdf',
|
|
154
154
|
choices=('ascii','netcdf'),
|
|
155
|
-
help='
|
|
155
|
+
help='Goddard Ocean Tide model format to download')
|
|
156
156
|
# compress output ascii and netCDF4 tide files with gzip
|
|
157
157
|
parser.add_argument('--gzip','-G',
|
|
158
158
|
default=False, action='store_true',
|
pytmd-2.2.6/version.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.2.6
|
pytmd-2.2.5/version.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.2.5
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|