Skip to contents

Introduction

TexMesonet is a statewide earth observation network managed by the Texas Water Development Board (TWDB). TWDB stations collect near-real-time weather and soil observations, including air temperature, humidity, precipitation, wind, solar radiation, soil temperature, and soil moisture.

preMetabolizer provides three helpers for the public TexMesonet API:

Note: These functions contact the TexMesonet API and require an internet connection. Code chunks that call the API will not run during package installation if the service is unreachable. Run them interactively in your own session.

Caching downloaded data

Repeated calls to the TexMesonet API download the same data on every run. This vignette saves each result to a local cache directory the first time it is downloaded and reloads from disk on subsequent runs. The cache lives in tools::R_user_dir("preMetabolizer", which = "cache"), a platform-appropriate, user-specific directory that persists across sessions. Each data-fetching chunk below checks for a cached .rds file, downloads and saves on the first run, and reloads from disk on all subsequent runs.

Discover TWDB stations

Use tex_meso_stations() to retrieve station names, IDs, display IDs, coordinates, elevation, activity status, and online dates.

cache_file <- file.path(cache_dir, "tex_meso_stations.rds")
if (!file.exists(cache_file)) {
  stations <- tex_meso_stations()
  saveRDS(stations, cache_file)
} else {
  stations <- readRDS(cache_file)
}

glimpse(stations)
#> Rows: 137
#> Columns: 12
#> $ station_id      <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,…
#> $ station_name    <chr> "Blanco Weather Station", "Altwein Rd", "Headwaters Ra…
#> $ display_id      <chr> "BCBWS", "BCALT", "KEHEA", "BCHOR", "BCARN", "BNLCP", …
#> $ state           <chr> "TX", "TX", "TX", "TX", "TX", "TX", "TX", "TX", "TX", …
#> $ county          <chr> "Blanco", "Blanco", "Kendall", "Blanco", "Blanco", "Ba…
#> $ latitude        <dbl> 30.08906, 30.14957, 30.08845, 30.01853, 30.11047, 29.8…
#> $ longitude       <dbl> -98.41806, -98.54044, -98.69757, -98.45631, -98.30463,…
#> $ elevation       <int> 1334, 1730, 1959, 1416, 1316, 2163, 2206, 1881, 166, 3…
#> $ active          <lgl> FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,…
#> $ station_type    <int> 1, 2, 2, 2, 2, 1, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, …
#> $ station_display <lgl> FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,…
#> $ online_date     <date> 2016-04-28, 2016-04-28, 2016-04-28, 2016-04-28, 2016-…

The active and displayed arguments make it easy to focus on stations that are currently operating and shown by TexMesonet.

cache_file <- file.path(cache_dir, "tex_meso_stations_active.rds")
if (!file.exists(cache_file)) {
  active_stations <- tex_meso_stations(active = TRUE, displayed = TRUE)
  saveRDS(active_stations, cache_file)
} else {
  active_stations <- readRDS(cache_file)
}

active_stations |>
  select(station_id, station_name, display_id, county, latitude, longitude) |>
  arrange(county, station_name)
#> # A tibble: 126 × 6
#>    station_id station_name            display_id county  latitude longitude
#>         <int> <chr>                   <chr>      <chr>      <dbl>     <dbl>
#>  1          6 Love Creek Preserve     BNLCP      Bandera     29.8     -99.4
#>  2         69 Phyllis Thomas Bandera  BNPTB      Bandera     29.7     -99.4
#>  3         37 Pecan Grove Farms       BPPGF      Bastrop     30.2     -97.5
#>  4        102 Pawnee Ranch            BEPAW      Bee         28.6     -98.0
#>  5         34 City of Rogers          BLCOR      Bell        30.9     -97.2
#>  6         33 City of Troy            BLCOT      Bell        31.2     -97.3
#>  7         35 Doc Curb Pump Station   BLDOC      Bell        31.0     -97.5
#>  8         36 River Ridge Ranch       BLRRR      Bell        31.0     -97.8
#>  9        107 EAA Field Research Park BXEAA      Bexar       29.7     -98.4
#> 10          2 Altwein Rd              BCALT      Blanco      30.1     -98.5
#> # ℹ 116 more rows

For station time-series requests, keep the integer station_id. This example finds stations in Blanco County and selects one station ID for later use.

blanco_stations <- active_stations |>
  filter(county == "Blanco") |>
  select(station_id, station_name, display_id, latitude, longitude, elevation)

blanco_stations
#> # A tibble: 4 × 6
#>   station_id station_name         display_id latitude longitude elevation
#>        <int> <chr>                <chr>         <dbl>     <dbl>     <int>
#> 1          2 Altwein Rd           BCALT          30.1     -98.5      1730
#> 2          4 Holt Oaks Ranch      BCHOR          30.0     -98.5      1416
#> 3          5 Arnosky Farms        BCARN          30.1     -98.3      1316
#> 4        108 Blanco Middle School BCMID          30.1     -98.4      1395

site_id <- blanco_stations$station_id[1]

Retrieve current observations

tex_meso_current() returns the most recent available observation from each TWDB station. Not every station measures every parameter, so some columns may contain missing values.

cache_file <- file.path(cache_dir, "tex_meso_current.rds")
if (!file.exists(cache_file)) {
  current <- tex_meso_current()
  saveRDS(current, cache_file)
} else {
  current <- readRDS(cache_file)
}

glimpse(current)
#> Rows: 125
#> Columns: 39
#> $ object_id             <int> 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17, …
#> $ station_id            <int> 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17, …
#> $ station_name          <chr> "Altwein Rd", "Headwaters Ranch", "Holt Oaks Ran…
#> $ display_id            <chr> "BCALT", "KEHEA", "BCHOR", "BCARN", "BNLCP", "RE…
#> $ latitude              <dbl> 30.14957, 30.08845, 30.01853, 30.11047, 29.80180…
#> $ longitude             <dbl> -98.54044, -98.69757, -98.45631, -98.30463, -99.…
#> $ elevation             <int> 1730, 1959, 1416, 1316, 2163, 2206, 1881, 166, 3…
#> $ air_temp              <dbl> 26.66, 25.79, 27.74, 27.72, 26.04, 26.52, 28.62,…
#> $ air_temp2_m           <dbl> 26.66, 25.79, 27.74, 27.72, 26.04, 26.52, 28.62,…
#> $ humidity              <dbl> 62.01, 65.74, 59.53, 61.39, 60.04, 63.29, 27.86,…
#> $ precip                <dbl> 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,…
#> $ precip24_hr           <dbl> 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.254,…
#> $ precip48_hr           <dbl> 0.508, 0.254, 1.016, 1.524, 0.254, 0.000, 0.254,…
#> $ precip72_hr           <dbl> 0.508, 0.254, 1.016, 1.524, 0.762, 0.000, 0.254,…
#> $ wind_speed            <dbl> 0.0896, 0.0000, 0.0000, 0.6992, 3.4681, 1.1112, …
#> $ wind_speed2_m         <dbl> 0.0896, 0.0000, 0.0000, 0.6992, 1.9221, 1.1112, …
#> $ wind_direction        <dbl> 90, 53, 81, 145, 180, 144, 334, 114, 102, 155, 1…
#> $ wind_direction2_m     <dbl> 90, 53, 81, 145, 161, 144, 315, 130, 102, 155, 1…
#> $ wind_gust             <dbl> 1.0789, 0.0000, 0.0000, 1.8778, 4.9980, 2.6767, …
#> $ wind_gust2_m          <dbl> 1.0789, 0.0000, 0.0000, 1.8778, 3.4756, 2.6767, …
#> $ battery_voltage       <dbl> 12.81, 12.87, 12.94, 12.73, 12.80, 12.35, 13.25,…
#> $ soil_moisture         <dbl> 0.257, 0.381, 0.397, 0.485, 0.308, 0.472, 0.166,…
#> $ soil_temperature      <dbl> 25.73, 21.83, 22.80, 22.46, 24.45, 23.06, 27.43,…
#> $ soil_moisture5_cm     <dbl> 0.213, 0.254, 0.174, 0.370, 0.117, 0.211, NA, 0.…
#> $ soil_temperature5_cm  <dbl> 29.63, 24.94, 26.99, 33.26, 27.23, 26.39, NA, 24…
#> $ soil_moisture10_cm    <dbl> 0.224, 0.180, 0.316, 0.447, 0.298, 0.309, 0.156,…
#> $ soil_temperature10_cm <dbl> 29.45, 23.93, 26.02, 24.20, 27.19, 25.25, 29.87,…
#> $ soil_moisture20_cm    <dbl> 0.257, 0.381, 0.397, 0.485, 0.308, 0.472, 0.166,…
#> $ soil_temperature20_cm <dbl> 25.73, 21.83, 22.80, 22.46, 24.45, 23.06, 27.43,…
#> $ soil_moisture50_cm    <dbl> 0.241, NA, 0.382, 0.528, NA, NA, NA, 0.267, NA, …
#> $ soil_temperature50_cm <dbl> 22.52, NA, 21.93, NA, NA, NA, NA, 22.84, NA, 24.…
#> $ data_interval_minutes <dbl> 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, …
#> $ recorded_time         <dttm> 2026-05-11 01:00:00, 2026-05-11 01:00:00, 2026-…
#> $ air_temp9_m           <dbl> NA, NA, NA, NA, 26.46, NA, 28.69, 27.94, NA, NA,…
#> $ wind_speed10_m        <dbl> NA, NA, NA, NA, 3.4681, NA, 7.0486, 0.4121, NA, …
#> $ wind_direction10_m    <dbl> NA, NA, NA, NA, 180, NA, 334, 114, NA, NA, 105, …
#> $ wind_gust10_m         <dbl> NA, NA, NA, NA, 4.9980, NA, 10.1626, 1.9992, NA,…
#> $ air_pressure          <dbl> NA, NA, NA, NA, 938.0679, NA, 946.2365, 1007.136…
#> $ solar_radiation       <dbl> NA, NA, NA, NA, 14.1333, NA, 64.9955, 5.4105, NA…

The TexMesonet API reports units in a separate object. preMetabolizer stores that object as a data-frame attribute.

attr(current, "units")
#> $air_pressure
#> [1] "Millibars"
#> 
#> $air_temp
#> [1] "Celsius"
#> 
#> $battery_voltage
#> [1] "Volts"
#> 
#> $data_interval
#> [1] "Minutes"
#> 
#> $elevation
#> [1] "Feet"
#> 
#> $precipitation
#> [1] "Millimeters"
#> 
#> $relative_humidity
#> [1] "Percent"
#> 
#> $soil_moisture
#> [1] "Centimeters Cubed per Centimeters Cubed"
#> 
#> $soil_temp
#> [1] "Celsius"
#> 
#> $solar_radiation
#> [1] "Watts per Square Meter"
#> 
#> $wind_speed
#> [1] "Meters per Second"

For a quick station check, join current observations to station metadata or filter directly by station_id.

current |>
  filter(station_id == site_id) |>
  select(
    station_id,
    station_name,
    recorded_time,
    air_temp,
    humidity,
    precip,
    wind_speed,
    air_pressure
  )
#> # A tibble: 1 × 8
#>   station_id station_name recorded_time       air_temp humidity precip
#>        <int> <chr>        <dttm>                 <dbl>    <dbl>  <dbl>
#> 1          2 Altwein Rd   2026-05-11 01:00:00     26.7     62.0      0
#> # ℹ 2 more variables: wind_speed <dbl>, air_pressure <dbl>

Retrieve recent time series

tex_meso_timeseries() retrieves observations for a single station over a look-back window measured in minutes. By default, variable = "all" uses the TexMesonet charting-fields endpoint and returns all fields available for that station.

cache_file <- file.path(cache_dir, "tex_meso_timeseries_blanco.rds")
if (!file.exists(cache_file)) {
  recent <- tex_meso_timeseries(
    site_id = site_id,
    prior_minutes = 24 * 60
  )
  saveRDS(recent, cache_file)
} else {
  recent <- readRDS(cache_file)
}

glimpse(recent)
#> Rows: 97
#> Columns: 27
#> $ air_temp           <dbl> 26.66, 26.92, 27.05, 27.27, 28.08, 27.85, 28.58, 29…
#> $ air_temp9_m        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ humidity           <dbl> 62.01, 60.01, 58.70, 58.56, 56.74, 56.90, 53.09, 52…
#> $ precip             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
#> $ wind_speed         <dbl> 0.0896, 0.5317, 1.2313, 1.3495, 1.5281, 1.6219, 1.7…
#> $ wind_speed2_m      <dbl> 0.0896, 0.5317, 1.2313, 1.3495, 1.5281, 1.6219, 1.7…
#> $ wind_speed10_m     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ wind_direction     <dbl> 90, 118, 161, 163, 143, 160, 149, 106, 100, 176, 17…
#> $ wind_direction2_m  <dbl> 90, 118, 161, 163, 143, 160, 149, 106, 100, 176, 17…
#> $ wind_direction10_m <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ wind_gust2_m       <dbl> 1.0789, 1.6115, 2.6767, 2.4104, 3.4756, 4.0082, 3.7…
#> $ wind_gust10_m      <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ air_pressure       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ solar_radiation    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ soil_moist5        <dbl> 0.213, 0.213, 0.214, 0.215, 0.215, 0.215, 0.215, 0.…
#> $ soil_moist10       <dbl> 0.224, 0.224, 0.224, 0.224, 0.225, 0.225, 0.225, 0.…
#> $ soil_moist20       <dbl> 0.257, 0.257, 0.257, 0.257, 0.257, 0.257, 0.257, 0.…
#> $ soil_moist50       <dbl> 0.241, 0.241, 0.241, 0.240, 0.241, 0.240, 0.240, 0.…
#> $ soil_temp5         <dbl> 29.63, 29.81, 29.97, 30.15, 30.27, 30.41, 30.47, 30…
#> $ soil_temp10        <dbl> 29.45, 29.51, 29.60, 29.70, 29.67, 29.70, 29.69, 29…
#> $ soil_temp20        <dbl> 25.73, 25.64, 25.55, 25.48, 25.36, 25.29, 25.19, 25…
#> $ soil_temp50        <dbl> 22.52, 22.52, 22.49, 22.49, 22.50, 22.51, 22.48, 22…
#> $ water_level        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ water_temp         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ water_level2       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ water_temp2        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ date_time          <dttm> 2026-05-11 01:00:00, 2026-05-11 00:45:00, 2026-05-…

The returned date_time column is parsed as UTC.

range(recent$date_time, na.rm = TRUE)
#> [1] "2026-05-10 01:10:00 UTC" "2026-05-11 01:00:00 UTC"
attr(recent$date_time, "tzone")
#> [1] "UTC"

Retrieve one variable

TexMesonet also provides smaller single-variable endpoints. Use the variable argument when you only need one series.

cache_file <- file.path(cache_dir, "tex_meso_timeseries_blanco_temperature.rds")
if (!file.exists(cache_file)) {
  temperature <- tex_meso_timeseries(
    site_id = site_id,
    prior_minutes = 24 * 60,
    variable = "temperature"
  )
  saveRDS(temperature, cache_file)
} else {
  temperature <- readRDS(cache_file)
}

temperature |>
  arrange(date_time) |>
  tail()
#> # A tibble: 6 × 2
#>   value date_time          
#>   <dbl> <dttm>             
#> 1    81 2026-05-11 00:35:00
#> 2    81 2026-05-11 00:40:00
#> 3    80 2026-05-11 00:45:00
#> 4    80 2026-05-11 00:50:00
#> 5    80 2026-05-11 00:55:00
#> 6    80 2026-05-11 01:00:00

attr(temperature, "units")
#> [1] "Fahrenheit"

Available single-variable values are "temperature", "humidity", "barometric_pressure", "precip", and "wind_speed".

Example workflow: station weather summary

The charting-fields endpoint is useful when you want to prepare local meteorological covariates for a stream site. The example below summarizes hourly temperature, humidity, precipitation, and wind speed from the recent station data.

hourly_weather <- recent |>
  mutate(hour = lubridate::floor_date(date_time, "hour")) |>
  group_by(hour) |>
  summarise(
    air_temp_C = mean(air_temp, na.rm = TRUE),
    humidity_pct = mean(humidity, na.rm = TRUE),
    precip_mm = sum(precip, na.rm = TRUE),
    wind_speed_m_s = mean(wind_speed, na.rm = TRUE),
    .groups = "drop"
  )

hourly_weather
#> # A tibble: 25 × 5
#>    hour                air_temp_C humidity_pct precip_mm wind_speed_m_s
#>    <dttm>                   <dbl>        <dbl>     <dbl>          <dbl>
#>  1 2026-05-10 01:00:00       26.4         59.5         0         0.105 
#>  2 2026-05-10 02:00:00       24.8         57.2         0         0.0289
#>  3 2026-05-10 03:00:00       23.2         67.0         0         0     
#>  4 2026-05-10 04:00:00       22.1         74.7         0         0     
#>  5 2026-05-10 05:00:00       21.0         81.0         0         0     
#>  6 2026-05-10 06:00:00       20.8         83.4         0         0     
#>  7 2026-05-10 07:00:00       20.9         82.6         0         0.0009
#>  8 2026-05-10 08:00:00       20.6         82.9         0         0     
#>  9 2026-05-10 09:00:00       19.9         84.0         0         0.548 
#> 10 2026-05-10 10:00:00       20.2         82.1         0         0.289 
#> # ℹ 15 more rows

Plotting the recent series can help identify gaps or station behavior before combining meteorological data with stream logger observations.

ggplot(hourly_weather, aes(hour, air_temp_C)) +
  geom_line(color = "#2c7fb8") +
  geom_point(color = "#2c7fb8", size = 1.5) +
  labs(
    x = NULL,
    y = "Air temperature (°C)",
    title = "Recent TexMesonet air temperature"
  ) +
  theme_bw()

API scope

These functions use the lightweight TexMesonet API endpoints for TWDB stations. For historical custom downloads, longer date ranges, or non-TWDB provider networks shown in the TexMesonet map viewer, use the TexMesonet Custom Downloads page or the source network recommended by TWDB.