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:
-
tex_meso_stations()retrieves TWDB station metadata. -
tex_meso_current()retrieves the most recent observation from each TWDB station. -
tex_meso_timeseries()retrieves recent time-series observations for one station.
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 rowsFor 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.
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 rowsPlotting the recent series can help identify gaps or station behavior before combining meteorological data with stream logger observations.
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.
