Skip to content

AerEO logo

Access, extract, reproject for Earth Observation — locally or on AWS Lambda, without reinventing the wheel.

AerEO

AerEO is a plugin-based satellite data extraction framework. It wires together the catalog, reading, reprojection, and writing tools you already trust (STAC, Earthaccess, Satpy, odc-geo) behind a single pipeline where every step can be replaced. The result: analysis-ready GeoTIFFs aligned to the Major TOM grid.


Install

AerEO's core framework includes built-in search (STAC, NASA Earthaccess, etc.), read, reproject, and write functions. You can extend it with plugins for other sensors and formats — by combining search, read, reproject, and write plugins you can access hundreds of constellations without changing your pipeline.

uv add aereo
# or
pip install aereo
uv add aereo aereo-read-satpy
# or
pip install aereo aereo-read-satpy

Configure earthaccess credentials (.netrc, environment variables, or earthaccess.login()) before searching.

uv add aereo aereo-search-aws-goes aereo-read-satpy
# or
pip install aereo aereo-search-aws-goes aereo-read-satpy

GOES data on AWS is public, so no authentication is required.

uv add aereo aereo-search-tessera aereo-read-tessera
# or
pip install aereo aereo-search-tessera aereo-read-tessera

GeoTessera data is public, so no authentication is required.

Install the core framework with uv add aereo (or pip install aereo). Sensor-specific plugins are separate packages so you only ship what you need. Optional extras cover serverless (aereo[serverless]), swath reprojection (aereo[swath]), and viz (aereo[viz]); use aereo[all] to install them all.


Copy/paste example

Save this as quickstart.py and run it with uv run quickstart.py:

Network speed note: This example downloads Sentinel-2 data from Earth Search over the public internet. From a local machine the download can be a bottleneck. For the fastest first experience, run it in Google Colab or an AWS compute instance in the same region as the data (us-west-2 for Earth Search).

"""Pure-Python quickstart for AerEO.
To run the full pipeline:

    uv run python examples/quickstart_pure_python.py
"""

from __future__ import annotations

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


def main() -> None:
    """Build a job in pure Python and run the extraction pipeline."""
    # 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,
    )

    print("--- ExtractionJob ---")
    print(f"name: {job.name}")
    print(f"output_uri: {job.output_uri}")
    print(f"grid_dist: {job.grid_dist}")

    print("\n--- Search ---")
    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),
    )
    print(f"Found {len(assets)} asset rows")

    if assets.empty:
        print("No assets found; nothing to extract.")
        return

    print("\n--- Build tasks ---")
    tasks = job.build_tasks(assets, build_grouped_tasks)
    print(f"Built {len(tasks)} task(s)")

    print("\n--- Extract ---")
    # Run only the first task for demo speed.
    artifacts = job.execute(tasks[:1], executor=LocalExecutor(workers=1))
    print(f"Extracted {len(artifacts)} artifact(s)")

    catalog_uri = job.write_catalog(artifacts)
    print(f"\nCatalog written to: {catalog_uri}")


if __name__ == "__main__":
    main()

Open /tmp/aereo_quickstart — you have GeoTIFFs on the Major TOM grid. The script also calls job.write_catalog(artifacts), so an artifacts.parquet catalog is written next to the GeoTIFFs.


What you get

These outputs come straight from the tutorial notebooks. Every plot shows grid-aligned patches on the Major TOM grid, with the target AOI overlaid.

See the full gallery in the Tutorials section.


How it works

flowchart LR
    Search["Search provider"] --> Prepare["Prepare tasks\n(grid + grouping)"]
    Prepare --> Execute["Execute\n(local / Lambda)"]
    Execute --> Catalog["Output catalog\n+ GeoTIFFs"]
  1. Search — query a catalog and get a validated GeoDataFrame[AssetSchema].
  2. Prepare — group assets by time and native CRS into ExtractionTask objects.
  3. Execute — run each task through read → preprocess → reproject → postprocess → write, producing grid-aligned artifacts and a catalog.

Any stage can be replaced by a function you write. Learn how in Build a Plugin.


Why AerEO?

  • Search anything

    One job.search(...) call works with STAC, Earthaccess, public S3, and custom catalogs. Swap the search function without changing your pipeline.

  • Grid aligned

    Outputs are indexed on the Major TOM grid, so Sentinel-2, VIIRS, Sentinel-3, and GOES scenes stack together out of the box.

  • One config, multiple runtimes

    The same Hydra config package runs in a notebook and on AWS Lambda.

  • Plain Python plugins

    No inheritance, no framework boilerplate. AerEO plugins are @validate_call functions registered via standard Python entry points.