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 longitudesconstituents.py: calculates constituent parameters and nodal argumentsio.NOAA.py: query and parsing functions for NOAA webservices APIpredict.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]
)