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) Center for Operational Oceanographic Products and Services (CO-OPS) for the selected station.

The NOAA Tides and Currents API provides responses in xml format, which can be easily converted into pandas DataFrames.

Python Dependencies

Program Dependencies

  • astro.py: computes the basic astronomical mean longitudes

  • constituents.py: calculates constituent parameters and nodal arguments

  • io.NOAA.py: query and parsing functions for NOAA webservices API

  • predict.py: predict tidal values using harmonic constants

Note

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

import logging
import ipyleaflet
import ipywidgets
import pandas as pd
import matplotlib.dates as mdates
import matplotlib.pyplot as plt

# import tide programs
import pyTMD.io
import pyTMD.predict
import pyTMD.tools
import timescale.time

# create logger
logging.basicConfig(level=logging.INFO)

Query NOAA webservices for list of tide stations

# get list of active tide prediction stations
stations = pyTMD.io.NOAA.prediction_stations()

Create leaflet map for selecting NOAA station

%matplotlib widget
# widgets for selecting date range
TMDwidgets = pyTMD.tools.widgets()
# create dropdown with all (active) tide prediction stations
# use Ketchikan (AK) as default station
TMDwidgets.stations = ipywidgets.Dropdown(
    options=stations.index,
    value="Ketchikan",
    description="Tide Stations:",
    disabled=False,
    style=TMDwidgets.style,
)
# create date pickers for start and end dates
# use previous 2 days of predictions as a default
end_date = pd.Timestamp.now().floor(freq="d")
start_date = end_date - pd.Timedelta("2 day")
# create date pickers for start and end dates
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,
)

# create leaflet map
station_row = stations.iloc[TMDwidgets.stations.index]
m = pyTMD.tools.leaflet(
    center=(station_row.lat, station_row.long),
    zoom=7,
    layer_control=False,
    attribution=False,
)

# create empty plot for tide heights
fig, ax1 = plt.subplots(num=1)
(l1,) = ax1.plot([], [], color="mediumseagreen", label="MSL")
fb = ax1.fill_between([], [], [], zorder=1, color=l1.get_color(), alpha=0.35)
(l2,) = ax1.plot([], [], color="darkorchid", label="Tides")
ax1.grid(linestyle="-", axis="x")
date_formatter = mdates.DateFormatter("%Y-%m-%d")
ax1.xaxis.set_major_formatter(date_formatter)
ax1.set_xlabel("Time [UTC]")
ax1.set_ylabel("Water Level Height [m]")
axtitle = ax1.set_title(None)
lgd = ax1.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)
fig.subplots_adjust(left=0.15, right=0.98, bottom=0.10, top=0.95)
fig.autofmt_xdate()


# calculate tide prediction and plot
def update_tide_prediction(*args, **kwargs):
    # 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
    startdate = TMDwidgets.start_date.value.strftime("%Y%m%d")
    enddate = TMDwidgets.end_date.value.strftime("%Y%m%d")
    # get harmonic constituents for station
    hcons = pyTMD.io.NOAA.harmonic_constituents(stationId=station_id)
    # get water levels for station and date range
    api = "waterlevelrawsixmin"
    wlevel = pyTMD.io.NOAA.water_level(
        api, stationId=station_id, beginDate=startdate, endDate=enddate
    )
    # predict tides at water level timestamps
    ts = timescale.from_datetime(wlevel.timeStamp)
    tpred = hcons.tmd.predict(ts.tide, deltat=ts.tt_ut1, corrections="GOT")
    # infer minor constituents and add to major components
    tpred += hcons.tmd.infer(ts.tide, deltat=ts.tt_ut1, corrections="GOT")
    # update plot with predicted values
    l1.set_data(wlevel.timeStamp, wlevel.WL)
    fb.set_data(
        wlevel.timeStamp, wlevel.WL - wlevel.sigma, wlevel.WL + wlevel.sigma
    )
    l2.set_data(wlevel.timeStamp, tpred)
    # update plot title
    axtitle.set_text(f"{station_name} (id: {station_id})")
    # rescale and redraw figure
    ax1.relim()
    ax1.autoscale_view()
    fig.canvas.draw()


# update map center if station name is changed
def update_map_origin(*args, **kwargs):
    """callback for handling station changes"""
    station_row = stations.iloc[TMDwidgets.stations.index]
    m.map.center = (station_row.lat, station_row.long)


# set station name when feature is clicked
def handle_click(feature, **kwargs):
    """callback for handling mouse clicks"""
    TMDwidgets.stations.value = feature["properties"]["name"]


# create tooltip when feature is hovered
def handle_hover(feature, **kwargs):
    """callback for creating hover tooltips"""
    prop = feature["properties"]
    m.tooltip.value = "<br>".join([f"{k}: {prop[k]}" for k in ["name", "ID"]])
    m.tooltip.layout.height = "40px"
    m.tooltip.layout.visibility = "visible"
    m.map.add(m.hover_control)


# remove tooltip upon mouseout of feature
def handle_mouseout(_, content, buffers):
    """callback for removing hover tooltips upon mouseout"""
    event_type = content.get("type", "")
    if event_type == "mouseout":
        m.tooltip.value = ""
        m.tooltip.layout.visibility = "hidden"
        m.map.remove(m.hover_control)


# run for default station
update_tide_prediction()

# create GeoJSON with data for each station
GeoJSON = {"type": "FeatureCollection", "features": []}
for i, row in stations.iterrows():
    feature = {"type": "Feature"}
    feature["properties"] = {"name": row.name, "ID": row.ID}
    feature["geometry"] = {"type": "Point", "coordinates": [row.long, row.lat]}
    GeoJSON["features"].append(feature)

# create ipyleaflet GeoJSON layer
m.geojson = ipyleaflet.GeoJSON(
    data=GeoJSON,
    point_style={"radius": 2.0, "fillOpacity": 0.5, "weight": 3.0},
    hover_style={"color": "yellow"},
)

# create tooltip for station names
m.tooltip = ipywidgets.HTML()
m.tooltip.layout.margin = "0px 20px 20px 20px"
m.tooltip.layout.visibility = "hidden"
# create widget for hover tooltips
m.hover_control = ipyleaflet.WidgetControl(
    widget=m.tooltip, position="bottomright"
)

# handle interactions with GeoJSON points
# set station name upon click
m.geojson.on_click(handle_click)
# show station name upon hover
m.geojson.on_hover(handle_hover)
m.geojson.on_msg(handle_mouseout)
# update tide predictions for NOAA station change
TMDwidgets.stations.observe(update_tide_prediction)
# update tide predictions for date range change
TMDwidgets.start_date.observe(update_tide_prediction)
TMDwidgets.end_date.observe(update_tide_prediction)
TMDwidgets.stations.observe(update_map_origin)
# add geojson of stations to map
m.map.add(m.geojson)
# display widgets
TMDwidgets.VBox(
    [m.map, TMDwidgets.stations, TMDwidgets.start_date, TMDwidgets.end_date]
)