One job definition. Every sensor on the same grid. Run it locally or on AWS Lambda.
AerEO is a plugin-based satellite data extraction framework. You declare an
ExtractionJob — search, read, process, write — and AerEO delivers
analysis-ready GeoTIFFs aligned to the Major TOM
grid, plus an artifacts.parquet
index where every row is a grid cell observation. Because every sensor lands on
the same grid cells, multi-sensor and multi-date outputs join.
Every pipeline stage is a plain Python function: keep the built-ins, swap one, or ship your own as a plugin.
| Jobs, not scripts | One ExtractionJob bundles grid, AOI, and pipeline stages — the same object runs in a notebook, a script, or Lambda. |
| One grid for every sensor | Outputs align to Major TOM cells via a deterministic per-cell GeoBox. Optical, SAR, different dates — identical pixels. |
| A catalog you can query | Every run writes artifacts.parquet — a Major TOM index with one row per (grid cell, observation), ready to join across constellations. |
| Local today, serverless tomorrow | LocalExecutor → LambdaExecutor is a one-line change; the job doesn't move. |
| Plugins are functions | No base classes. A typed function + an entry point is a plugin. |
Sentinel-2 NDWI extracted as Major TOM grid cells:
The same grid cells from two very different sensors:
And a multi-sensor training batch joined by grid_cell — Sentinel-2 NDVI at two
dates + Sentinel-1 SAR, mosaicked per cell:
uv add "aereo[all]"
# or
pip install "aereo[all]"Sensor-specific search and I/O plugins are separate packages, so you only ship what you need. Per-sensor install commands and credentials: Install. Python 3.12+.
Performance tip: run AerEO in the same AWS region as your data source — cross-region downloads are slow and incur egress charges.
Save as quickstart.py, run with uv run quickstart.py (no credentials
needed; fastest in Colab or an AWS instance in us-west-2):
"""Pure-Python quickstart for AerEO."""
from datetime import datetime, timezone
from shapely.geometry import Polygon
from aereo.builtins import (
build_grouped_tasks,
read_odc_stac,
search_stac,
write_geotiff,
)
from aereo.executors import LocalExecutor
from aereo.pipeline import ExtractionJob
# Tiny AOI around Chocón reservoir, Argentina.
aoi = Polygon(
[
(-68.90986824592407, -39.23705421799603),
(-68.65925870907353, -39.23705421799603),
(-68.65925870907353, -39.41589522092947),
(-68.90986824592407, -39.41589522092947),
(-68.90986824592407, -39.23705421799603),
]
)
job = ExtractionJob(
name="quickstart",
grid_dist=10_000,
output_uri="/tmp/aereo_quickstart",
search=search_stac,
read=read_odc_stac,
write=write_geotiff,
target_aoi=aoi,
)
assets = job.search(
stac_api_url="https://earth-search.aws.element84.com/v1",
collections={"sentinel-2-l2a": ["red", "nir"]},
intersects=aoi,
start_datetime=datetime(2024, 1, 1, tzinfo=timezone.utc),
end_datetime=datetime(2024, 1, 10, tzinfo=timezone.utc),
)
tasks = job.build_tasks(assets, build_grouped_tasks)
artifacts = job.execute(tasks[:1], executor=LocalExecutor(workers=1)) # first task only
catalog_uri = job.write_catalog(artifacts)
print(f"Catalog: {catalog_uri}")Open /tmp/aereo_quickstart — GeoTIFFs on the Major TOM grid plus
artifacts.parquet, one row per grid cell.
Prefer config files? The same job is a small YAML with Hydra _target_
entries; override anything from Python or the CLI. See
Configuration.
Runnable notebooks for every workflow — open in Colab or read as an executable book at frandorr.github.io/aereo-notebooks.
| I want to... | Notebook | |
|---|---|---|
| Try it without credentials | 01 — Sentinel-2 | |
| Compute a vegetation index | 01b — Sentinel-2 NDVI | |
| Compute a water index | 01c — Sentinel-2 NDWI | |
| Pull thermal bands from NASA | 02 — VIIRS | |
| Extract Sentinel 3 OLCI | 03 — Sentinel-3 OLCI | |
| Compute NDVI from Sentinel-3 | 03b — Sentinel-3 NDVI | |
| Extract foundation-model embeddings | 04 — GeoTessera | |
| Use a geostationary sensor | 05 — GOES-19 ABI | |
| See two constellations on one grid | 06 — Multiple constellations | |
| Work with SAR (cloud-proof) | 07 — Sentinel-1 SAR | |
| Build an ML dataset from many sensors | 08 — ML-ready dataset | |
| Extend AerEO with my own code | 09 — Build your own plugin | |
| Pair weather + climate data | 10 — GOES-19 + CHIRPS | |
| Learn the raw API (no config files) | Step by step raw |
NASA Earthdata authentication for the VIIRS / Sentinel-3 notebooks
Those notebooks use earthaccess. Create a ~/.netrc following the
earthaccess authentication guide.
In Colab, run this once:
import os
from getpass import getpass
username = getpass("Earthdata username: ")
password = getpass("Earthdata password: ")
netrc_path = os.path.expanduser("~/.netrc")
with open(netrc_path, "w") as f:
f.write(f"machine urs.earthdata.nasa.gov login {username} password {password}\n")
os.chmod(netrc_path, 0o600)After a run you have grid-aligned GeoTIFFs and artifacts.parquet — and that
parquet is a Major TOM index: one row per (grid cell, observation), with
grid_cell, start_time, end_time, uri, collection, and the cell
geometry, in the same spirit as the Major-TOM Core
datasets. Repeated cells
across rows are different observations of the same ground pixels, so joining
across sensors and dates is a filter, not a reprojection:
import geopandas as gpd
df = gpd.read_parquet("output/artifacts.parquet")
print(df[["grid_cell", "collection", "start_time", "uri"]].head())The full workflow — joins, gap-filling mosaics, a merged multi-sensor index — is the 08 — ML-ready dataset notebook.
AerEO discovers plugins through the aereo.plugins entry-point group, so any
installed package can add search providers, readers, writers, and processors.
These ship with aereo itself — no extra install needed:
| Plugin | Type | Description |
|---|---|---|
search_stac |
Search | Query any STAC API and return GeoDataFrame[AssetSchema] |
build_grouped_tasks |
Task builder | Group assets by time and native CRS into grid-aligned ExtractionTask objects |
read_odc_stac |
Reader | Load STAC assets via odc.stac into an xarray.Dataset |
reproject_odc |
Reprojector | Reproject/resample a dataset to a target geobox with odc-geo |
reproject_swath |
Reprojector | Resample swath data (e.g. VIIRS, OLCI) to a target grid with pyresample |
process_select_bands |
Processor | Subset a dataset to a list of bands |
process_qa_mask |
Processor | Apply a QA bit-mask band to the data |
process_ndvi |
Processor | Compute NDVI from NIR and red bands |
process_ndwi |
Processor | Compute NDWI from green and NIR bands |
process_normalize |
Processor | Normalize pixel values per band (min-max, z-score) |
process_composite |
Processor | Create a temporal composite (median, mean, ...) |
write_geotiff |
Writer | Write a dataset to GeoTIFF |
| Plugin | Type | Description | Install |
|---|---|---|---|
aereo-search-aws-goes |
Search | Discover GOES-R series data (GOES-16 through GOES-19) on public NOAA AWS S3 buckets | PyPI · Repo |
aereo-search-tessera |
Search | Search GeoTessera satellite embedding tiles | PyPI · Repo |
aereo-herbie |
Search + Reader | Discover and read NWP model data (HRRR, GFS, ECMWF, GEFS) via Herbie GRIB2 inventories | Repo |
aereo-read-satpy |
Reader | Load satellite data from many EO formats via Satpy into xarray.Dataset |
PyPI · Repo |
aereo-read-tessera |
Reader | Read GeoTessera satellite embedding tiles | PyPI · Repo |
To build your own, start from the aereo-plugin-template and follow Build a Plugin.
Install · Quickstart · Configuration · Tutorials · Build a Plugin · Run on AWS Lambda
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
No assets found |
Date range or AOI too restrictive | Widen the time range or check the AOI geometry |
| Downloads are very slow | Running in a different AWS region than the data | Move your runtime to the data's region (e.g. us-west-2 for Earth Search) |
earthaccess authentication error |
Missing .netrc or expired credentials |
Follow the earthaccess guide |
grid_dist looks wrong |
It is in meters, not pixels or degrees | Use values like 10_000 for 10 km cells |
| Outputs do not line up | Different sensors without a shared grid | Ensure all jobs use the same grid_dist and Major TOM grid |
-
AerEO is inspired by the work done in FDL sat-extractor.
-
It is built upon the Major TOM grid from ESA.
Apache License 2.0



