Compare NOAA Tides

This (notebook) downloads and plots measurements and forecasts from NOAA Tides and Currents. Predictions use the harmonic constituents provided by the National Oceanic and Atmospheric Administration (NOAA) for the selected station.

Python Dependencies

Program Dependencies

  • arguments.py: load the nodal corrections for tidal constituents

  • astro.py: computes the basic astronomical mean longitudes

  • io.constituents.py: basic tide model constituent class

  • predict.py: predict tidal values using harmonic constants

  • time.py: utilities for calculating time operations

This notebook uses Jupyter widgets to set parameters for calculating the tidal time series.

import pyTMD
import pandas
import logging
import timescale
import traceback
import ipywidgets
import numpy as np
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
# create logger
logging.basicConfig(level=logging.INFO)

Functions for querying NOAA webservices

def build_query(api, **kwargs):
    # NOAA webservices hosts
    HOST = 'https://tidesandcurrents.noaa.gov/axis/webservices'
    OPENDAP = 'https://opendap.co-ops.nos.noaa.gov/axis/webservices'
    # NOAA webservices query arguments
    arguments = '?format=xml'
    for key, value in kwargs.items():
        arguments += f'&{key}={value}'
    arguments += '&Submit=Submit'
    # NOAA API query url
    url = f'{HOST}/{api}/response.jsp{arguments}'
    # lxml namespaces for parsing
    namespaces = {}
    namespaces['wsdl'] = f'{OPENDAP}/{api}/wsdl'
    return (url, namespaces)

def from_xml(url, **kwargs):
    # query the NOAA webservices API
    try:
        logging.debug(url)
        df = pandas.read_xml(url, **kwargs)
    except ValueError:
        logging.error(traceback.format_exc())
    # return the dataframe
    else:
        return df

Query NOAA webservices for list of tide stations

xpath = '//wsdl:station'
url, namespaces = build_query('tidepredictionstations')
stations = from_xml(url, xpath=xpath, namespaces=namespaces).set_index('name')
stations = stations.sort_index().drop(columns=['metadata'])

Select NOAA station and dates for prediction

# display widgets
TMDwidgets = pyTMD.tools.widgets()
# create dropdown with all tide prediction station data
TMDwidgets.stations = ipywidgets.Dropdown(
    options=stations.index,
    value='La Jolla (Scripps Institution Wharf)',
    description='Tide Stations:',
    disabled=False,
    style=TMDwidgets.style,
)
# create date pickers for start and end dates
end_date = pandas.Timestamp.now().floor(freq='d')
start_date = end_date - pandas.Timedelta('2 day')
TMDwidgets.start_date = ipywidgets.DatePicker(
    description='Start Date:',
    value=start_date,
    disabled=False,
    style=TMDwidgets.style,
)
TMDwidgets.end_date = ipywidgets.DatePicker(
    description='End Date:',
    value=end_date,
    disabled=False,
    style=TMDwidgets.style,
)
# display widgets
TMDwidgets.VBox([
    TMDwidgets.stations,
    TMDwidgets.start_date,
    TMDwidgets.end_date
])

Predict tidal time series

# get station name and indicator
station_row = stations.iloc[TMDwidgets.stations.index]
station_name = station_row.name
station_id = station_row.ID
# set query parameters
xpath = '//wsdl:item'
unit = 0
timeZone = 0
startdate = TMDwidgets.start_date.value.strftime('%Y%m%d')
enddate = TMDwidgets.end_date.value.strftime('%Y%m%d')
datum = 'MSL'

# get harmonic constituents for station
url, namespaces = build_query('harmonicconstituents',
    stationId=station_id, unit=unit, timeZone=timeZone)
hcons = from_xml(url, xpath=xpath, namespaces=namespaces)

# get water levels for station and date range
url, namespaces = build_query('waterlevelrawsixmin',
    stationId=station_id, unit=unit, timeZone=timeZone,
    beginDate=startdate, endDate=enddate, datum=datum)
wlevel = from_xml(url, xpath=xpath, namespaces=namespaces,
    parse_dates=['timeStamp'])

# parse harmonic constituents
c = [pyTMD.io.constituents.parse(row['name']) for i, row in hcons.iterrows()]
# calculate complex phase in radians for Euler's
cph = -1j*hcons.phase*np.pi/180.0
# calculate constituent oscillation
hc = hcons.amplitude*np.exp(cph)

# predict tides at water level timestamps
ts = timescale.from_datetime(wlevel.timeStamp)
TIDE = pyTMD.predict.time_series(ts.tide, hc.values, c,
    deltat=ts.tt_ut1, corrections='GOT')
# infer minor tidal constituents
TIDE += pyTMD.predict.infer_minor(ts.tide, hc.values, c,
    deltat=ts.tt_ut1, corrections='GOT')

Compare measured tide values with predictions

fig, ax = plt.subplots(num=1)
ax.plot(wlevel.timeStamp, wlevel.WL, color='mediumseagreen', label='MSL')
ax.fill_between(wlevel.timeStamp, wlevel.WL-wlevel.sigma,
    y2=wlevel.WL+wlevel.sigma, zorder=1,
    color='mediumseagreen', alpha=0.35)
ax.plot(wlevel.timeStamp, TIDE, color='darkorchid', label='Tides')
ax.grid(linestyle='-', axis='x')
ax.set_title(station_name)
ax.set_ylabel('Water Level Height [m]')
lgd = ax.legend(frameon=True)
lgd.get_frame().set_boxstyle('square,pad=0.0')
lgd.get_frame().set_edgecolor("white")
lgd.get_frame().set_alpha(1.0)
for line in lgd.get_lines():
    line.set_linewidth(6)
date_formatter = mdates.DateFormatter("%Y-%m-%d %H:%M")
ax.xaxis.set_major_formatter(date_formatter)
fig.autofmt_xdate()
plt.show()