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.
Files changed (65) hide show
  1. {pytmd-2.2.5 → pytmd-2.2.6}/CITATION.cff +2 -2
  2. {pytmd-2.2.5 → pytmd-2.2.6}/PKG-INFO +1 -1
  3. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/astro.py +3 -3
  4. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/compute.py +14 -7
  5. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/crs.py +1 -1
  6. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/GOT.py +8 -8
  7. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/model.py +1 -1
  8. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/predict.py +20 -12
  9. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/utilities.py +2 -598
  10. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/PKG-INFO +1 -1
  11. {pytmd-2.2.5 → pytmd-2.2.6}/scripts/aviso_fes_tides.py +16 -4
  12. {pytmd-2.2.5 → pytmd-2.2.6}/scripts/gsfc_got_tides.py +6 -6
  13. pytmd-2.2.6/version.txt +1 -0
  14. pytmd-2.2.5/version.txt +0 -1
  15. {pytmd-2.2.5 → pytmd-2.2.6}/.gitignore +0 -0
  16. {pytmd-2.2.5 → pytmd-2.2.6}/CODE_OF_CONDUCT.rst +0 -0
  17. {pytmd-2.2.5 → pytmd-2.2.6}/CONTRIBUTORS.rst +0 -0
  18. {pytmd-2.2.5 → pytmd-2.2.6}/LICENSE +0 -0
  19. {pytmd-2.2.5 → pytmd-2.2.6}/MANIFEST.in +0 -0
  20. {pytmd-2.2.5 → pytmd-2.2.6}/README.rst +0 -0
  21. {pytmd-2.2.5 → pytmd-2.2.6}/providers/AVISO.json +0 -0
  22. {pytmd-2.2.5 → pytmd-2.2.6}/providers/ESR.json +0 -0
  23. {pytmd-2.2.5 → pytmd-2.2.6}/providers/GSFC.json +0 -0
  24. {pytmd-2.2.5 → pytmd-2.2.6}/providers/README.rst +0 -0
  25. {pytmd-2.2.5 → pytmd-2.2.6}/providers/TPXO.json +0 -0
  26. {pytmd-2.2.5 → pytmd-2.2.6}/providers/_model_to_database.py +0 -0
  27. {pytmd-2.2.5 → pytmd-2.2.6}/providers/_providers_to_database.py +0 -0
  28. {pytmd-2.2.5 → pytmd-2.2.6}/providers/_update_providers.py +0 -0
  29. {pytmd-2.2.5 → pytmd-2.2.6}/providers/providers.json +0 -0
  30. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/__init__.py +0 -0
  31. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/arguments.py +0 -0
  32. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/compute_tide_corrections.py +0 -0
  33. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/ce1973_tab1.txt +0 -0
  34. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/ct1971_tab5.txt +0 -0
  35. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/d1921_tab.txt +0 -0
  36. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/database.json +0 -0
  37. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/doodson.json +0 -0
  38. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/opoleloadcoefcmcor.txt.gz +0 -0
  39. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/tab5.2e.txt +0 -0
  40. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/tab5.3a.txt +0 -0
  41. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/data/tab5.3b.txt +0 -0
  42. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/ellipse.py +0 -0
  43. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/interpolate.py +0 -0
  44. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/ATLAS.py +0 -0
  45. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/FES.py +0 -0
  46. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/IERS.py +0 -0
  47. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/OTIS.py +0 -0
  48. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/__init__.py +0 -0
  49. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/io/constituents.py +0 -0
  50. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/math.py +0 -0
  51. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/solve/__init__.py +0 -0
  52. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/solve/constants.py +0 -0
  53. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/spatial.py +0 -0
  54. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/tools.py +0 -0
  55. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD/version.py +0 -0
  56. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/SOURCES.txt +0 -0
  57. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/dependency_links.txt +0 -0
  58. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/requires.txt +0 -0
  59. {pytmd-2.2.5 → pytmd-2.2.6}/pyTMD.egg-info/top_level.txt +0 -0
  60. {pytmd-2.2.5 → pytmd-2.2.6}/pyproject.toml +0 -0
  61. {pytmd-2.2.5 → pytmd-2.2.6}/scripts/arcticdata_tides.py +0 -0
  62. {pytmd-2.2.5 → pytmd-2.2.6}/scripts/reduce_OTIS_files.py +0 -0
  63. {pytmd-2.2.5 → pytmd-2.2.6}/scripts/verify_box_tpxo.py +0 -0
  64. {pytmd-2.2.5 → pytmd-2.2.6}/setup.cfg +0 -0
  65. {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.5"
40
- date-released: "2025-06-23"
39
+ version: "2.2.6"
40
+ date-released: "2025-07-24"
41
41
  keywords:
42
42
  - Ocean Tides
43
43
  - Load Tides
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyTMD
3
- Version: 2.2.5
3
+ Version: 2.2.6
4
4
  Summary: Python-based tidal prediction software for estimating ocean, load, solid Earth and pole tides
5
5
  Author: Tyler Sutterley
6
6
  Author-email: tsutterl@uw.edu
@@ -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 GSFC Global Ocean Tides (GOT) model
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 calcuation of the difference between apparent and
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 tranforming from the
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 (05/2025)
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 dictoinary
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 Global Ocean Tide (GOT) models and makes initial
7
- calculations to run the tide program
8
- Includes functions to extract tidal harmonic constants out of a tidal model for
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 Global Ocean Tide (GOT) models
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 Global Ocean Tide (GOT) models
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 Global Ocean Tide (GOT) model file
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 Global Ocean Tide (GOT) netCDF4 model file
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 Global Ocean Tide (GOT) models
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 (05/2025)
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 bouys or altimetry points
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 Terrestial Time (TT)
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) + my*np.sin(phi))
1160
- S[:,1] = dfactor[:,1]*np.cos(theta)*(mx*np.sin(phi) - my*np.cos(phi))
1161
- S[:,2] = dfactor[:,2]*np.sin(2.0*theta)*(mx*np.cos(phi) + my*np.sin(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.einsum('ti...,jit...->tj...', S, R)
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 Terrestial Time (TT)
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 - dn*cosla*sinphi
1761
- DY = dr*sinla*cosphi - dn*sinla*sinphi
1762
- DZ = dr*sinphi + dn*cosphi
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 (01/2025)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyTMD
3
- Version: 2.2.5
3
+ Version: 2.2.6
4
4
  Summary: Python-based tidal prediction software for estimating ocean, load, solid Earth and pole tides
5
5
  Author: Tyler Sutterley
6
6
  Author-email: tsutterl@uw.edu
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  u"""
3
3
  aviso_fes_tides.py
4
- Written by Tyler Sutterley (01/2025)
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 (FES2014)
28
- --currents: download tide model current outputs (FES2012 and FES2014)
29
- --extrapolated: Download extrapolated tide model outputs (FES2022)
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 GSFC Global Ocean Tide (GOT) models
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 Global Ocean Tide models from NASA
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
- # Global Ocean Tide model to download
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='Global Ocean Tide model to download')
151
- # Global Ocean Tide model format to download
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='Global Ocean Tide model format to download')
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',
@@ -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