Download USGS 3DEP DEMs and Sentinel-2 L2A imagery with zero API keys
Project description
earthfetch
Analysis-ready Earth data in one line. Zero API keys. Zero accounts.
📖 Docs: https://ethan-m2024.github.io/earthfetch/
import earthfetch as ef
# Cloud-free composite of any place on Earth — geocoded, cloud-masked,
# mosaicked across scene boundaries, reflectance-scaled, in your UTM zone:
rgb = ef.composite("Moab, Utah", bands=["B04", "B03", "B02"],
start="2026-05-01", end="2026-06-15")
ef.preview(rgb, "moab.png")
# NDVI for a farm polygon, clipped to its boundary:
ds = ef.composite("field.geojson", bands=["B08", "B04"],
start="2026-05-01", end="2026-06-01")
ndvi = ef.ndvi(ds)
# Terrain anywhere (Alps -> Copernicus DEM, auto-UTM):
terr = ef.terrain((6.85, 45.82, 6.90, 45.87)) # dem, slope, aspect, hillshade
ef.to_cog(terr.hillshade, "hillshade.tif")
One scene replaces an afternoon: no EarthExplorer queues, no Copernicus tokens, no manual SCL masking, no tile mosaicking, no gdalwarp. Only the AOI window travels over the network — everything is read from Cloud-Optimized GeoTIFFs with HTTP range requests.
AOI: pass anything
Every function takes bboxes, GeoJSON dicts, .geojson files, shapely
geometries, or place names:
ef.composite((-111.9, 40.7, -111.8, 40.8), ...) # bbox tuple
ef.composite("Yosemite National Park", ...) # geocoded (Nominatim)
ef.composite("watershed.geojson", ...) # file; clips to polygon
ef.composite(shapely_polygon, ...) # __geo_interface__
crs="utm" (default in composite/terrain/load_naip) picks the right
UTM zone. Explicit polygons clip results to their boundary; geocoded place
names return the full rectangle (pass clip=True to cut to the boundary).
Data sources (all free, no auth)
| Source | What | Coverage | Resolutions |
|---|---|---|---|
| USGS 3DEP (The National Map) | DEM | United States | 1 m, 10 m, 30 m, 5 m (AK) |
| Copernicus GLO-30 (AWS) | DEM | Global | 30 m |
| Sentinel-2 L2A (Earth Search / AWS) | Multispectral imagery | Global | 10 / 20 / 60 m |
| NAIP (Planetary Computer) | Aerial photography (RGBN) | United States | 0.6-1 m |
Looking for "Google Earth"-quality imagery? That's NAIP: actual aerial photos where you can see individual cars and trees.
img = ef.load_naip("Moab, Utah", res=1) # 1 m RGB mosaic
nir = ef.load_naip(aoi, bands=["N","R","G"]) # false-color infrared
Install
pip install earthfetch # search + download only (requests)
pip install "earthfetch[xarray]" # + load_dem / load_sentinel2 / stack
Composites, indices, terrain
# method: "median" (robust), "mean", "first" (fastest)
da = ef.composite(aoi, bands=["B04","B03","B02"], start=..., end=...,
method="median", mask_clouds=True, max_scenes=8)
ef.ndvi(ds); ef.ndwi(ds); ef.nbr(ds); ef.evi(ds); ef.savi(ds)
terr = ef.terrain(aoi, products=["dem","slope","aspect","hillshade"],
resolution="10m") # USGS in US, Copernicus elsewhere
ef.to_geotiff(obj, "out.tif"); ef.to_cog(obj, "out.tif")
ef.preview(obj, "look.png") # percentile-stretched quicklook
Array API (pipelines)
import earthfetch as ef
bbox = (-111.90, 40.70, -111.85, 40.75) # (min_lon, min_lat, max_lon, max_lat)
# DEM as xarray.DataArray — any CRS, any pixel size
dem = ef.load_dem(bbox, resolution="10m", crs="EPSG:32612", res=10)
# Outside the US? Copernicus GLO-30 is used automatically (source="auto"),
# or ask for it explicitly:
alps = ef.load_dem((6.85, 45.82, 6.88, 45.85), crs="EPSG:32632", source="copernicus")
# Sentinel-2 bands, clearest scene in a date range
s2 = ef.load_sentinel2(bbox, bands=["B04", "B08"], crs="EPSG:32612",
start="2026-05-01", end="2026-06-01", max_cloud=20)
# Everything aligned on one grid (ML-ready xarray.Dataset)
ds = ef.stack(bbox, crs="EPSG:32612", res=30, bands=["B04", "B08"],
start="2026-05-01", end="2026-06-01")
All arrays are float32 with NaN nodata and carry crs, transform, and source URLs in attrs. Bands accept ESA ids (B02–B12, B8A, SCL) or Earth Search asset keys (red, nir, ...).
Search + download API (files)
tiles = ef.search_dem(bbox, resolution="10m") # metadata only
paths = ef.download_dem(bbox, resolution="10m", out_dir="dem")
scenes = ef.search_sentinel2(bbox, "2026-05-01", "2026-06-01", max_cloud=15)
files = ef.download_sentinel2(scenes[0], bands=["B04", "B08"], out_dir="s2")
from earthfetch import clip_reproject # needs [raster]
clip_reproject(paths, bbox, "EPSG:32612", "dem_utm.tif")
Downloads default to a per-user cache ($EARTHFETCH_CACHE to override), stream to .part files, verify byte counts, skip files already on disk, and run in parallel. The library never prints — it logs to the earthfetch logger; pass progress=earthfetch.utils.print_progress for a progress bar in scripts.
CLI
earthfetch dem --bbox -111.9 40.7 -111.8 40.8 --search-only --json
earthfetch dem --bbox -111.9 40.7 -111.8 40.8 --resolution 10m --out dem/
earthfetch s2 --bbox -111.9 40.7 -111.8 40.8 \
--start 2026-05-01 --end 2026-06-01 --bands B04,B08 --out s2/
--json emits machine-readable search results; -v logs library activity.
Errors
Everything raises a subclass of earthfetch.EarthfetchError:
TileNotFoundError (bbox outside coverage), NoScenesError (no clear
scenes — widen dates or raise max_cloud), DownloadError,
BandNotFoundError, MissingDependencyError.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file earthfetch-0.7.0.tar.gz.
File metadata
- Download URL: earthfetch-0.7.0.tar.gz
- Upload date:
- Size: 66.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
edc6427adec64457095497ea2e85c7edcc9ba7ee53b448e82004f7598f6e6aaa
|
|
| MD5 |
c385f33075b4cfdffac265ca6fa3e0b1
|
|
| BLAKE2b-256 |
51dd8839df4ffccf9a943d52b3be07bdc8ae66aeda7dcd13a2f52b318baba66f
|
Provenance
The following attestation bundles were made for earthfetch-0.7.0.tar.gz:
Publisher:
ci.yml on Ethan-M2024/earthfetch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
earthfetch-0.7.0.tar.gz -
Subject digest:
edc6427adec64457095497ea2e85c7edcc9ba7ee53b448e82004f7598f6e6aaa - Sigstore transparency entry: 2093964480
- Sigstore integration time:
-
Permalink:
Ethan-M2024/earthfetch@5199065edf2eef0f8284707acfda081cb120f906 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/Ethan-M2024
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@5199065edf2eef0f8284707acfda081cb120f906 -
Trigger Event:
push
-
Statement type:
File details
Details for the file earthfetch-0.7.0-py3-none-any.whl.
File metadata
- Download URL: earthfetch-0.7.0-py3-none-any.whl
- Upload date:
- Size: 43.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14c003731b477a0f36c346c3fe7d10225e2667f3b47d8eed54bba7b62236c2a0
|
|
| MD5 |
e951b603f97ebec4350aaf70a8da0874
|
|
| BLAKE2b-256 |
8aeb22fd353b81c2d78fa5709789d997f6168b93f304c31135b8234c6f77f63f
|
Provenance
The following attestation bundles were made for earthfetch-0.7.0-py3-none-any.whl:
Publisher:
ci.yml on Ethan-M2024/earthfetch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
earthfetch-0.7.0-py3-none-any.whl -
Subject digest:
14c003731b477a0f36c346c3fe7d10225e2667f3b47d8eed54bba7b62236c2a0 - Sigstore transparency entry: 2093964631
- Sigstore integration time:
-
Permalink:
Ethan-M2024/earthfetch@5199065edf2eef0f8284707acfda081cb120f906 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/Ethan-M2024
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@5199065edf2eef0f8284707acfda081cb120f906 -
Trigger Event:
push
-
Statement type: