Skip to content

Commit e38d049

Browse files
authored
feat: functions to read and interpolate from all constituents to address #91 (#137)
* feat: add functions to read and interpolate from all constituents to address #91 * test: switch interpolation test to soft tabs * test: add tests for io methods * feat: update forecast notebook with dynamic plotting
1 parent dc6473a commit e38d049

21 files changed

+2058
-366
lines changed

doc/source/api_reference/io/ATLAS.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ Calling Sequence
2020

2121
.. autofunction:: pyTMD.io.ATLAS.extract_constants
2222

23+
.. autofunction:: pyTMD.io.ATLAS.read_constants
24+
25+
.. autofunction:: pyTMD.io.ATLAS.interpolate_constants
26+
2327
.. autofunction:: pyTMD.io.ATLAS.read_netcdf_grid
2428

2529
.. autofunction:: pyTMD.io.ATLAS.read_netcdf_elevation

doc/source/api_reference/io/FES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ Calling Sequence
2727

2828
.. autofunction:: pyTMD.io.FES.extract_constants
2929

30+
.. autofunction:: pyTMD.io.FES.read_constants
31+
32+
.. autofunction:: pyTMD.io.FES.interpolate_constants
33+
3034
.. autofunction:: pyTMD.io.FES.read_ascii_file
3135

3236
.. autofunction:: pyTMD.io.FES.read_netcdf_file

doc/source/api_reference/io/GOT.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ Calling Sequence
2020

2121
.. autofunction:: pyTMD.io.GOT.extract_constants
2222

23+
.. autofunction:: pyTMD.io.GOT.read_constants
24+
25+
.. autofunction:: pyTMD.io.GOT.interpolate_constants
26+
2327
.. autofunction:: pyTMD.io.GOT.read_ascii_file
2428

2529
.. autofunction:: pyTMD.io.GOT.extend_array

doc/source/api_reference/io/OTIS.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ Calling Sequence
2525

2626
.. autofunction:: pyTMD.io.OTIS.extract_constants
2727

28+
.. autofunction:: pyTMD.io.OTIS.read_constants
29+
30+
.. autofunction:: pyTMD.io.OTIS.interpolate_constants
31+
2832
.. autofunction:: pyTMD.io.OTIS.read_otis_grid
2933

3034
.. autofunction:: pyTMD.io.OTIS.read_atlas_grid
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
===============
2+
io.constituents
3+
===============
4+
5+
Basic tide model constituent class
6+
7+
`Source code`__
8+
9+
.. __: https://github.com/tsutterley/pyTMD/blob/main/pyTMD/io/constituents.py
10+
11+
General Attributes and Methods
12+
==============================
13+
14+
.. autoclass:: pyTMD.io.constituents
15+
:members:

doc/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ conventions for calculating radial pole tide displacements.
3939
api_reference/io/FES.rst
4040
api_reference/io/GOT.rst
4141
api_reference/io/OTIS.rst
42+
api_reference/io/constituents.rst
4243
api_reference/io/ocean_pole_tide.rst
4344
api_reference/load_constituent.rst
4445
api_reference/load_nodal_corrections.rst

notebooks/Plot Tide Forecasts.ipynb

Lines changed: 100 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"cells": [
33
{
4+
"attachments": {},
45
"cell_type": "markdown",
56
"metadata": {},
67
"source": [
@@ -35,8 +36,10 @@
3536
"- `load_nodal_corrections.py`: load the nodal corrections for tidal constituents \n",
3637
"- `model.py`: retrieves tide model parameters for named tide models\n",
3738
"- `io.OTIS.py`: extract tidal harmonic constants from OTIS tide models \n",
38-
"- `io.ATLAS.py`: extract tidal harmonic constants from ATLAS netcdf models \n",
39+
"- `io.ATLAS.py`: extract tidal harmonic constants from ATLAS netCDF4 tide models \n",
40+
"- `io.GOT.py`: extract tidal harmonic constants from GOT tide models \n",
3941
"- `io.FES.py`: extract tidal harmonic constants from FES tide models \n",
42+
"- `io.constituents.py`: basic tide model constituent class \n",
4043
"- `predict.py`: predict tidal values using harmonic constants \n",
4144
"- `time.py`: utilities for calculating time operations\n",
4245
"- `utilities.py`: download and management utilities for files\n",
@@ -120,6 +123,8 @@
120123
"metadata": {},
121124
"outputs": [],
122125
"source": [
126+
"%matplotlib widget\n",
127+
"\n",
123128
"# get model parameters\n",
124129
"model = pyTMD.model(TMDwidgets.directory.value,\n",
125130
" format=TMDwidgets.atlas.value,\n",
@@ -130,94 +135,122 @@
130135
"YMD = TMDwidgets.datepick.value\n",
131136
"# calculate a weeks forecast every minute\n",
132137
"minutes = np.arange(7*1440)\n",
138+
"# convert time from MJD to days relative to Jan 1, 1992 (48622 MJD)\n",
133139
"tide_time = pyTMD.time.convert_calendar_dates(YMD.year, YMD.month,\n",
134140
" YMD.day, minute=minutes)\n",
135141
"hours = minutes/60.0\n",
142+
"\n",
143+
"# create plot with tidal displacements, high and low tides and dates\n",
144+
"fig,ax1 = plt.subplots(num=1)\n",
145+
"xmax = np.ceil(hours[-1]).astype('i')\n",
146+
"l1, = ax1.plot([], [], 'k')\n",
147+
"l2, = ax1.plot([], [], 'r*')\n",
148+
"l3, = ax1.plot([], [], 'b*')\n",
149+
"for h in range(24,xmax,24):\n",
150+
" ax1.axvline(h,color='gray',lw=0.5,ls='dashed',dashes=(11,5))\n",
151+
"ax1.set_xlim(0,xmax)\n",
152+
"ax1.set_ylabel(f'{model.name} Tidal Displacement [cm]')\n",
153+
"args = (YMD.year,YMD.month,YMD.day)\n",
154+
"ax1.set_xlabel('Time from {0:4d}-{1:02d}-{2:02d} UTC [Hours]'.format(*args))\n",
155+
"ttl = ax1.set_title(None)\n",
156+
"fig.subplots_adjust(left=0.10,right=0.98,bottom=0.10,top=0.95)\n",
157+
"\n",
136158
"# delta time (TT - UT1) file\n",
137159
"delta_file = pyTMD.utilities.get_data_path(['data','merged_deltat.data'])\n",
138160
"\n",
139-
"# leaflet location\n",
140-
"LAT,LON = np.copy(m.marker.location)\n",
141-
"# verify longitudes\n",
142-
"LON = m.wrap_longitudes(LON)\n",
143161
"# read tidal constants and interpolate to leaflet points\n",
144162
"if model.format in ('OTIS','ATLAS','ESR'):\n",
145-
" amp,ph,D,c = pyTMD.io.OTIS.extract_constants(np.atleast_1d(LON),\n",
146-
" np.atleast_1d(LAT), model.grid_file, model.model_file,\n",
147-
" model.projection, type=model.type, method='spline',\n",
148-
" extrapolate=True, grid=model.format)\n",
163+
" constituents = pyTMD.io.OTIS.read_constants(\n",
164+
" model.grid_file, model.model_file,\n",
165+
" model.projection, type=model.type,\n",
166+
" grid=model.format)\n",
167+
" c = constituents.fields\n",
149168
" DELTAT = np.zeros_like(tide_time)\n",
150169
"elif (model.format == 'netcdf'):\n",
151-
" amp,ph,D,c = pyTMD.io.ATLAS.extract_constants(np.atleast_1d(LON),\n",
152-
" np.atleast_1d(LAT), model.grid_file, model.model_file,\n",
153-
" type=model.type, method='spline', extrapolate=True,\n",
154-
" scale=model.scale, compressed=model.compressed)\n",
170+
" constituents = pyTMD.io.ATLAS.read_constants(\n",
171+
" model.grid_file, model.model_file,\n",
172+
" type=model.type, compressed=model.compressed)\n",
173+
" c = constituents.fields\n",
155174
" DELTAT = np.zeros_like(tide_time)\n",
156175
"elif (model.format == 'GOT'):\n",
157-
" amp,ph,c = pyTMD.io.GOT.extract_constants(np.atleast_1d(LON),\n",
158-
" np.atleast_1d(LAT), model.model_file, method='spline',\n",
159-
" extrapolate=True, scale=model.scale,\n",
160-
" compressed=model.compressed)\n",
176+
" constituents = pyTMD.io.GOT.read_constants(\n",
177+
" model.model_file, compressed=model.compressed)\n",
178+
" c = constituents.fields\n",
161179
" # interpolate delta times from calendar dates to tide time\n",
162180
" DELTAT = pyTMD.time.interpolate_delta_time(delta_file, tide_time)\n",
163181
"elif (model.format == 'FES'):\n",
164-
" amp,ph = pyTMD.io.FES.extract_constants(np.atleast_1d(LON),\n",
165-
" np.atleast_1d(LAT), model.model_file, type=model.type,\n",
166-
" version=model.version, method='spline', extrapolate=True,\n",
167-
" scale=model.scale, compressed=model.compressed)\n",
182+
" constituents = pyTMD.io.FES.read_constants(model.model_file,\n",
183+
" type=model.type, version=model.version,\n",
184+
" compressed=model.compressed)\n",
168185
" c = model.constituents\n",
169186
" # interpolate delta times from calendar dates to tide time\n",
170187
" DELTAT = pyTMD.time.interpolate_delta_time(delta_file, tide_time)\n",
171188
"\n",
172-
"# calculate complex phase in radians for Euler's\n",
173-
"cph = -1j*ph*np.pi/180.0\n",
174-
"# calculate constituent oscillation\n",
175-
"hc = amp*np.exp(cph)\n",
189+
"# update the tide prediction and plot\n",
190+
"def update_tide_prediction(*args):\n",
191+
" # leaflet location\n",
192+
" LAT,LON = np.copy(m.marker.location)\n",
193+
" # verify longitudes\n",
194+
" LON = m.wrap_longitudes(LON)\n",
195+
" if model.format in ('OTIS','ATLAS','ESR'):\n",
196+
" amp,ph,D = pyTMD.io.OTIS.interpolate_constants(\n",
197+
" np.atleast_1d(LON), np.atleast_1d(LAT),\n",
198+
" constituents, model.projection, type=model.type,\n",
199+
" method='spline', extrapolate=True)\n",
200+
" elif (model.format == 'netcdf'):\n",
201+
" amp,ph,D = pyTMD.io.ATLAS.interpolate_constants(\n",
202+
" np.atleast_1d(LON), np.atleast_1d(LAT),\n",
203+
" constituents, type=model.type, scale=model.scale,\n",
204+
" method='spline', extrapolate=True)\n",
205+
" elif (model.format == 'GOT'):\n",
206+
" amp,ph = pyTMD.io.GOT.interpolate_constants(\n",
207+
" np.atleast_1d(LON), np.atleast_1d(LAT),\n",
208+
" constituents, scale=model.scale,\n",
209+
" method='spline', extrapolate=True)\n",
210+
" elif (model.format == 'FES'):\n",
211+
" amp,ph = pyTMD.io.FES.interpolate_constants(\n",
212+
" np.atleast_1d(LON), np.atleast_1d(LAT),\n",
213+
" constituents, scale=model.scale,\n",
214+
" method='spline', extrapolate=True)\n",
215+
" # calculate complex phase in radians for Euler's\n",
216+
" cph = -1j*ph*np.pi/180.0\n",
217+
" # calculate constituent oscillation\n",
218+
" hc = amp*np.exp(cph)\n",
219+
" # predict tidal elevations at time 1 and infer minor corrections\n",
220+
" TIDE = pyTMD.predict.time_series(tide_time, hc, c,\n",
221+
" deltat=DELTAT, corrections=model.format)\n",
222+
" MINOR = pyTMD.predict.infer_minor(tide_time, hc, c,\n",
223+
" deltat=DELTAT, corrections=model.format)\n",
224+
" TIDE.data[:] += MINOR.data[:]\n",
225+
" # convert to centimeters\n",
226+
" TIDE.data[:] *= 100.0\n",
176227
"\n",
177-
"# convert time from MJD to days relative to Jan 1, 1992 (48622 MJD)\n",
178-
"# predict tidal elevations at time 1 and infer minor corrections\n",
179-
"TIDE = pyTMD.predict.time_series(tide_time, hc, c,\n",
180-
" deltat=DELTAT, corrections=model.format)\n",
181-
"MINOR = pyTMD.predict.infer_minor(tide_time, hc, c,\n",
182-
" deltat=DELTAT, corrections=model.format)\n",
183-
"TIDE.data[:] += MINOR.data[:]\n",
184-
"# convert to centimeters\n",
185-
"TIDE.data[:] *= 100.0\n",
186-
"\n",
187-
"# differentiate to calculate high and low tides\n",
188-
"diff = np.zeros_like(tide_time, dtype=np.float64)\n",
189-
"# forward differentiation for starting point\n",
190-
"diff[0] = TIDE.data[1] - TIDE.data[0]\n",
191-
"# backward differentiation for end point\n",
192-
"diff[-1] = TIDE.data[-1] - TIDE.data[-2]\n",
193-
"# centered differentiation for all others\n",
194-
"diff[1:-1] = (TIDE.data[2:] - TIDE.data[0:-2])/2.0\n",
195-
"# indices of high and low tides\n",
196-
"htindex, = np.nonzero((np.sign(diff[0:-1]) >= 0) & (np.sign(diff[1:]) < 0))\n",
197-
"ltindex, = np.nonzero((np.sign(diff[0:-1]) <= 0) & (np.sign(diff[1:]) > 0))\n",
228+
" # differentiate to calculate high and low tides\n",
229+
" diff = np.zeros_like(tide_time, dtype=np.float64)\n",
230+
" # forward differentiation for starting point\n",
231+
" diff[0] = TIDE.data[1] - TIDE.data[0]\n",
232+
" # backward differentiation for end point\n",
233+
" diff[-1] = TIDE.data[-1] - TIDE.data[-2]\n",
234+
" # centered differentiation for all others\n",
235+
" diff[1:-1] = (TIDE.data[2:] - TIDE.data[0:-2])/2.0\n",
236+
" # indices of high and low tides\n",
237+
" htindex, = np.nonzero((np.sign(diff[0:-1]) >= 0) & (np.sign(diff[1:]) < 0))\n",
238+
" ltindex, = np.nonzero((np.sign(diff[0:-1]) <= 0) & (np.sign(diff[1:]) > 0))\n",
239+
" # update plot data\n",
240+
" l1.set_data(hours, TIDE.data)\n",
241+
" l2.set_data(hours[htindex], TIDE.data[htindex])\n",
242+
" l3.set_data(hours[ltindex], TIDE.data[ltindex])\n",
243+
" # update plot title\n",
244+
" ttl.set_text(u'{0:0.6f}\\u00b0N {1:0.6f}\\u00b0W'.format(LAT,LON))\n",
245+
" ax1.relim()\n",
246+
" ax1.autoscale_view()\n",
247+
" fig.canvas.draw()\n",
198248
"\n",
199-
"# create plot with tidal displacements, high and low tides and dates\n",
200-
"fig,ax1 = plt.subplots(num=1)\n",
201-
"xmax = np.ceil(hours[-1]).astype('i')\n",
202-
"ax1.plot(hours, TIDE.data, 'k')\n",
203-
"ax1.plot(hours[htindex], TIDE.data[htindex], 'r*')\n",
204-
"ax1.plot(hours[ltindex], TIDE.data[ltindex], 'b*')\n",
205-
"for h in range(24,xmax,24):\n",
206-
" ax1.axvline(h,color='gray',lw=0.5,ls='dashed',dashes=(11,5))\n",
207-
"ax1.set_xlim(0,xmax)\n",
208-
"ax1.set_ylabel(f'{model.name} Tidal Displacement [cm]')\n",
209-
"args = (YMD.year,YMD.month,YMD.day)\n",
210-
"ax1.set_xlabel('Time from {0:4d}-{1:02d}-{2:02d} UTC [Hours]'.format(*args))\n",
211-
"ax1.set_title(u'{0:0.6f}\\u00b0N {1:0.6f}\\u00b0W'.format(LAT,LON))\n",
212-
"fig.subplots_adjust(left=0.10,right=0.98,bottom=0.10,top=0.95)"
249+
"# run tide prediction at initial location\n",
250+
"update_tide_prediction()\n",
251+
"# watch marker location for changes\n",
252+
"m.marker_text.observe(update_tide_prediction)"
213253
]
214-
},
215-
{
216-
"cell_type": "code",
217-
"execution_count": null,
218-
"metadata": {},
219-
"outputs": [],
220-
"source": []
221254
}
222255
],
223256
"metadata": {

pyTMD/compute_tide_corrections.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,12 +292,14 @@ def compute_tide_corrections(x, y, delta_time, DIRECTORY=None, MODEL=None,
292292
model.model_file, model.projection, type=model.type,
293293
method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
294294
grid=model.format, apply_flexure=APPLY_FLEXURE)
295+
# use delta time at 2000.0 to match TMD outputs
295296
deltat = np.zeros_like(t)
296297
elif (model.format == 'netcdf'):
297298
amp,ph,D,c = pyTMD.io.ATLAS.extract_constants(lon, lat, model.grid_file,
298299
model.model_file, type=model.type, method=METHOD,
299300
extrapolate=EXTRAPOLATE, cutoff=CUTOFF, scale=model.scale,
300301
compressed=model.compressed)
302+
# use delta time at 2000.0 to match TMD outputs
301303
deltat = np.zeros_like(t)
302304
elif (model.format == 'GOT'):
303305
amp,ph,c = pyTMD.io.GOT.extract_constants(lon, lat, model.model_file,

0 commit comments

Comments
 (0)