pyTMD 2.2.4__tar.gz → 2.2.5__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 (66) hide show
  1. {pytmd-2.2.4 → pytmd-2.2.5}/.gitignore +2 -0
  2. {pytmd-2.2.4 → pytmd-2.2.5}/CITATION.cff +2 -2
  3. {pytmd-2.2.4 → pytmd-2.2.5}/PKG-INFO +1 -1
  4. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/compute.py +19 -10
  5. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/compute_tide_corrections.py +1 -1
  6. pytmd-2.2.5/pyTMD/ellipse.py +196 -0
  7. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/io/OTIS.py +58 -15
  8. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/io/model.py +112 -39
  9. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/solve/constants.py +38 -10
  10. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD.egg-info/PKG-INFO +1 -1
  11. pytmd-2.2.5/version.txt +1 -0
  12. pytmd-2.2.4/pyTMD/ellipse.py +0 -143
  13. pytmd-2.2.4/version.txt +0 -1
  14. {pytmd-2.2.4 → pytmd-2.2.5}/CODE_OF_CONDUCT.rst +0 -0
  15. {pytmd-2.2.4 → pytmd-2.2.5}/CONTRIBUTORS.rst +0 -0
  16. {pytmd-2.2.4 → pytmd-2.2.5}/LICENSE +0 -0
  17. {pytmd-2.2.4 → pytmd-2.2.5}/MANIFEST.in +0 -0
  18. {pytmd-2.2.4 → pytmd-2.2.5}/README.rst +0 -0
  19. {pytmd-2.2.4 → pytmd-2.2.5}/providers/AVISO.json +0 -0
  20. {pytmd-2.2.4 → pytmd-2.2.5}/providers/ESR.json +0 -0
  21. {pytmd-2.2.4 → pytmd-2.2.5}/providers/GSFC.json +0 -0
  22. {pytmd-2.2.4 → pytmd-2.2.5}/providers/README.rst +0 -0
  23. {pytmd-2.2.4 → pytmd-2.2.5}/providers/TPXO.json +0 -0
  24. {pytmd-2.2.4 → pytmd-2.2.5}/providers/_model_to_database.py +0 -0
  25. {pytmd-2.2.4 → pytmd-2.2.5}/providers/_providers_to_database.py +0 -0
  26. {pytmd-2.2.4 → pytmd-2.2.5}/providers/_update_providers.py +0 -0
  27. {pytmd-2.2.4 → pytmd-2.2.5}/providers/providers.json +0 -0
  28. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/__init__.py +0 -0
  29. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/arguments.py +2 -2
  30. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/astro.py +0 -0
  31. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/crs.py +0 -0
  32. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/ce1973_tab1.txt +0 -0
  33. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/ct1971_tab5.txt +0 -0
  34. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/d1921_tab.txt +0 -0
  35. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/database.json +0 -0
  36. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/doodson.json +0 -0
  37. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/opoleloadcoefcmcor.txt.gz +0 -0
  38. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/tab5.2e.txt +0 -0
  39. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/tab5.3a.txt +0 -0
  40. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/data/tab5.3b.txt +0 -0
  41. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/interpolate.py +0 -0
  42. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/io/ATLAS.py +0 -0
  43. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/io/FES.py +0 -0
  44. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/io/GOT.py +0 -0
  45. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/io/IERS.py +0 -0
  46. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/io/__init__.py +0 -0
  47. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/io/constituents.py +0 -0
  48. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/math.py +0 -0
  49. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/predict.py +0 -0
  50. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/solve/__init__.py +0 -0
  51. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/spatial.py +0 -0
  52. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/tools.py +0 -0
  53. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/utilities.py +0 -0
  54. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD/version.py +0 -0
  55. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD.egg-info/SOURCES.txt +0 -0
  56. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD.egg-info/dependency_links.txt +0 -0
  57. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD.egg-info/requires.txt +0 -0
  58. {pytmd-2.2.4 → pytmd-2.2.5}/pyTMD.egg-info/top_level.txt +0 -0
  59. {pytmd-2.2.4 → pytmd-2.2.5}/pyproject.toml +0 -0
  60. {pytmd-2.2.4 → pytmd-2.2.5}/scripts/arcticdata_tides.py +0 -0
  61. {pytmd-2.2.4 → pytmd-2.2.5}/scripts/aviso_fes_tides.py +0 -0
  62. {pytmd-2.2.4 → pytmd-2.2.5}/scripts/gsfc_got_tides.py +0 -0
  63. {pytmd-2.2.4 → pytmd-2.2.5}/scripts/reduce_OTIS_files.py +0 -0
  64. {pytmd-2.2.4 → pytmd-2.2.5}/scripts/verify_box_tpxo.py +0 -0
  65. {pytmd-2.2.4 → pytmd-2.2.5}/setup.cfg +0 -0
  66. {pytmd-2.2.4 → pytmd-2.2.5}/setup.py +0 -0
@@ -42,6 +42,8 @@ wheels/
42
42
  *.egg
43
43
  .pytest_cache
44
44
  pythonenv*/
45
+ venv/
46
+ *build-commands.txt
45
47
  setup-miniconda-patched-environment.yml
46
48
  # OS generated files #
47
49
  ######################
@@ -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.4"
40
- date-released: "2025-05-07"
39
+ version: "2.2.5"
40
+ date-released: "2025-06-23"
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.4
3
+ Version: 2.2.5
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
  compute.py
4
- Written by Tyler Sutterley (12/2024)
4
+ Written by Tyler Sutterley (05/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 05/2025: added option to select constituents to read from model
65
66
  Updated 12/2024: moved check points function as compute.tide_masks
66
67
  Updated 11/2024: expose buffer distance for cropping tide model data
67
68
  Updated 10/2024: compute delta times based on corrections type
@@ -89,7 +90,7 @@ UPDATE HISTORY:
89
90
  Updated 06/2024: use np.clongdouble instead of np.longcomplex
90
91
  Updated 04/2024: use wrapper to importlib for optional dependencies
91
92
  Updated 02/2024: changed class name for ellipsoid parameters to datum
92
- Updated 01/2024: made the inferrence of minor constituents an option
93
+ Updated 01/2024: made the inference of minor constituents an option
93
94
  refactored lunisolar ephemerides functions
94
95
  renamed module to compute and added tidal currents function
95
96
  Updated 12/2023: use new crs class for coordinate reprojection
@@ -230,6 +231,7 @@ def tide_elevations(
230
231
  EXTRAPOLATE: bool = False,
231
232
  CUTOFF: int | float = 10.0,
232
233
  CORRECTIONS: str | None = None,
234
+ CONSTITUENTS: list | None = None,
233
235
  INFER_MINOR: bool = True,
234
236
  MINOR_CONSTITUENTS: list | None = None,
235
237
  APPEND_NODE: bool = False,
@@ -297,6 +299,8 @@ def tide_elevations(
297
299
  Set to ``np.inf`` to extrapolate for all points
298
300
  CORRECTIONS: str or None, default None
299
301
  Nodal correction type, default based on model
302
+ CONSTITUENTS: list or None, default None
303
+ Specify constituents to read from model
300
304
  INFER_MINOR: bool, default True
301
305
  Infer the height values for minor tidal constituents
302
306
  MINOR_CONSTITUENTS: list or None, default None
@@ -366,7 +370,8 @@ def tide_elevations(
366
370
  nt = len(ts)
367
371
 
368
372
  # read tidal constants and interpolate to grid points
369
- amp, ph, c = model.extract_constants(lon, lat, type=model.type,
373
+ amp, ph, c = model.extract_constants(lon, lat,
374
+ type=model.type, constituents=CONSTITUENTS,
370
375
  crop=CROP, bounds=BOUNDS, buffer=BUFFER, method=METHOD,
371
376
  extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
372
377
  append_node=APPEND_NODE, apply_flexure=APPLY_FLEXURE)
@@ -395,7 +400,7 @@ def tide_elevations(
395
400
  for i in range(nt):
396
401
  TIDE = pyTMD.predict.map(ts.tide[i], hc, c,
397
402
  deltat=deltat[i], corrections=nodal_corrections)
398
- # calculate values for minor constituents by inferrence
403
+ # calculate values for minor constituents by inference
399
404
  if INFER_MINOR:
400
405
  MINOR = pyTMD.predict.infer_minor(ts.tide[i], hc, c,
401
406
  deltat=deltat[i], corrections=nodal_corrections,
@@ -410,7 +415,7 @@ def tide_elevations(
410
415
  tide.mask = np.any(hc.mask,axis=1)
411
416
  tide.data[:] = pyTMD.predict.drift(ts.tide, hc, c,
412
417
  deltat=deltat, corrections=nodal_corrections)
413
- # calculate values for minor constituents by inferrence
418
+ # calculate values for minor constituents by inference
414
419
  if INFER_MINOR:
415
420
  minor = pyTMD.predict.infer_minor(ts.tide, hc, c,
416
421
  deltat=deltat, corrections=nodal_corrections,
@@ -424,7 +429,7 @@ def tide_elevations(
424
429
  HC = hc[s,None,:]
425
430
  TIDE = pyTMD.predict.time_series(ts.tide, HC, c,
426
431
  deltat=deltat, corrections=nodal_corrections)
427
- # calculate values for minor constituents by inferrence
432
+ # calculate values for minor constituents by inference
428
433
  if INFER_MINOR:
429
434
  MINOR = pyTMD.predict.infer_minor(ts.tide, HC, c,
430
435
  deltat=deltat, corrections=nodal_corrections,
@@ -458,6 +463,7 @@ def tide_currents(
458
463
  EXTRAPOLATE: bool = False,
459
464
  CUTOFF: int | float = 10.0,
460
465
  CORRECTIONS: str | None = None,
466
+ CONSTITUENTS: list | None = None,
461
467
  INFER_MINOR: bool = True,
462
468
  MINOR_CONSTITUENTS: list | None = None,
463
469
  FILL_VALUE: float = np.nan,
@@ -523,6 +529,8 @@ def tide_currents(
523
529
  Set to ``np.inf`` to extrapolate for all points
524
530
  CORRECTIONS: str or None, default None
525
531
  Nodal correction type, default based on model
532
+ CONSTITUENTS: list or None, default None
533
+ Specify constituents to read from model
526
534
  INFER_MINOR: bool, default True
527
535
  Infer the height values for minor tidal constituents
528
536
  MINOR_CONSTITUENTS: list or None, default None
@@ -595,7 +603,8 @@ def tide_currents(
595
603
  # iterate over u and v currents
596
604
  for t in model.type:
597
605
  # read tidal constants and interpolate to grid points
598
- amp, ph, c = model.extract_constants(lon, lat, type=t,
606
+ amp, ph, c = model.extract_constants(lon, lat,
607
+ type=t, constituents=CONSTITUENTS,
599
608
  crop=CROP, bounds=BOUNDS, buffer=BUFFER, method=METHOD,
600
609
  extrapolate=EXTRAPOLATE, cutoff=CUTOFF)
601
610
  # calculate complex phase in radians for Euler's
@@ -623,7 +632,7 @@ def tide_currents(
623
632
  for i in range(nt):
624
633
  TIDE = pyTMD.predict.map(ts.tide[i], hc, c,
625
634
  deltat=deltat[i], corrections=nodal_corrections)
626
- # calculate values for minor constituents by inferrence
635
+ # calculate values for minor constituents by inference
627
636
  if INFER_MINOR:
628
637
  MINOR = pyTMD.predict.infer_minor(ts.tide[i], hc, c,
629
638
  deltat=deltat[i], corrections=nodal_corrections,
@@ -638,7 +647,7 @@ def tide_currents(
638
647
  tide[t].mask = np.any(hc.mask,axis=1)
639
648
  tide[t].data[:] = pyTMD.predict.drift(ts.tide, hc, c,
640
649
  deltat=deltat, corrections=nodal_corrections)
641
- # calculate values for minor constituents by inferrence
650
+ # calculate values for minor constituents by inference
642
651
  if INFER_MINOR:
643
652
  minor = pyTMD.predict.infer_minor(ts.tide, hc, c,
644
653
  deltat=deltat, corrections=nodal_corrections,
@@ -652,7 +661,7 @@ def tide_currents(
652
661
  HC = hc[s,None,:]
653
662
  TIDE = pyTMD.predict.time_series(ts.tide, HC, c,
654
663
  deltat=deltat, corrections=nodal_corrections)
655
- # calculate values for minor constituents by inferrence
664
+ # calculate values for minor constituents by inference
656
665
  if INFER_MINOR:
657
666
  MINOR = pyTMD.predict.infer_minor(ts.tide, HC, c,
658
667
  deltat=deltat, corrections=nodal_corrections,
@@ -59,7 +59,7 @@ PROGRAM DEPENDENCIES:
59
59
  interpolate.py: interpolation routines for spatial data
60
60
 
61
61
  UPDATE HISTORY:
62
- Updated 01/2024: made the inferrence of minor constituents an option
62
+ Updated 01/2024: made the inference of minor constituents an option
63
63
  refactored lunisolar ephemerides functions
64
64
  deprecated in favor of refactored compute.py module
65
65
  Updated 12/2023: use new crs class for coordinate reprojection
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python
2
+ u"""
3
+ ellipse.py
4
+ Written by Tyler Sutterley (05/2025)
5
+ Expresses the amplitudes and phases for the u and v components in terms of
6
+ four ellipse parameters using Foreman's formula
7
+
8
+ CALLING SEQUENCE:
9
+ umajor,uminor,uincl,uphase = pyTMD.ellipse.ellipse(u,v)
10
+
11
+ INPUTS:
12
+ u: zonal current (EW)
13
+ v: meridional current (NS)
14
+
15
+ OUTPUTS:
16
+ major: amplitude of the semimajor semi-axis
17
+ minor: amplitude of the semiminor semi-axis
18
+ incl: angle of inclination of the northern semimajor semi-axis
19
+ phase: phase lag of the maximum current behind the maximum tidal potential
20
+ of the individual constituent
21
+
22
+ REFERENCE:
23
+ M. G. G. Foreman and R. F. Henry, "The harmonic analysis of tidal model time
24
+ series", Advances in Water Resources, 12(3), 109-120, (1989).
25
+ https://doi.org/10.1016/0309-1708(89)90017-1
26
+
27
+ UPDATE HISTORY:
28
+ Updated 06/2025: added function to calculate x and y coordinates of ellipse
29
+ Updated 01/2024: added inverse function to get currents from parameters
30
+ use complex algebra to calculate tidal ellipse parameters
31
+ Updated 09/2023: renamed to ellipse.py (from tidal_ellipse.py)
32
+ Updated 03/2023: add basic variable typing to function inputs
33
+ Updated 04/2022: updated docstrings to numpy documentation format
34
+ Written 07/2020
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import numpy as np
39
+
40
+ __all__ = [
41
+ "ellipse",
42
+ "inverse",
43
+ "_xy"
44
+ ]
45
+
46
+ def ellipse(u: np.ndarray, v: np.ndarray):
47
+ """
48
+ Expresses the amplitudes and phases for the u and v components in terms of
49
+ four ellipse parameters using Foreman's formula :cite:p:`Foreman:1989dt`
50
+
51
+ Parameters
52
+ ----------
53
+ u: np.ndarray
54
+ zonal current (EW)
55
+ v: np.ndarray
56
+ meridional current (NS)
57
+
58
+ Returns
59
+ -------
60
+ major: np.ndarray
61
+ amplitude of the semi-major axis
62
+ minor: np.ndarray
63
+ amplitude of the semi-minor axis
64
+ incl: np.ndarray
65
+ angle of inclination of the northern semi-major axis
66
+ phase: np.ndarray
67
+ phase lag of the maximum current behind the maximum tidal potential
68
+ """
69
+ # validate inputs
70
+ u = np.atleast_1d(u)
71
+ v = np.atleast_1d(v)
72
+ # wp, wm: complex radius of positively and negatively rotating vectors
73
+ wp = (u + 1j*v)/2.0
74
+ wm = np.conj(u - 1j*v)/2.0
75
+ # ap, am: amplitudes of positively and negatively rotating vectors
76
+ ap = np.abs(wp)
77
+ am = np.abs(wm)
78
+ # ep, em: phases of positively and negatively rotating vectors
79
+ ep = np.angle(wp, deg=True)
80
+ em = np.angle(wm, deg=True)
81
+ # determine the amplitudes of the semimajor and semiminor axes
82
+ # using Foreman's formula
83
+ major = (ap + am)
84
+ minor = (ap - am)
85
+ # determine the inclination and phase using Foreman's formula
86
+ incl = (em + ep)/2.0
87
+ phase = (em - ep)/2.0
88
+ # adjust orientation of ellipse
89
+ k = (incl//180.0)
90
+ incl -= 180.0*k
91
+ phase += 180.0*k
92
+ phase = np.mod(phase, 360.0)
93
+ # return values
94
+ return (major, minor, incl, phase)
95
+
96
+ def inverse(
97
+ major: np.ndarray,
98
+ minor: np.ndarray,
99
+ incl: np.ndarray,
100
+ phase: np.ndarray
101
+ ):
102
+ """
103
+ Calculates currents u, v using the four tidal ellipse
104
+ parameters from Foreman's formula :cite:p:`Foreman:1989dt`
105
+
106
+ Parameters
107
+ ----------
108
+ major: np.ndarray
109
+ amplitude of the semi-major axis
110
+ minor: np.ndarray
111
+ amplitude of the semi-minor axis
112
+ incl: np.ndarray
113
+ angle of inclination of the northern semi-major axis
114
+ phase: np.ndarray
115
+ phase lag of the maximum current behind the maximum tidal potential
116
+
117
+ Returns
118
+ -------
119
+ u: np.ndarray
120
+ zonal current (EW)
121
+ v: np.ndarray
122
+ meridional current (NS)
123
+ """
124
+ # validate inputs
125
+ major = np.atleast_1d(major)
126
+ minor = np.atleast_1d(minor)
127
+ # convert inclination and phase to radians
128
+ incl = np.atleast_1d(incl)*np.pi/180.0
129
+ phase = np.atleast_1d(phase)*np.pi/180.0
130
+ # ep, em: phases of positively and negatively rotating vectors
131
+ ep = (incl - phase)
132
+ em = (incl + phase)
133
+ # ap, am: amplitudes of positively and negatively rotating vectors
134
+ ap = (major + minor)/2.0
135
+ am = (major - minor)/2.0
136
+ # wp, wm: complex radius of positively and negatively rotating vectors
137
+ wp = ap * np.exp(1j*ep)
138
+ wm = am * np.exp(1j*em)
139
+ # calculate complex currents
140
+ u = wp + np.conj(wm)
141
+ v = -1j*(wp - np.conj(wm))
142
+ # return values
143
+ return (u, v)
144
+
145
+ def _xy(
146
+ major: float | np.ndarray,
147
+ minor: float | np.ndarray,
148
+ incl: float | np.ndarray,
149
+ **kwargs
150
+ ):
151
+ """
152
+ Calculates the x and y coordinates of the tidal ellipse
153
+
154
+ Parameters
155
+ ----------
156
+ major: np.ndarray
157
+ amplitude of the semi-major axis
158
+ minor: np.ndarray
159
+ amplitude of the semi-minor axis
160
+ incl: np.ndarray
161
+ angle of inclination of the northern semi-major axis
162
+ phase: np.ndarray or None, default None
163
+ phase lag of the maximum current behind the maximum tidal potential
164
+ xy: tuple, default (0.0, 0.0)
165
+ center of the ellipse (x, y)
166
+ N: int or None, default None
167
+ number of points to calculate along the ellipse
168
+
169
+ Returns
170
+ -------
171
+ x: np.ndarray
172
+ x coordinates of the tidal ellipse
173
+ y: np.ndarray
174
+ y coordinates of the tidal ellipse
175
+ """
176
+ # set default number of points
177
+ kwargs.setdefault('phase', None)
178
+ kwargs.setdefault('xy', (0.0, 0.0))
179
+ kwargs.setdefault('N', 1000)
180
+ # validate inputs
181
+ phi = incl*np.pi/180.0
182
+ # calculate the angle of the ellipse
183
+ if kwargs['phase'] is not None:
184
+ # use the phase lag and inclination
185
+ th = (kwargs['phase'] + incl)*np.pi/180.0
186
+ else:
187
+ # use a full rotation
188
+ th = np.linspace(0, 2*np.pi, kwargs['N'])
189
+ # calculate x and y coordinates
190
+ x = kwargs['xy'][0] + \
191
+ major*np.cos(th)*np.cos(phi) - \
192
+ minor*np.sin(th)*np.sin(phi)
193
+ y = kwargs['xy'][1] + \
194
+ major*np.cos(th)*np.sin(phi) + \
195
+ minor*np.sin(th)*np.cos(phi)
196
+ return (x, y)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  u"""
3
3
  OTIS.py
4
- Written by Tyler Sutterley (12/2024)
4
+ Written by Tyler Sutterley (05/2025)
5
5
 
6
6
  Reads files for a tidal model and makes initial calculations to run tide program
7
7
  Includes functions to extract tidal harmonic constants from OTIS tide models for
@@ -36,6 +36,8 @@ OPTIONS:
36
36
  ATLAS: reading a global solution with localized solutions
37
37
  TMD3: combined global or local netCDF4 solution
38
38
  OTIS: combined global or local solution
39
+ constituents: list or None, default None
40
+ Specify constituents to read from model
39
41
  apply_flexure: apply ice flexure scaling factor to constituents
40
42
 
41
43
  OUTPUTS:
@@ -58,6 +60,7 @@ PROGRAM DEPENDENCIES:
58
60
  interpolate.py: interpolation routines for spatial data
59
61
 
60
62
  UPDATE HISTORY:
63
+ Updated 05/2025: added option to select constituents to read from model
61
64
  Updated 12/2024: released version of TMD3 has different variable names
62
65
  Updated 11/2024: expose buffer distance for cropping tide model data
63
66
  Updated 10/2024: save latitude and longitude to output constituent object
@@ -210,6 +213,8 @@ def extract_constants(
210
213
  - ``'ATLAS'``: reading a global solution with localized solutions
211
214
  - ``'OTIS'``: combined global or local solution
212
215
  - ``'TMD3'``: combined global or local netCDF4 solution
216
+ constituents: list or None, default None
217
+ Specify constituents to read from model
213
218
  crop: bool, default False
214
219
  Crop tide model data to (buffered) bounds
215
220
  bounds: list or NoneType, default None
@@ -245,6 +250,7 @@ def extract_constants(
245
250
  # set default keyword arguments
246
251
  kwargs.setdefault('type', 'z')
247
252
  kwargs.setdefault('grid', 'OTIS')
253
+ kwargs.setdefault('constituents', None)
248
254
  kwargs.setdefault('crop', False)
249
255
  kwargs.setdefault('bounds', None)
250
256
  kwargs.setdefault('buffer', None)
@@ -394,12 +400,19 @@ def extract_constants(
394
400
  elif kwargs['type'] in ('z','V','U'):
395
401
  unit_conv = 1.0
396
402
 
397
- # read and interpolate each constituent
403
+ # read list of constituents
398
404
  if isinstance(model_file,list):
399
- constituents = [read_constituents(m)[0].pop() for m in model_file]
400
- nc = len(constituents)
405
+ cons = [read_constituents(m)[0].pop() for m in model_file]
406
+ nc = len(cons)
401
407
  else:
402
- constituents,nc = read_constituents(model_file, grid=kwargs['grid'])
408
+ cons,nc = read_constituents(model_file, grid=kwargs['grid'])
409
+ # reduce number of constituents
410
+ if kwargs['constituents'] is not None:
411
+ # verify that constituents is a list
412
+ if isinstance(kwargs['constituents'], str):
413
+ kwargs['constituents'] = [kwargs['constituents']]
414
+ # number of constituents to read
415
+ nc = len(kwargs['constituents'])
403
416
 
404
417
  # number of output data points
405
418
  npts = len(D)
@@ -407,8 +420,17 @@ def extract_constants(
407
420
  amplitude.mask = np.zeros((npts,nc), dtype=bool)
408
421
  ph = np.ma.zeros((npts,nc))
409
422
  ph.mask = np.zeros((npts,nc), dtype=bool)
423
+ constituents = []
410
424
  # read and interpolate each constituent
411
- for i,c in enumerate(constituents):
425
+ for j in range(nc):
426
+ # if reading a specific constituent
427
+ if (kwargs['constituents'] is not None):
428
+ c = kwargs['constituents'][j]
429
+ else:
430
+ c = cons[j]
431
+ # find index of constituent in list
432
+ i = cons.index(c)
433
+ # read constituent for type
412
434
  if (kwargs['type'] == 'z'):
413
435
  # read z constituent from elevation file
414
436
  if (kwargs['grid'] == 'ATLAS'):
@@ -503,13 +525,15 @@ def extract_constants(
503
525
  is_geographic=is_geographic)
504
526
  # convert units
505
527
  # amplitude and phase of the constituent
506
- amplitude.data[:,i] = np.abs(hci.data)/unit_conv
507
- amplitude.mask[:,i] = np.copy(hci.mask)
508
- ph.data[:,i] = np.arctan2(-np.imag(hci), np.real(hci))
509
- ph.mask[:,i] = np.copy(hci.mask)
528
+ amplitude.data[:,j] = np.abs(hci.data)/unit_conv
529
+ amplitude.mask[:,j] = np.copy(hci.mask)
530
+ ph.data[:,j] = np.arctan2(-np.imag(hci), np.real(hci))
531
+ ph.mask[:,j] = np.copy(hci.mask)
510
532
  # update mask to invalidate points outside model domain
511
- ph.mask[:,i] |= invalid
512
- amplitude.mask[:,i] |= invalid
533
+ ph.mask[:,j] |= invalid
534
+ amplitude.mask[:,j] |= invalid
535
+ # append constituent to list
536
+ constituents.append(c)
513
537
 
514
538
  # convert phase to degrees
515
539
  phase = ph*180.0/np.pi
@@ -552,6 +576,8 @@ def read_constants(
552
576
  - ``'ATLAS'``: reading a global solution with localized solutions
553
577
  - ``'OTIS'``: combined global or local solution
554
578
  - ``'TMD3'``: combined global or local netCDF4 solution
579
+ constituents: list or None, default None
580
+ Specify constituents to read from model
555
581
  crop: bool, default False
556
582
  Crop tide model data to (buffered) bounds
557
583
  bounds: list or NoneType, default None
@@ -569,6 +595,7 @@ def read_constants(
569
595
  # set default keyword arguments
570
596
  kwargs.setdefault('type', 'z')
571
597
  kwargs.setdefault('grid', 'OTIS')
598
+ kwargs.setdefault('constituents', None)
572
599
  kwargs.setdefault('crop', False)
573
600
  kwargs.setdefault('bounds', None)
574
601
  kwargs.setdefault('buffer', 0)
@@ -664,18 +691,34 @@ def read_constants(
664
691
  gridx, gridy = np.meshgrid(xi, yi)
665
692
  lon, lat = crs.transform(gridx, gridy, direction='INVERSE')
666
693
 
667
- # read each constituent
694
+ # read list of constituents
668
695
  if isinstance(model_file, list):
669
696
  cons = [read_constituents(m)[0].pop() for m in model_file]
697
+ nc = len(cons)
670
698
  else:
671
- cons,_ = read_constituents(model_file, grid=kwargs['grid'])
699
+ cons,nc = read_constituents(model_file, grid=kwargs['grid'])
700
+ # reduce number of constituents
701
+ if kwargs['constituents'] is not None:
702
+ # verify that constituents is a list
703
+ if isinstance(kwargs['constituents'], str):
704
+ kwargs['constituents'] = [kwargs['constituents']]
705
+ # number of constituents to read
706
+ nc = len(kwargs['constituents'])
707
+
672
708
  # save output constituents and coordinate reference system
673
709
  constituents = pyTMD.io.constituents(x=xi, y=yi,
674
710
  bathymetry=bathymetry.data, mask=mask, crs=crs,
675
711
  longitude=lon, latitude=lat)
676
712
 
677
713
  # read each model constituent
678
- for i,c in enumerate(cons):
714
+ for j in range(nc):
715
+ # if reading a specific constituent
716
+ if (kwargs['constituents'] is not None):
717
+ c = kwargs['constituents'][j]
718
+ else:
719
+ c = cons[j]
720
+ # find index of constituent in list
721
+ i = cons.index(c)
679
722
  if (kwargs['type'] == 'z'):
680
723
  # read constituent from elevation file
681
724
  if (kwargs['grid'] == 'ATLAS'):