NDBC Temperature Map

Most recent readings from Everglades National Park buoys via SECOORA ERDDAP

Code
library(dplyr)
library(htmltools)
library(knitr)
library(leaflet)
library(readr)
library(scales)

source("R/fetch_ndbc_observations.R")
source("R/plot_ndbc_field.R")

ndbc_data <- fetch_ndbc_hourly_snapshots(
  param = "sea_water_temperature",
  value_col = "temperature"
)

temperature_data <- ndbc_data$latest
start_time <- ndbc_data$start_time
end_time <- ndbc_data$end_time
data_as_of <- ndbc_data$data_as_of
report_time <- format(Sys.time(), "%Y-%m-%d %H:%M %Z", tz = ndbc_display_tz)
ref_date_label <- format(ndbc_data$reference_date, "%Y-%m-%d")
data_as_of_label <- format_ndbc_time(data_as_of)
query_window_label <- paste(
  format_ndbc_erddap_iso(start_time),
  "to",
  format_ndbc_erddap_iso(end_time)
)

Data are pulled from SECOORA ERDDAP using a 48-hour window ending at the latest available ERDDAP time (2026-07-27 05:06 EDT). The buoy map below shows the most recent non-missing temperature value for each station. Interpolated fields use snapshots at 06:00, 12:00, 18:00, and 24:00 US Eastern on 2026-07-26.

Report generated: 2026-07-27 10:14 EDT
Latest ERDDAP data: 2026-07-27 05:06 EDT
Query window: 2026-07-25 05:06 EDT to 2026-07-27 05:06 EDT

Code
if (nrow(temperature_data) == 0) {
  stop("No temperature data returned from ERDDAP for the current time window.")
}

temperature_data |>
  arrange(desc(time)) |>
  mutate(
    time = format_ndbc_time(time),
    temperature = number(temperature, accuracy = 0.1)
  ) |>
  select(Station = station, `Station ID` = station_id, Time = time, `Temperature (°C)` = temperature) |>
  kable()
Station Station ID Time Temperature (°C)
Buoy_Key bkyf1 2026-07-27 05:00 EDT 30.1
Butternut_Kay bnkf1 2026-07-27 05:00 EDT 31.5
Bob_Allen_Key bobf1 2026-07-27 05:00 EDT 31.0
Blackwater_Sound bwsf1 2026-07-27 05:00 EDT 31.8
Duck_Key dkkf1 2026-07-27 05:00 EDT 31.4
Garfield_Bight gbtf1 2026-07-27 05:00 EDT 30.5
Highway_Creek hcef1 2026-07-27 05:00 EDT 32.1
Joe_Bay jbyf1 2026-07-27 05:00 EDT 32.1
Johnson_Key jkyf1 2026-07-27 05:00 EDT 31.3
Little_Blackwater_Sound lbsf1 2026-07-27 05:00 EDT 31.2
Little_Madeira_Bay lmdf1 2026-07-27 05:00 EDT 31.1
Little_Rabbit_Key lrkf1 2026-07-27 05:00 EDT 30.8
Long_Sound lsnf1 2026-07-27 05:00 EDT 31.1
Middle_Key mdkf1 2026-07-27 05:00 EDT 30.9
Murray_Key mukf1 2026-07-27 05:00 EDT 31.2
Peterson_Key pkyf1 2026-07-27 05:00 EDT 31.5
Terrapin_Bay tbyf1 2026-07-27 05:00 EDT 31.3
Thursday_Point thrf1 2026-07-27 05:00 EDT 31.1
Whipray_Basin wrbf1 2026-07-27 05:00 EDT 31.1
Code
temperature_palette <- colorNumeric(
  palette = c("#313695", "#74add1", "#ffffbf", "#f46d43", "#a50026"),
  domain = temperature_data$temperature,
  na.color = "#999999"
)

leaflet(temperature_data) |>
  addProviderTiles("CartoDB.Positron") |>
  setView(
    lng = mean(temperature_data$longitude),
    lat = mean(temperature_data$latitude),
    zoom = 9
  ) |>
  addCircleMarkers(
    lng = ~longitude,
    lat = ~latitude,
    radius = 8,
    stroke = TRUE,
    weight = 1,
    color = "#333333",
    fillColor = ~temperature_palette(temperature),
    fillOpacity = 0.85,
    label = ~sprintf(
      "%s: %.1f °C\n%s",
      location,
      temperature,
      format_ndbc_time(time)
    )
  ) |>
  addLegend(
    position = "bottomright",
    pal = temperature_palette,
    values = temperature_data$temperature,
    title = "Temperature (°C)",
    opacity = 0.9
  )

Temperature is shown in degrees Celsius. Cooler values (blue) and warmer values (red) reflect spatial variation across the buoy network.

Interpolated temperature field

The fields below use DIVAnd (Data-Interpolating Variational Analysis) with bathymetry masking, following the same approach as sfer-mbon-oxygen: ~0.01° horizontal grid resolution (~1 km), a two-scale analysis (smooth 0.08° background + 0.035° local anomalies with epsilon2 = 0.01), and interpolation run separately within each connected sea component so distant buoys do not smooth over local features. Land is masked using Natural Earth 10 m coastlines plus NOAA BlueTopo 4 m bathymetry (8 m tiles fill gaps); cells with missing bathymetry or elevation ≥ −0.2 m are treated as land. Sea cells are extended along water paths from each buoy (not across islands), then short bridges are added only through BlueTopo water cells (elevation < −0.2 m) to connect nearby stations still isolated from the main field.

Four panels show the interpolated field at 06:00, 12:00, 18:00, and 24:00 US Eastern on 2026-07-26. Each panel uses buoy observations closest to that hour.

Code
temperature_fields <- interpolate_ndbc_hourly_fields(
  ndbc_data$snapshots,
  value_col = "temperature"
)

for (result in temperature_fields) {
  cat(
    sprintf(
      "Hour %02d: %d field cells, %d observations\n",
      result$hour,
      if (is.null(result$field)) 0L else nrow(result$field),
      nrow(result$observations)
    )
  )
}
Hour 06: 1194 field cells, 19 observations
Hour 12: 1194 field cells, 19 observations
Hour 18: 1194 field cells, 19 observations
Hour 24: 1194 field cells, 19 observations
Code
if (all(vapply(temperature_fields, function(x) is.null(x$field) || nrow(x$field) == 0, logical(1)))) {
  stop("No interpolated temperature fields were produced.")
}

plot_ndbc_field_panels(
  temperature_fields,
  value_col = "temperature",
  legend_title = "Temperature (°C)",
  popup_suffix = " °C",
  palette = c("#313695", "#74add1", "#abd9e9", "#fee090", "#f46d43", "#a50026"),
  label_fn = format_hour_panel_label
)

2026-07-26 06:00 EDT

2026-07-26 12:00 EDT

2026-07-26 18:00 EDT

2026-07-26 24:00 EDT

The colored grids show the DIVAnd-interpolated field at each hour; circle markers show the buoy observations used as input.