Near real-time HF-Radar currents in the proximity of the Deepwater Horizon site

Near real-time HF-Radar currents in the proximity of the Deepwater Horizon site#

Created: 2017-07-25

Updated: 2026-05-01

The explosion on the Deepwater Horizon (DWH) tragically killed 11 people, and resulted in one of the largest marine oil spills in history. One of the first questions when there is such a tragedy is: where will the oil go if that happened today?

In order the help answer that question one can use near real time currents from the HF-Radar sites near the incident.

First let’s start with the HF-Radar site, where one can browse the available data interactively. Below we show an IFrame for the site.

In this notebook we will demonstrate how to obtain such data programmatically.

(For more information on the DWH see http://response.restoration.noaa.gov/oil-and-chemical-spills/significant-incidents/deepwater-horizon-oil-spill.)

from IPython.display import IFrame

url = "https://hfradar.ioos.us/hfrnet/"

IFrame(
    src=url,
    width=750,
    height=450,
)

The interactive interface is handy for exploration but we usually need to download “mechanically” in order to use them in our analysis, plots, or for downloading time-series.

One way to achieve that is to use an OPeNDAP client, here Python’s xarray, and explore the endpoints directly. The NCEI hfradar catalog is organized in folders and subfolders by year, month, region and then individual files that contains the datetime info and data resolution. The regions are:

  • USWC → West Coast

  • USHI → Hawaii

  • USEGC → US East and Gulf Coasts

  • PRVI → Puerto Rico/Virgin Islands

  • AKNS → Alaska North Slope

  • GLNA → Great Lakes North America

  • GAK → Gulf of Alaska

Note that the region folders and file names can change from year to year. We will download data around 2017-07-25, the original data when this notebooks was first posted.

import xarray as xr

year, month = "2017", "07"
days = ["24", "25", "26"]
times = [f"{n:04}" for n in list(range(0, 2400, 100))]

urls = []
for day in days:
    for time in times:
        url = (
            "https://www.ncei.noaa.gov/thredds-ocean/dodsC/ioos/hfradar/rtv/"
            f"{year}/{year}{month}/USEGC/{year}{month}{day}{time}_hfr_usegc_6km_rtv_uwls_NDBC.nc"
        )
        urls.append(url)


# We need to use `preprocess` b/c not all coords, like `nSites`,
# align properly.
ds = xr.open_mfdataset(urls, preprocess=lambda ds: ds[["u", "v"]])
ds
<xarray.Dataset> Size: 186MB
Dimensions:  (time: 72, lat: 460, lon: 701)
Coordinates:
  * time     (time) datetime64[ns] 576B 2017-07-24 ... 2017-07-26T23:00:00
  * lat      (lat) float32 2kB 21.74 21.79 21.84 21.9 ... 46.39 46.44 46.49
  * lon      (lon) float32 3kB -97.88 -97.83 -97.77 ... -57.35 -57.29 -57.23
Data variables:
    u        (time, lat, lon) float32 93MB dask.array<chunksize=(1, 460, 701), meta=np.ndarray>
    v        (time, lat, lon) float32 93MB dask.array<chunksize=(1, 460, 701), meta=np.ndarray>
Attributes: (12/23)
    netcdf_library_version:          4.1.3
    format_version:                  HFRNet_1.0.0
    product_version:                 HFRNet_1.1.05
    Conventions:                     CF-1.1
    title:                           Near-Real Time Surface Ocean Velocity, U...
    institution:                     National Data Buoy Center
    ...                              ...
    grid_resolution:                 6km
    grid_projection:                 equidistant cylindrical
    regional_description:            Unites States, East and Gulf Coast
    DODS.strlen:                     25
    DODS.dimName:                    nSites_maxStrlen
    DODS_EXTRA.Unlimited_Dimension:  time

How about extracting a day time-series from the dataset and then averaged around the area of interest?

dx = dy = 2.25  # Area around the point of interest.
center = -87.373643, 29.061888  # Point of interest.
dsr = ds.sel(
    lon=(ds.lon < center[0] + dx) & (ds.lon > center[0] - dx),
    lat=(ds.lat < center[1] + dy) & (ds.lat > center[1] - dy),
)

avg = dsr.mean(dim=["lon", "lat"])
time = avg["time"].to_index().to_pydatetime()

Now all we have to do is mask the missing data with NaNs and average over the area.

import numpy.ma as ma

v = avg["v"].data
u = avg["u"].data

u = ma.masked_invalid(u)
v = ma.masked_invalid(v)
import matplotlib.pyplot as plt
from oceans.plotting import stick_plot

fig, ax = plt.subplots(figsize=(11, 2.75))

q = stick_plot(time, u, v, ax=ax)

ref = 0.5
qk = plt.quiverkey(
    q,
    0.1,
    0.85,
    ref,
    "{} {}".format(ref, ds["u"].units),
    labelpos="N",
    coordinates="axes",
)

_ = plt.xticks(rotation=70)
../../../_images/faca0aa234c67101f00c29de2317c3690a84cafe08dc9cc843c8d67148c23576.png

To close this post let’s us reproduce the HF radar image from the interactive site using yesterday’s data and the NRT endpoint from NDBC.

from datetime import date, timedelta

ds = xr.open_dataset(
    "https://dods.ndbc.noaa.gov/thredds/dodsC/hfradar_usegc_6km_25hravg"
)
yesterday = date.today() - timedelta(days=1)
dsy = ds.sel(time=yesterday, method="nearest")

Now that we singled out the date and and time we want the data, we trigger the download by accessing the data with xarray’s .data property.

u = dsy["u_mean"].data
v = dsy["v_mean"].data

lon = dsy.coords["lon"].data
lat = dsy.coords["lat"].data
time = dsy.coords["time"].data

The cell below computes the speed from the velocity. We can use the speed computation to color code the vectors. Note that we re-create the vector velocity preserving the direction but using intensity of 1. (The same visualization technique used in the HF radar DAC.)

import numpy as np
from oceans.ocfis import spdir2uv, uv2spdir

angle, speed = uv2spdir(u, v)
us, vs = spdir2uv(np.ones_like(speed), angle, deg=True)

Now we can create a matplotlib figure displaying the data.

import cartopy.crs as ccrs
from cartopy import feature
from cartopy.mpl.gridliner import LATITUDE_FORMATTER, LONGITUDE_FORMATTER

LAND = feature.NaturalEarthFeature(
    "physical", "land", "10m", edgecolor="face", facecolor="lightgray"
)

sub = 2
bbox = lon.min(), lon.max(), lat.min(), lat.max()

fig, ax = plt.subplots(figsize=(9, 9), subplot_kw=dict(projection=ccrs.PlateCarree()))


ax.set_extent([center[0] - dx - dx, center[0] + dx, center[1] - dy, center[1] + dy])
vmin, vmax = np.nanmin(speed[::sub, ::sub]), np.nanmax(speed[::sub, ::sub])
speed_clipped = np.clip(speed[::sub, ::sub], 0, 0.65)
ax.quiver(
    lon[::sub],
    lat[::sub],
    us[::sub, ::sub],
    vs[::sub, ::sub],
    speed_clipped,
    scale=30,
)

# Deepwater Horizon site.
ax.plot(-88.365997, 28.736628, marker="o", color="crimson")
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER

feature = ax.add_feature(LAND, zorder=0, edgecolor="black")
../../../_images/a7d98db48c3df1f1be5dcba640def3b68052db3954137db7808079c7efca68b6.png