Python package for downloading and processing data from the Kansas Mesonet, an environmental monitoring network operated by Kansas State University.
pip install ksmesopy
pip install "ksmesopy[app]" # Use this to also install a GUIDependencies: numpy, pandas, matplotlib. The desktop app additionally requires guile.
import ksmesopy as ms
# Download daily temperature and precipitation for one station
df = ms.request_data(
station="Manhattan",
start="2024-01-01",
end="2024-12-31",
interval="day",
variables=["TEMP2MAVG", "TEMP2MMIN", "TEMP2MMAX", "PRECIP"],
)
# Optional: rename columns to snake_case
df = ms.rename_columns(df)
# TIMESTAMP → timestamp, TEMP2MAVG → tair_2m_avg, PRECIP → precip, …Run this in your terminal
ksmesopy-appA GUI for selecting stations, date ranges, variables, and intervals, with a tabular view and time-series chart. Exports data to CSV and charts as PNG.
| Function | Returns | Description |
|---|---|---|
get_stations() |
DataFrame |
Full station metadata table |
get_stations(names_only=True) |
list[str] |
Sorted list of station names only |
get_stations_active() |
DataFrame |
Availability table: STATION, OBS_INTERVAL (s), START, END |
| Function | Returns | Description |
|---|---|---|
request_data(station, start, end, interval, variables, *, verbose, sleep) |
DataFrame |
Download data for one station. interval is "day", "hour", or "5min". Daily timestamps are corrected from the Mesonet's next-day convention. |
request_data_multi(stations, start, end, interval, variables, *, verbose, sleep) |
dict[str, DataFrame] |
Same as above for a list of stations; returns one DataFrame per station. |
list_variables(interval=None) |
list[dict] |
Variable catalogue filtered by interval, or all variables if None. Each entry has keys api_name, snake_name, description, intervals. |
rename_columns(df, preset="snake") |
DataFrame |
Rename API column names to snake_case (e.g. TEMP2MAVG → tair_2m_avg). Pass a dict for a custom mapping. |
| Function | Returns | Description |
|---|---|---|
calibrate_vwc(df, vwc_cols=None) |
DataFrame |
Replace firmware VWC values with the KSU site-specific calibration. Requires SOILKA*CM and SOILEC*CM columns. Works on any subset of depths. |
compute_soil_water_storage(df) |
DataFrame |
Trapezoidal soil water storage in the top 50 cm (mm). Requires all four VWC depths; adds a STORAGE_MM column. Call calibrate_vwc() first for calibrated storage. |
| Function | Returns | Description |
|---|---|---|
growing_degree_days(tmin, tmax, base=10.0, ceiling=30.0) |
ndarray |
Daily GDD. Both tmin/tmax clipped to [base, ceiling] before averaging. |
heat_index(temp, rh) |
ndarray |
NOAA/NWS apparent temperature (°C). |
wind_chill(temp, wspd) |
ndarray |
NWS wind chill (°C); valid for temp ≤ 10 °C, wind ≥ 1.3 m s⁻¹. |
temperature_humidity_index(temp, rh) |
ndarray |
THI for livestock heat stress. Thresholds (dairy): <68 none · 68–72 mild · 72–80 moderate · 80–90 severe · >90 dangerous. |
| Function | Returns | Description |
|---|---|---|
reference_et_penman_monteith(doy, lat, elev, tmin, tmax, srad, wspd, rhmin, rhmax, *, vpd, ea, wind_height) |
ndarray |
FAO-56 Penman-Monteith. Supply vapour pressure via ea=, vpd=, or rhmin=+rhmax=. srad in W m⁻², converted internally. |
reference_et_hargreaves(doy, lat, tmin, tmax, *, tmean) |
ndarray |
Hargreaves–Samani. Temperature only — no humidity, radiation, or wind needed. |
Both return ETo (mm day⁻¹) as an ndarray. Use extraterrestrial_radiation(doy, lat) if you also need Ra.
| Function | Output |
|---|---|
saturation_vapor_pressure(temp) |
es (kPa) |
actual_vapor_pressure(temp, rh) |
ea (kPa) |
vapor_pressure_deficit(temp, rh) |
VPD (kPa, ≥ 0) |
slope_saturation_vapor_pressure(temp) |
Δ (kPa °C⁻¹) |
atmospheric_pressure(elev) |
P (kPa) |
psychrometric_constant(elev) |
γ (kPa °C⁻¹) |
extraterrestrial_radiation(doy, lat) |
Ra (MJ m⁻² day⁻¹) |
net_radiation(srad_mj, tmin, tmax, ea, elev, doy, lat) |
Rn (MJ m⁻² day⁻¹) |
srad_to_mj(srad, period) |
energy (MJ m⁻²) |
All functions accept scalars or NumPy arrays.
Each function draws onto a Matplotlib Axes supplied by the caller, so panels compose freely inside any figure layout. All functions accept API column names (TEMP2MAVG) or snake_case names (tair_2m_avg) interchangeably, and return the axes they drew on so they can be further customised.
import matplotlib.pyplot as plt
import ksmesopy as ms
fig, axes = plt.subplots(5, 1, sharex=True, figsize=(12, 14))
ms.plot_temperature(axes[0], df, ["TEMP2MAVG", "TEMP2MMIN", "TEMP2MMAX"])
ms.plot_precip(axes[1], df, "PRECIP")
ms.plot_humidity(axes[2], df, "RELHUM2MAVG")
ms.plot_solar_radiation(axes[3], df, ["SRAVG", "Ra"]) # Ra drawn dashed
ms.plot_vwc(axes[4], df) # auto-detects VWC columns
plt.tight_layout()
plt.savefig("meteogram.png", dpi=150)All functions accept API column names (TEMP2MAVG) or snake_case names (tair_2m_avg) interchangeably, and return the axes they drew on so they can be further customised.
| Function | Key behaviour |
|---|---|
plot_temperature(ax, df, variables, *, band, ylabel, legend) |
Shaded band when min/avg/max triplet detected (band=True); plain lines otherwise. Works for air and soil temperature. |
plot_precip(ax, df, variable, *, ylabel, color) |
Bar chart, bar width inferred from timestamp spacing. |
plot_humidity(ax, df, variables, *, ylabel, legend) |
Lines, y-axis fixed 0–100 %. |
plot_vpd(ax, df, variables, *, ylabel, legend) |
Filled area + line, y-axis starts at 0. |
plot_solar_radiation(ax, df, variables, *, ylabel, legend) |
Filled area for observed; dashed line for Ra columns (detected by name). |
plot_wind(ax, df, speed, direction, *, ylabel, legend) |
Speed as line; direction overlaid as scatter on a twin y-axis with N/E/S/W ticks. |
plot_vwc(ax, df, variables, *, ylabel, legend) |
Sequential colormap shallow→deep; auto-detects VWC columns if variables=None. |
plot_et(ax, df, variables, *, bar, ylabel, legend) |
Line by default; bar=True for daily totals. |
Intervals: D = daily only · H = hourly and daily · A = 5-min, hourly, and daily
| API name | snake_case | Description | Unit | Intervals |
|---|---|---|---|---|
TEMP2MAVG |
tair_2m_avg |
Air temperature 2 m avg | °C | A |
TEMP2MMIN |
tair_2m_min |
Air temperature 2 m min | °C | D |
TEMP2MMAX |
tair_2m_max |
Air temperature 2 m max | °C | D |
TEMP10MAVG |
tair_10m_avg |
Air temperature 10 m avg | °C | A |
TEMP10MMIN |
tair_10m_min |
Air temperature 10 m min | °C | D |
TEMP10MMAX |
tair_10m_max |
Air temperature 10 m max | °C | D |
RELHUM2MAVG |
rh_2m_avg |
Relative humidity 2 m avg | % | A |
RELHUM2MMIN |
rh_2m_min |
Relative humidity 2 m min | % | D |
RELHUM2MMAX |
rh_2m_max |
Relative humidity 2 m max | % | D |
VPDEFAVG |
vpd_avg |
Vapor pressure deficit avg | kPa | A |
PRESSUREAVG |
pressure_avg |
Atmospheric pressure avg | kPa | A |
PRECIP |
precip |
Precipitation gauge 1 | mm | A |
PRECIP2 |
precip2 |
Precipitation gauge 2 | mm | A |
SRAVG |
srad |
Solar radiation avg | W m⁻² | A |
WSPD2MAVG |
wspd_2m_avg |
Wind speed 2 m avg | m s⁻¹ | A |
WSPD2MMAX |
wspd_2m_max |
Wind speed 2 m max | m s⁻¹ | H¹ |
WDIR2M |
wdir_2m |
Wind direction 2 m | ° | A |
WDIR2MSTD |
wdir_2m_std |
Wind direction 2 m std dev | ° | A |
WSPD10MAVG |
wspd_10m_avg |
Wind speed 10 m avg | m s⁻¹ | A |
WSPD10MMAX |
wspd_10m_max |
Wind speed 10 m max | m s⁻¹ | H¹ |
WDIR10M |
wdir_10m |
Wind direction 10 m | ° | A |
WDIR10MSTD |
wdir_10m_std |
Wind direction 10 m std dev | ° | A |
¹ Available at 5-min and daily only (not hourly).
| API name | snake_case | Description | Unit | Intervals |
|---|---|---|---|---|
SOILTMP5AVG |
tsoil_5cm |
Soil temperature 5 cm avg | °C | A |
SOILTMP5MIN |
tsoil_5cm_min |
Soil temperature 5 cm min | °C | D |
SOILTMP5MAX |
tsoil_5cm_max |
Soil temperature 5 cm max | °C | D |
SOILTMP10AVG |
tsoil_10cm |
Soil temperature 10 cm avg | °C | A |
SOILTMP10MIN |
tsoil_10cm_min |
Soil temperature 10 cm min | °C | D |
SOILTMP10MAX |
tsoil_10cm_max |
Soil temperature 10 cm max | °C | D |
| API name | snake_case | Description | Unit | Intervals |
|---|---|---|---|---|
SOILTMP5AVG655 |
tsoil_5cm_655 |
Soil temperature 5 cm | °C | A |
SOILTMP10AVG655 |
tsoil_10cm_655 |
Soil temperature 10 cm | °C | A |
SOILTMP20AVG655 |
tsoil_20cm_655 |
Soil temperature 20 cm | °C | A |
SOILTMP50AVG655 |
tsoil_50cm_655 |
Soil temperature 50 cm | °C | A |
SOILKA5CM |
ka_5cm |
Dielectric constant 5 cm | — | A |
SOILKA10CM |
ka_10cm |
Dielectric constant 10 cm | — | A |
SOILKA20CM |
ka_20cm |
Dielectric constant 20 cm | — | A |
SOILKA50CM |
ka_50cm |
Dielectric constant 50 cm | — | A |
SOILEC5CM |
ec_5cm |
Electrical conductivity 5 cm | dS m⁻¹ | A |
SOILEC10CM |
ec_10cm |
Electrical conductivity 10 cm | dS m⁻¹ | A |
SOILEC20CM |
ec_20cm |
Electrical conductivity 20 cm | dS m⁻¹ | A |
SOILEC50CM |
ec_50cm |
Electrical conductivity 50 cm | dS m⁻¹ | A |
VWC5CM |
vwc_5cm |
Volumetric water content 5 cm | m³ m⁻³ | A |
VWC10CM |
vwc_10cm |
Volumetric water content 10 cm | m³ m⁻³ | A |
VWC20CM |
vwc_20cm |
Volumetric water content 20 cm | m³ m⁻³ | A |
VWC50CM |
vwc_50cm |
Volumetric water content 50 cm | m³ m⁻³ | A |
VWC note: the Mesonet API returns VWC computed by the CS655 firmware equation. Call
calibrate_vwc()to replace those values with the KSU site-specific calibration. RequiresSOILKA*CMandSOILEC*CMto be fetched alongsideVWC*CM. Works for any subset of depths independently.
Precipitation. The Mesonet operates dual tipping-bucket rain gauges at most stations. request_data() returns both (PRECIP, PRECIP2); the desktop app merges them to the row-wise maximum automatically. For scripted use, merge manually:
df["PRECIP"] = df[["PRECIP", "PRECIP2"]].max(axis=1)
df.drop(columns="PRECIP2", inplace=True)Daily timestamps. The Mesonet API stores each day's aggregated values at 00:00 of the following calendar day. request_data() corrects for this automatically; the returned TIMESTAMP always reflects the observation date.
Missing values. The API encodes missing observations as "M". These are converted to NaN on read. Periods before a station or sensor was installed are pre-filled with NaN rather than omitted.
MIT
