The Air and Water Database (AWDB) provides access to real-time monitoring data from NRCS networks including SCAN (Soil Climate Analysis Network), SNOTEL (SNOwpack TELemetry), and others.
Overview
AWDB contains data from thousands of automated monitoring stations across the United States, providing:
Soil moisture and temperature at multiple depths
Precipitation and weather data
Snow water equivalent and depth
Wind speed and direction
Solar radiation
Quick Start
Find Data Near a Location
The most common workflow is finding monitoring data for specific environmental properties near a geographic point:
from soildb.awdb.convenience import get_property_data_nearfrom soildb.exceptions import AWDBConnectionError, AWDBQueryErrorasyncdef quick_start():"""Find and retrieve soil moisture data near Denver, CO. """try:# Get soil moisture data near Denver, CO# Date range chosen for peak sensor activity (summer months) result =await get_property_data_near( latitude=39.74, longitude=-104.99, property_name='soil_moisture', start_date='2024-07-15', end_date='2024-07-22', auto_select_sensor=True )print(f"Station: {result['site_name']}")print(f"Distance: {result['metadata']['distance_km']:.1f} km")print(f"Data points: {len(result['data_points'])}")if result['data_points']: first = result['data_points'][0]print(f"Sample: {first['timestamp'][:10]} = {first['value']}%")except AWDBConnectionError as e:print(f"Connection error: {e}")print("The AWDB service is unreachable. Check your internet connection or try again later.")except AWDBQueryError as e:print(f"Query error: {e}")print("Invalid parameters or the requested property is not available at this location.")try: loop = asyncio.get_running_loop()import nest_asyncio nest_asyncio.apply() loop.run_until_complete(quick_start())exceptRuntimeError: asyncio.run(quick_start())
Station: Denver WSFO AP
Distance: 10.8 km
Data points: 0
Station Discovery
Find Stations by Network and Location
from soildb.awdb.convenience import discover_stationsfrom soildb.exceptions import AWDBConnectionError, AWDBQueryErrorasyncdef find_stations():"""Find SCAN stations in a specific state."""try: stations =await discover_stations( network_codes=['SCAN'], state_codes=['CO'], active_only=True, limit=2 )print(f"Found {len(stations)} SCAN stations in Colorado")for station in stations:print(f" - {station['name']} ({station['station_triplet']})")ifnot stations:print("No stations found with these criteria. Try different filters.")except AWDBConnectionError as e:print(f"Connection error: {e}")except AWDBQueryError as e:print(f"Query error: {e}")try: loop = asyncio.get_running_loop()import nest_asyncio nest_asyncio.apply() loop.run_until_complete(find_stations())exceptRuntimeError: asyncio.run(find_stations())
Found 2 SCAN stations in Colorado
- CPER (2197:CO:SCAN)
- Nunn #1 (2017:CO:SCAN)
Find Stations by Location (Nearby)
from soildb.awdb.convenience import discover_stations_nearbyfrom soildb.exceptions import AWDBConnectionError, AWDBQueryErrorasyncdef nearby_stations():"""Find stations near a point. Tip: Use max_distance_km to control search radius. Smaller radius = fewer but closer stations. """try: stations =await discover_stations_nearby( latitude=39.74, longitude=-104.99, max_distance_km=50, network_codes=['SCAN'], limit=2 )print(f"Found {len(stations)} SCAN stations within 50 km")if stations:for st in stations:print(f" - {st['name']}: {st['distance_km']:.1f} km away")else:print("No stations found in this radius.")except AWDBConnectionError as e:print(f"Connection error: {e}")except AWDBQueryError as e:print(f"Query error: {e}")try: loop = asyncio.get_running_loop()import nest_asyncio nest_asyncio.apply() loop.run_until_complete(nearby_stations())exceptRuntimeError: asyncio.run(nearby_stations())
Found 0 SCAN stations within 50 km
No stations found in this radius.
Data Retrieval
Get Soil Moisture Data
from soildb.awdb.convenience import get_soil_moisture_by_depthfrom soildb.exceptions import AWDBConnectionError, AWDBQueryErrorasyncdef get_moisture():"""Retrieve soil moisture at multiple depths. This station (2197:CO:SCAN) typically has sensors at 2", 4", 8", and 20" depths. Using July dates ensures active sensor readings (winter data is sparse). """try: data =await get_soil_moisture_by_depth('2197:CO:SCAN', depths_inches=[-2, -4, -8, -20], # Standard depths for this station start_date='2024-07-15', # Summer = most reliable sensor activity end_date='2024-07-22'# 1-week range )print("Soil Moisture Data Retrieved")print(f"\nAvailable depths: {list(data.get('depths', {}).keys())}")for depth, info in data.get('depths', {}).items(): n_points = info.get('n_data_points', 0)print(f" Depth {depth}\": {n_points} readings")except AWDBConnectionError as e:print(f"Connection error: {e}")except AWDBQueryError as e:print(f"Query error: {e}")try: loop = asyncio.get_running_loop()import nest_asyncio nest_asyncio.apply() loop.run_until_complete(get_moisture())exceptRuntimeError: asyncio.run(get_moisture())
from soildb.awdb.convenience import station_available_propertiesfrom soildb.exceptions import AWDBConnectionError, AWDBQueryErrorasyncdef list_properties():"""List all properties available at a SCAN station. Properties can include soil moisture, air temperature, precipitation, snow depth, solar radiation, wind speed, and more. """try: properties =await station_available_properties('2197:CO:SCAN')print(f"Station has {len(properties)} available properties")# Show first 5 with detailsprint("\nProperties (showing first 5):")for prop in properties[:5]: name = prop.get('property_name', 'Unknown') desc = prop.get('description', '')print(f" - {name}")if desc:print(f" ({desc[:50]}...)"iflen(desc) >50elsef" ({desc})")except AWDBConnectionError as e:print(f"Connection error: {e}")except AWDBQueryError as e:print(f"Query error: {e}")try: loop = asyncio.get_running_loop()import nest_asyncio nest_asyncio.apply() loop.run_until_complete(list_properties())exceptRuntimeError: asyncio.run(list_properties())
Station has 30 available properties
Properties (showing first 5):
- battery
(Battery)
- dew_point_temp
(Dew Point Temp)
- precipitation_increment
(Precipitation Increment)
- precipitation_month_to_date
(Precipitation Month To Date)
- precipitation
(Precipitation)
API Reference
Key Functions
Discovery:
discover_stations() — Find stations by network, state, elements, or other criteria
discover_stations_nearby() — Find stations within a distance of a point
Data Retrieval:
get_property_data_near() — Get data for a property at the nearest station to a point
get_soil_moisture_by_depth() — Get multi-depth soil moisture time series
station_available_properties() — List properties available at a station
station_sensors() — List sensor types available at a station
station_sensor_depths() — Get available depths for a property at a station
Client:
AWDBClient — Lower-level async client for direct API access
Important Notes
Rate Limiting: AWDB is a free public resource. Space out requests and avoid concurrent hammering of the API. If you receive errors:
Wait before retrying
Reduce the number of concurrent queries
Use smaller date ranges
Limit the number of stations fetched
Availability: The AWDB API experiences occasional downtime. Examples may fail intermittently. If you see errors, try again later.
Date Ranges: Use short date ranges (1-3 days) for testing. Longer ranges may timeout or hit rate limits.
Error Handling
AWDB errors fall into two main categories. Handle them specifically instead of catching generic exceptions:
Connection Errors (Network Issues)
Raised when the API is unreachable or times out.
from soildb.exceptions import AWDBConnectionErrortry: stations =await discover_stations(network_codes=['SCAN'])except AWDBConnectionError as e:print("API unreachable. Possible causes:")print(" - Internet connection down")print(" - AWDB service temporarily unavailable")print(" - Request timed out (large result set)")print("Solution: Wait 30 seconds and retry")
Common causes: - Network connectivity issues - API server down or slow - Request timeout (too many stations, large date range)
Query Errors (Invalid Parameters)
Raised when parameters are invalid or data doesn’t exist.
from soildb.exceptions import AWDBQueryErrortry: data =await get_soil_moisture_by_depth('9999:XX:FAKE', # Non-existent station start_date='2024-07-01', end_date='2024-07-31' )except AWDBQueryError as e:print("Query failed. Possible causes:")print(" - Station triplet doesn't exist")print(" - Property not available at this station")print(" - Date range outside station's operational period")print("Solution: Check station triplet and available properties")
Common causes: - Station triplet format incorrect (should be stationid:state:network) - Station doesn’t support requested property - Date range predates station’s operational start
Empty Results (Not an Error)
If a query returns zero data points, this is normal and not an error:
# Winter soil moisture queries often return empty# (sensors less active in cold months)result =await get_soil_moisture_by_depth('2197:CO:SCAN', start_date='2024-12-01', # Winter = sparse data end_date='2024-12-02')ifnot result['data_points']:print("No data for this date range (normal for off-season)")# Try summer dates instead: 2024-07-01 to 2024-07-31
Why it happens: - Seasonal: Soil sensors inactive in winter; snow/precipitation sparse in summer - Sensor downtime: Station undergoing maintenance or has failed sensor - Data gaps: Historical data may have gaps
Solution: Try a different season or verify station is operational
Station Data Availability Guide
Not all stations have all properties available, and data varies by season. Use this guide to choose reliable station + property + date combinations:
Recommended Combinations
Station
Network
Properties
Best Seasons
Example Dates
Notes
2197:CO:SCAN
SCAN
Soil moisture, air temp, precipitation
May-Sept
2024-07-01 to 2024-07-31
Denver area; reliable soil data
2197:CO:SCAN
SCAN
Temperature, precipitation
Year-round
2024-11-01 to 2024-11-30
Air sensors active all year
2197:CO:SCAN
SCAN
Snow depth
Dec-March
2024-02-01 to 2024-02-15
Winter peak; sparse summer
Property Availability by Season
Property
Best Season
Typical Readings/Day
Notes
Soil Moisture
May-Sept
3-6 (multi-depth)
Dormant or sparse Oct-April
Air Temperature
Year-round
24 (hourly)
Most reliable property; slight gaps possible
Precipitation
Year-round
1-2 daily
Generally reliable; occasional gaps
Snow Depth
Dec-March
1-2 daily
Zero/sparse outside winter
Solar Radiation
May-Sept
Hourly
Not available at all stations
Tips for Reliable Queries
Check station first: Use discover_stations() to verify station exists
Confirm property: Use station_available_properties() before querying
Choose season wisely: Soil data sparse in winter; snow sparse in summer
Use week-long ranges: Single-day ranges may miss data points
Handle empty gracefully: Empty result = normal for off-season, not an error
Working with Results
Convert to DataFrame
response =await get_property_data_near(...)df = response.to_pandas() # If soildb returns a response object
Parse Time Series Data
result =await get_soil_moisture_by_depth(...)for depth_inches, depth_data in result['depths'].items():print(f"\nDepth: {depth_inches} inches")for point in depth_data['data_points'][:5]:print(f" {point['timestamp']}: {point['value']}%")
Deprecated Functions
The following functions have been replaced with more intuitive names:
Old Name
New Name
Status
find_stations_by_criteria()
discover_stations()
Removed
get_monitoring_station_data()
get_property_data_near()
Removed
get_nearby_stations()
discover_stations_nearby()
Removed
list_available_variables()
station_available_properties()
Removed
Examples
See 05_awdb.py for a complete working example with error handling and throttling.