How to Download SRTM DEMs for Python Hydrology Workflows
To programmatically acquire elevation data for catchment modeling, query the OpenTopography Global DEM API using requests, validate the binary payload with rasterio, and cache the output in a project-local directory. This approach replaces deprecated FTP endpoints, handles API rate limits gracefully, and delivers georeferenced GeoTIFFs ready for sink filling, flow direction computation, and watershed delineation.
Integrating this step into your broader Hydrology Data Preparation & DEM Processing pipeline ensures reproducible, version-controlled terrain inputs. The method below prioritizes memory efficiency, automatic retry logic, and immediate raster validation so your downstream hydrologic models never fail on corrupted or empty tiles.
Production-Ready Python Implementation
import time
import requests
import rasterio
from pathlib import Path
def download_srtm_dem(bbox, output_dir="dem_cache", resolution="SRTMGL1", api_key=None):
"""
Download SRTM DEM tiles for a given bounding box with caching and retry logic.
bbox: tuple (south, north, west, east) in decimal degrees (WGS84)
resolution: 'SRTMGL1' (30m) or 'SRTMGL3' (90m)
"""
Path(output_dir).mkdir(parents=True, exist_ok=True)
out_path = Path(output_dir) / f"srtm_{resolution}_{bbox[2]}_{bbox[3]}_{bbox[0]}_{bbox[1]}.tif"
if out_path.exists():
print(f"Cache hit: {out_path}")
return out_path
url = "https://portal.opentopography.org/API/globaldem"
params = {
"demtype": resolution,
"south": bbox[0],
"north": bbox[1],
"west": bbox[2],
"east": bbox[3],
"outputFormat": "GTiff"
}
if api_key:
params["API_Key"] = api_key
# Graceful rate-limit handling with exponential backoff
for attempt in range(3):
response = requests.get(url, params=params, timeout=120)
if response.status_code == 429:
wait = (2 ** attempt) * 5
print(f"Rate limited. Retrying in {wait}s...")
time.sleep(wait)
continue
response.raise_for_status()
break
else:
raise RuntimeError("Max retries exceeded due to persistent rate limits.")
with open(out_path, "wb") as f:
f.write(response.content)
# Validate raster integrity immediately after write
with rasterio.open(out_path) as src:
if src.count == 0 or src.width == 0:
raise ValueError("Downloaded DEM is empty or corrupted.")
print(f"Downloaded: {out_path.name} | Shape: {src.shape} | CRS: {src.crs}")
return out_path
# Example: Mid-sized watershed in Southern California
bbox = (34.5, 35.2, -118.5, -117.8) # South, North, West, East
dem_file = download_srtm_dem(bbox)
Step-by-Step Breakdown
- Bounding Box Definition: The API expects
(south, north, west, east)in decimal degrees. Always verify coordinates against WGS84 (EPSG:4326) to prevent tile misalignment. - Local Caching: The function checks for an existing file before hitting the network. This prevents redundant downloads during iterative model calibration or parameter sweeps.
- Retry Logic: OpenTopography enforces fair-use limits. The exponential backoff loop catches
429 Too Many Requestsresponses and pauses execution without crashing the pipeline. - Binary Validation: Writing raw bytes to disk is fast but risky. Opening the file immediately with
rasterioconfirms the GeoTIFF header is intact and the raster contains valid pixel arrays. - Metadata Logging: Printing shape and CRS provides instant feedback for automated logging systems, making it easier to trace data lineage in agency or research environments.
Preparing the DEM for Hydrologic Analysis
Raw SRTM data arrives in geographic coordinates (degrees), which are unsuitable for distance-based hydrologic calculations. Before running flow accumulation or stream network extraction, reproject the tile to a metric coordinate system (typically UTM) and apply void-filling algorithms to correct radar shadow artifacts.
For regional studies, consult the SRTM and LiDAR Data Acquisition guidelines to determine when 30m SRTMGL1 is sufficient versus when airborne LiDAR is required for micro-topographic precision. Once reprojected, standardize elevation values using rasterio.warp.reproject or pass the file directly to hydrology libraries like richdem or whitebox. Official API specifications and request limits are documented in the OpenTopography Global DEM API reference, while raster I/O best practices are covered in the Rasterio Quickstart.
Environment & Compatibility
| Component | Minimum Version | Notes |
|---|---|---|
| Python | 3.9+ | f-strings, pathlib, and requests timeout behavior are optimized for 3.9+. Avoid 3.7 due to legacy SSL/TLS deprecations. |
rasterio |
1.3.0+ | Requires GDAL ≥ 3.2. Older versions fail on SRTM void-filled metadata and CRS auto-detection. |
| GDAL | 3.4+ | Windows users must install via conda install -c conda-forge gdal rasterio to avoid DLL conflicts. |
| OS | Linux/macOS/Windows | Linux/macOS handle large memory-mapped rasters natively. Windows requires os.environ["GDAL_DISABLE_READDIR_ON_OPEN"]="EMPTY_DIR" for speed. |
Rate Limits & API Best Practices
- API Keys: Free endpoints cap at ~100 requests/day. Register for a free OpenTopography account to attach an
API_Keyparameter, which raises limits and prioritizes queue placement. - Tile Sizing: Keep bounding boxes under 10,000 km² per request. Larger extents trigger server-side chunking, increasing latency and memory pressure.
- Memory Management: SRTMGL1 tiles for large watersheds can exceed 500 MB. Use
rasterio.open()in context managers or leveragerasterio.windowsto process elevation data in streaming blocks rather than loading entire arrays into RAM. - Void Filling: SRTM contains data gaps in steep terrain. Apply
scipy.ndimage.gaussian_filteror dedicated hydrologic tools likepyshedsorwhitebox’sFillDepressionsbefore computing flow direction.
By standardizing acquisition, validation, and caching in a single function, you eliminate manual download steps and ensure every hydrologic model starts from a verified, reproducible terrain surface.