A STAC (SpatioTemporal Asset Catalog) benchmark catalog for flood inundation mapping (FIM) datasets. This repository provides tools to ingest, catalog, and serve various flood modeling and observation datasets in STAC format. The purpose of the catalog is to make it easier to find FIMs that can be used to evaluate the output of the National Water Center's (NWCs) flood models.
BenchmarkCat creates and manages a STAC catalog containing multiple collections of flood-related geospatial data from different sources:
- Base Level Elevation (BLE) collection - FEMA flood extent and depth maps. Thes are the "legacy" BLE FIM's used by the Office of Water Prediction's National Water Center circa 2023.
- Global Flood Monitoring (GFM) collection - Sentinel 1 satellite derived flood extents created by the European Space Agencies Copernicus program. There are two GFM related benchmark collections. One collection covers data from 2015 to 2021 and uses the Dartmouth Flood Observatory collection of flood events to only select GFM data likely to be associated with very large flood events. The other collection is called "GFM expanded" contains a wider variety of inundation observations from 2021 to 2025 and uses the baseline inundation supplied by the GFM system itself to only download events likely to be inundated.
- ICEYE collection - proprietary, satellite derived depth and extent FIMs produced by the company ICEYE by fusing data from their private SAR satellite constellation and publicly available satellite data.
- AHPS FIM - NOAA Advanced Hydrologic Prediction Service Flood Inundation Mapping extents derived from HEC-RAS models. There are two collections of FIMs associated with AHPS data. One set of FIMs was produced by the National Weather Service and the other set was produced by the USGS.
- High Water Marks (HWM) collection - USGS field surveyed flood measurements. In contrast to the other collections the HWM surveys are collections of point measurements taken along or near the boundary of a flood event's extent (the "highwater mark").
- Ripple collection - extent FIM's produced by Dewberry using its ripple and flows2fim software packages. These are HEC-RAS derived FIM's.
All the collections follow a flat collection structure. That is each collection only contains items and doesn't contain sub-collections. Because the main purpose of the collection is model evaluation the most important data included with each item are the FIM observations themselves and estimates of the peak discharges present during the flood event (flowfiles). Each collection contains these two things at a minimum. Where available and when deemed useful, accessory data is also provided.
benchmarkcat/
├── ingest/ # Data ingestion and STAC metadata creation modules
│ ├── utils.py # Shared utilities (S3Utils, RasterUtils, FlowfileUtils)
│ ├── batch_utils.py # Batch pipeline helpers (manifest I/O, parquet merge)
│ ├── ble/ # BLE ingestion
│ ├── gfm/ # GFM ingestion (batch_split, gfm_col, batch_merge)
│ ├── gfm_exp/ # GFM expanded ingestion (batch_split, gfm_exp_col, batch_merge)
│ ├── iceye/ # ICEYE ingestion
│ ├── ahps/ # AHPS FIM ingestion
│ ├── hwm/ # High water marks ingestion
│ └── ripple/ # Ripple collection ingestion
├── schemas/ # JSON Schema definitions
│ ├── ble/v1.0.0/
│ ├── iceye/v1.0.0/
│ ├── gfm/v1.0.0/
│ └── common_item_metadata/
├── scripts/ # Utility scripts
│ ├── run_pipeline_prefect.py # Prefect orchestrator for AWS Batch pipeline
│ ├── build_and_push.sh # Build & push Docker image to ECR
│ ├── batch-entrypoint.sh # Container entrypoint for batch jobs
│ ├── stac_processor.py # STAC catalog processing
│ └── normalize_cat.py # Catalog normalization
├── terraform/ # AWS Batch pipeline infrastructure (ECR, compute env, job queue)
│ # See docs/aws-batch-pipeline.md for usage
├── deployment/ # OWP production deployment — STAC API server, asset proxy, and S3 migration
│ ├── terraform/ # Terraform stack for the STAC API EC2/RDS environment (separate from root terraform/)
│ ├── asset-proxy/ # Nginx/Python proxy for serving S3 assets over HTTP
│ ├── s3_migration/ # Scripts to migrate the static S3 catalog to the API
│ └── scripts/ # Bootstrap and operational scripts for the deployed server
├── docs/ # Additional documentation
│ └── aws-batch-pipeline.md # AWS Batch pipeline setup and reference
├── Dockerfile # Container image for ingest jobs
├── Dockerfile.orchestration # Lightweight container for Prefect orchestrator + Terraform
├── setup.py # Package setup
└── requirements.txt # Python dependencies
Two Terraform stacks:
terraform/at the repo root manages AWS Batch infrastructure (ECR, compute environment, job queue, job definitions) used for scaling GFM/GFM-exp ingestion.deployment/terraform/is a separate stack that provisions the OWP production STAC API environment (EC2, RDS, networking). They are independent and must be applied separately.
- Python 3.9+
- AWS credentials configured (for S3 access)
- Required Python packages
# Clone the repository
git clone <repository-url>
cd benchmarkcat
# Install dependencies
pip install -r requirements.txt
# Install the package in development mode
pip install -e .We call the process of creating STAC metadata for each data source "ingestion". Each data source has its own ingestion module with a collection script. The general pattern is:
python3 -m ingest.<source>.<source>_col \
--bucket_name <s3-bucket> \
--asset_object_key <s3-path-to-data> \
--catalog_path <s3-path-to-catalog> \
--link_type uri \
--reprocess_assets # Optional: force reprocessingpython3 -m ingest.iceye.iceye_col \
--bucket_name fimc-data \
--asset_object_key benchmark/rs/iceye/ \
--catalog_path benchmark/stac-bench-cat/ \
--link_type uripython3 -m ingest.ble.ble_col \
--bucket_name fimc-data \
--asset_object_key benchmark/high_resolution_validation_data_ble/ \
--catalog_path benchmark/stac-bench-cat/ \
--link_type uriGFM uses Dartmouth Flood Observatory (DFO) events; data lives under benchmark/rs/gfm/. Supports --workers, --checkpoint-every, --profile, and OWP QC (use --skip-owp-qc to disable). Optional date filters: --after-date, --before-date, --dates (limit by scene acquisition date parsed from the Sentinel product name).
python3 -m ingest.gfm.gfm_col \
--bucket_name fimc-data \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/gfm/ \
--hucs_object_key benchmark/stac-bench-cat/assets/WBDHU8_webproj.gpkg \
--boundaries_object_key benchmark/stac-bench-cat/assets/Mexico_Canada_boundaries.gpkg \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_collection.parquet \
--link_type uri \
--workers 6 \
--checkpoint-every 10 \
--profile DataGFM expanded uses date-based PI4 scenes under benchmark/rs/PI4/. When OWP QC is enabled (default), items get owp:* properties (e.g. owp:qc_grade, owp:huc_summaries). Use --skip-owp-qc for faster runs without QC. Optional date filters: --after-date, --before-date, --dates (filter by date folder; e.g. only process 2024-01-01/ through 2024-06-30/).
python3 -m ingest.gfm_exp.gfm_exp_col \
--bucket_name fimc-data \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/PI4/ \
--hucs_object_key benchmark/stac-bench-cat/assets/WBDHU8_webproj.gpkg \
--boundaries_object_key benchmark/stac-bench-cat/assets/Mexico_Canada_boundaries.gpkg \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_expanded_collection.parquet \
--link_type uri \
--workers 6 \
--checkpoint-every 10 \
--profile DataBuild and run any ingest module in a container:
- GFM
docker build -t benchmarkcat .
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat \
ingest.gfm.gfm_col \
--bucket_name fimc-data \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/gfm/ \
--hucs_object_key benchmark/stac-bench-cat/assets/WBDHU8_webproj.gpkg \
--boundaries_object_key benchmark/stac-bench-cat/assets/Mexico_Canada_boundaries.gpkg \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_collection.parquet \
--link_type uri \
--workers 6 \
--checkpoint-every 10 \
--profile Data \
2>&1 | tee logs/gfm_col_run_wo_batch_worker.log- GFM Expanded
docker build -t benchmarkcat .
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat \
ingest.gfm_exp.gfm_exp_col \
--bucket_name fimc-data \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/PI4/ \
--hucs_object_key benchmark/stac-bench-cat/assets/WBDHU8_webproj.gpkg \
--boundaries_object_key benchmark/stac-bench-cat/assets/Mexico_Canada_boundaries.gpkg \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_expanded_collection.parquet \
--link_type uri \
--workers 6 \
--checkpoint-every 10 \
--profile Data \
2>&1 | tee logs/gfm_exp_col_run_wo_batch_worker.logA separate lightweight container for running the Prefect pipeline orchestrator and Terraform. It does not include GDAL or geospatial libraries.
docker build -f Dockerfile.orchestration -t benchmarkcat:orchestration .
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat:orchestration \
python3 scripts/run_pipeline_prefect.py --helpGFM and GFM expanded support a 3-phase batch workflow for scaling to many scenes. For local testing, run Phase 1, then Phase 2 (e.g. with --job-index 0), then Phase 3. All examples below use placeholder S3 paths under benchmark/stac-bench-cat/ and benchmark/rs/; replace with your bucket and paths as needed.
For AWS Batch deployment (Terraform, Docker build/push, and the run_pipeline_prefect.py Prefect orchestrator), see docs/aws-batch-pipeline.md.
Date filters (--after-date, --before-date, --dates) are applied only at Phase 1 (batch_split). Phase 2 workers process their slice of the manifest as-is and do not re-apply date filters; this avoids double filtering. When Phase 1 uses date filters, a sidecar metadata file is written at <manifest_s3_key>.meta.json with total_scenes, manifest_s3_key, created_at, and when applicable after_date, before_date, and/or dates so you can see what filters were used when the manifest was built.
Crash recovery & skip logic: Phase 2 workers automatically skip scenes that were already fully processed (parquet row exists and item JSON is present on S3). On startup, each worker loads the master parquet and any existing partial parquets from previous runs, so scenes completed by sibling workers before a crash are recognized and not reprocessed. Only newly processed scenes are written to this worker's partial parquet.
Phase 1 — Split (discover DFO events, write manifest to S3; optional --after-date, --before-date, --dates to limit manifest to scenes in that date range by acquisition date):
python3 -m ingest.gfm.batch_split \
--bucket_name fimc-data \
--asset_object_key benchmark/rs/gfm/ \
--manifest-s3-key benchmark/stac-bench-cat/batch/gfm_manifest.jsonl \
--profile DataWith Docker:
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat \
ingest.gfm.batch_split \
--bucket_name fimc-data \
--asset_object_key benchmark/rs/gfm/ \
--manifest-s3-key benchmark/stac-bench-cat/batch/gfm_manifest.jsonl \
--profile DataPhase 2 — Worker (process a slice; for local test use --job-index 0):
python3 -m ingest.gfm.gfm_col \
--mode batch-worker \
--bucket_name fimc-data \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/gfm/ \
--hucs_object_key benchmark/stac-bench-cat/assets/WBDHU8_webproj.gpkg \
--boundaries_object_key benchmark/stac-bench-cat/assets/Mexico_Canada_boundaries.gpkg \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_collection.parquet \
--manifest-s3-key benchmark/stac-bench-cat/batch/gfm_manifest.jsonl \
--partial-parquet-prefix benchmark/stac-bench-cat/batch/gfm_partials \
--workers 6 \
--job-index 0 \
--scenes-per-job 1000 \
--profile DataWith Docker:
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat \
ingest.gfm.gfm_col \
--mode batch-worker \
--bucket_name fimc-data \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/gfm/ \
--hucs_object_key benchmark/stac-bench-cat/assets/WBDHU8_webproj.gpkg \
--boundaries_object_key benchmark/stac-bench-cat/assets/Mexico_Canada_boundaries.gpkg \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_collection.parquet \
--manifest-s3-key benchmark/stac-bench-cat/batch/gfm_manifest.jsonl \
--partial-parquet-prefix benchmark/stac-bench-cat/batch/gfm_partials \
--workers 6 \
--job-index 0 \
--scenes-per-job 1000 \
--profile Data \
2>&1 | tee logs/gfm_col_run_with_batch_worker.logPhase 3 — Merge (concatenate partial parquets, rebuild collection.json):
python3 -m ingest.gfm.batch_merge \
--bucket_name fimc-data \
--partial-parquet-prefix benchmark/stac-bench-cat/batch/gfm_partials \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_collection.parquet \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/gfm/ \
--profile Data \
--keep-partialsAdd --keep-partials to preserve partial parquets for debugging.
With Docker:
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat \
ingest.gfm_exp.batch_merge \
--bucket_name fimc-data \
--partial-parquet-prefix benchmark/stac-bench-cat/batch/gfm_exp_partials \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_expanded_collection.parquet \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/PI4/ \
--profile Data \
--keep-partials \
2>&1 | tee logs/gfm_col_run_merge.logPhase 1 — Split (discover date/scene pairs, write manifest; GFM-exp supports --after-date, --before-date, and --dates (filter by date folder)):
python3 -m ingest.gfm_exp.batch_split \
--bucket_name fimc-data \
--asset_object_key benchmark/rs/PI4/ \
--manifest-s3-key benchmark/stac-bench-cat/batch/gfm_exp_manifest.jsonl \
--profile DataWith Docker:
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat \
ingest.gfm_exp.batch_split \
--bucket_name fimc-data \
--asset_object_key benchmark/rs/PI4/ \
--manifest-s3-key benchmark/stac-bench-cat/batch/gfm_exp_manifest.jsonl \
--profile DataPhase 2 — Worker (for local test use --job-index 0):
python3 -m ingest.gfm_exp.gfm_exp_col \
--mode batch-worker \
--bucket_name fimc-data \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/PI4/ \
--hucs_object_key benchmark/stac-bench-cat/assets/WBDHU8_webproj.gpkg \
--boundaries_object_key benchmark/stac-bench-cat/assets/Mexico_Canada_boundaries.gpkg \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_expanded_collection.parquet \
--manifest-s3-key benchmark/stac-bench-cat/batch/gfm_exp_manifest.jsonl \
--partial-parquet-prefix benchmark/stac-bench-cat/batch/gfm_exp_partials \
--workers 6 \
--job-index 0 \
--scenes-per-job 1000 \
--profile Data \
2>&1 | tee logs/gfm_exp_col_run_with_batch_worker.logWith Docker:
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat \
ingest.gfm_exp.gfm_exp_col \
--mode batch-worker \
--bucket_name fimc-data \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/PI4/ \
--hucs_object_key benchmark/stac-bench-cat/assets/WBDHU8_webproj.gpkg \
--boundaries_object_key benchmark/stac-bench-cat/assets/Mexico_Canada_boundaries.gpkg \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_expanded_collection.parquet \
--manifest-s3-key benchmark/stac-bench-cat/batch/gfm_exp_manifest.jsonl \
--partial-parquet-prefix benchmark/stac-bench-cat/batch/gfm_exp_partials \
--workers 6 \
--job-index 0 \
--scenes-per-job 1000 \
--profile Data \
2>&1 | tee logs/gfm_exp_col_run_with_batch_worker.logPhase 3 — Merge:
python3 -m ingest.gfm_exp.batch_merge \
--bucket_name fimc-data \
--partial-parquet-prefix benchmark/stac-bench-cat/batch/gfm_exp_partials \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_expanded_collection.parquet \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/PI4/ \
--profile Data \
--keep-partialsAdd --keep-partials to preserve partial parquets for debugging.
With Docker:
docker run --rm \
-v "$HOME/.aws:/root/.aws" \
benchmarkcat \
ingest.gfm_exp.batch_merge \
--bucket_name fimc-data \
--partial-parquet-prefix benchmark/stac-bench-cat/batch/gfm_exp_partials \
--derived_metadata_path benchmark/stac-bench-cat/assets/derived-asset-data/gfm_expanded_collection.parquet \
--catalog_path benchmark/stac-bench-cat/ \
--asset_object_key benchmark/rs/PI4/ \
--profile Data \
--keep-partials \
2>&1 | tee logs/gfm_exp_col_run_merge.logCommon arguments across all ingestion scripts:
--bucket_name: S3 bucket containing the data (default:fimc-data)--asset_object_key: S3 path prefix to the data directory--catalog_path: S3 path to the STAC catalog (default:benchmark/stac-bench-cat/)--link_type: Type of asset links -uri(S3 URIs) orurl(HTTP URLs)--reprocess_assets: Force reprocessing of assets even if cached--derived_metadata_path: S3 path for cached metadata (Parquet file)--profile: AWS profile name for boto3 (optional)
GFM and GFM Expanded additionally support:
--workers: Number of parallel workers (default: 1); use > 1 for local parallelism--checkpoint-every: Flush item JSONs and parquet every N scenes (default: 50); 0 = only at end--skip-owp-qc: Skip OWP QC grading and HUC-level metrics (faster runs)--hucs_object_key: S3 key for HUC8 boundaries GPKG (GFM/GFM-exp)--boundaries_object_key: S3 key for Mexico/Canada boundaries (GFM/GFM-exp; used to skip non-CONUS scenes)- Date filters:
--after-date(YYYY-MM-DD),--before-date(YYYY-MM-DD),--dates(comma-separated list). Limit processing to a date range or specific dates. GFM-exp: filters by date folder (top-level PI4 dirs). GFM: filters by scene acquisition date (parsed from Sentinel product name in path). Applied in order: after_date, then before_date, then dates list.
Batch-worker mode (GFM/GFM-exp) also uses: --mode batch-worker, --manifest-s3-key, --partial-parquet-prefix, --job-index (or AWS_BATCH_JOB_ARRAY_INDEX, AZ_BATCH_TASK_ID, BATCH_TASK_INDEX), --scenes-per-job, --workers
Each ingestion module follows this pipeline:
- Initialize S3 utilities - Connect to S3 and setup helpers
- Create collection - Define STAC collection metadata
- List data directories - Find all event/gauge/tile directories
- Process assets - For each directory:
- Extract metadata from JSON/XML files
- Calculate geometries from vector/raster files
- Generate thumbnails
- Standardize units (e.g., depth to inches)
- Cache results in Parquet for efficiency
- Create STAC items - Generate STAC items with properties and assets
- Validate - Validate collection and items against schemas
- Upload - Upload collection JSON to S3 catalog
To improve performance, derived metadata (geometries, statistics, etc.) is cached in Parquet files:
- Location:
benchmark/stac-bench-cat/assets/derived-asset-data/<collection>.parquet - Use
--reprocess_assetsflag to force regeneration - Cached data includes: geometries, bounding boxes, statistics, thumbnail paths, along with any other metadata that is extracted from a data source during collection creation.
Creates a local STAC catalog from a catalog on an object store. This script also downloads the assets for each item and modifies the asset HREFs to be HTTP links meant to be served from a HTTP file server. This script is currently used in conjunction with stac-migraction.sh to create a local STAC catalog with HTTP links then load that catalog into an API:
from scripts.stac_processor import STACProcessor
processor = STACProcessor(
base_url="http://0.0.0.0:8000/",
skip_existing=False
)
# Process a local STAC catalog
processor.process_catalog("path/to/catalog.json")This script converts a static, self-contained catalog on S3 into a catalog served via a STAC API. It was created as a way to deploy the benchmark STAC to the ParallelWorks environment. One of its main components is stac_processor.py.
For more information on using stac-migration.sh to move the static benchmark STAC on S3 to ParallelWorks see scripts/migrating-s3-catalog-to-OE.txt
There is also a version of the Benchmark STAC API that serves data directly from the object store. Historically this API was updated using the subset of this script that loads a catalog into a STAC API from a collection of static STAC json files.
Normalize all relative catalog links relative to an S3 catalog root. This is a convenience script to quickly create a catalog with absolute S3 links between catalog items from a more portable self-contained catalog with relative links.
This script takes a directory of zip files and unzips each archives contents to its own directory inside a target destination directory. This is the script that was used to extract the data provided by ICEYE for the ICEYE collection.
This script deletes and recreates a STAC collection on a STAC API. After the collection is recreated in the API it then uploads all items to the API from a directory containing the collection's item JSON. This can be used to refresh individual collections inside the benchmark STAC API that contains S3 HREFs.
JSON Schema definitions for validating STAC collections and items are in the schemas/ directory.
Two ways to validate:
- Against the official STAC spec: use
stac validate <path>(requires stactools:pip install stactools). - Against this project's schemas: use
stac-validator <path> --custom <schema-path>(requires stac-validator:pip install stac-validator).
Spec conformance (stactools)
# GFM and GFM expanded modified collection
stac validate stac/gfm/gfm-collection-modified/collection.json
stac validate stac/gfm-exp/gfm-expanded-collection-modified/collection.json
# GFM and GFM expanded modified item
stac validate stac/gfm/gfm-collection-modified/DFO-4230_tile-S1A_IW_GRDH_1SDV_20150316T235500_20150316T235525_005064_0065C2_6FB7/DFO-4230_tile-S1A_IW_GRDH_1SDV_20150316T235500_20150316T235525_005064_0065C2_6FB7.json
stac validate stac/gfm-exp/gfm-expanded-collection-modified/GFM-expanded_S1A_IW_GRDH_1SDV_20210801T003626_20210801T003651_039029_049AEE_F470/GFM-expanded_S1A_IW_GRDH_1SDV_20210801T003626_20210801T003651_039029_049AEE_F470.jsonCustom schema (stac-validator)
# ICEYE
stac-validator collection.json --custom schemas/iceye/v1.0.0/iceye_collection.json
stac-validator item.json --custom schemas/iceye/v1.0.0/iceye_item.json
# GFM and GFM expanded modified collection
stac-validator stac/gfm/gfm-collection-modified/collection.json --custom schemas/gfm/v1.0.0/gfm_collection.json
stac-validator stac/gfm-exp/gfm-expanded-collection-modified/collection.json --custom schemas/gfm/v1.0.0/gfm_collection.json
# GFM and GFM expanded modified item: schema uses relative $refs; run from schema directory so they resolve
cd schemas/gfm/v1.0.0 && stac-validator ../../../stac/gfm/gfm-collection-modified/DFO-4230_tile-S1A_IW_GRDH_1SDV_20150316T235500_20150316T235525_005064_0065C2_6FB7/DFO-4230_tile-S1A_IW_GRDH_1SDV_20150316T235500_20150316T235525_005064_0065C2_6FB7.json --custom gfm_item.json
# if shell is in schemas/gfm/v1.0.0, run the following else do a cd schemas/gfm/v1.0.0
stac-validator ../../../stac/gfm-exp/gfm-expanded-collection-modified/GFM-expanded_S1A_IW_GRDH_1SDV_20210801T003626_20210801T003651_039029_049AEE_F470/GFM-expanded_S1A_IW_GRDH_1SDV_20210801T003626_20210801T003651_039029_049AEE_F470.json --custom gfm_item.jsonEach schema directory contains:
<collection>_collection.json- Collection schema<collection>_item.json- Item schemaREADME.md- Documentation with examples
See individual schema READMEs:
To add a new data source collection:
-
Create module directory:
ingest/<source>/ -
Create core files:
ingest/<source>/ ├── __init__.py ├── <source>_stac.py # Asset definitions, utilities ├── <source>_handle_assets.py # Asset processing logic ├── <source>_col.py # Collection generation script └── README.md # Documentation -
Implement asset handler (
<source>_handle_assets.py):- Inherit patterns from existing handlers (BLE, ICEYE, GFM)
- Extract metadata from source files
- Calculate geometries and statistics
- Organize assets by type
- Generate thumbnails (optional)
- Implement Parquet caching
-
Implement collection script (
<source>_col.py):- Parse command-line arguments
- Create STAC collection with metadata
- Process each data unit (event/gauge/tile)
- Create STAC items with properties and assets
- Validate and upload to S3
-
Create schemas:
schemas/<source>/v1.0.0/- Collection schema JSON
- Item schema JSON
- README with examples
-
Document:
- Module README in
ingest/<source>/README.md - Update main README (this file)
- Add schema documentation
- Module README in
Reference the ICEYE implementation as a template:
- ingest/iceye/ - Complete module
- schemas/iceye/v1.0.0/ - Schemas
- Includes thumbnail generation and depth standardization features
Many collections automatically generate PNG thumbnails from extent files for quick preview:
thumbnail_s3_path = s3_utils.make_and_upload_thumbnail(
local_extent_path,
local_thumbnail_path,
bucket_name,
extent_file_key
)ICEYE data automatically standardizes depth measurements to inches:
- Detects original unit (feet/inches) from metadata or raster analysis
- Applies conversion factor (12.0 for feet → inches)
- Documents conversion in STAC properties
- Preserves provenance information
Complex multipolygon flood extents are simplified to convex hulls for STAC items, reducing file size while maintaining spatial coverage.
Flood extent collections include associated discharge data from the NWM (National Water Model):
- Flowfiles link stream segments to discharge values
- Statistics (min, max, mean, median) computed
- Column metadata documented in STAC properties
When OWP QC is enabled (default for GFM and GFM Expanded), each STAC item is enriched with per-HUC reliability and severity metrics. The QC pipeline mosaics raster tiles, clips to each intersecting HUC8, and computes flood area, uncertainty, observability, advisory noise, affected population, and anomaly ratio.
| Property | Type | Description |
|---|---|---|
owp:qc_grade |
string | Scene-level Data Quality Grade — best grade across all HUCs (A/B/C/D) |
owp:impact_score |
string | Scene-level Impact Score — highest impact across all HUCs (High/Medium/Low) |
owp:active_hucs |
list[string] | HUC8 ids that received grade A or B |
owp:total_flood_area_km2 |
float | Sum of flood area across all HUCs (km²) |
owp:huc_summaries |
list[object] | Per-HUC detail objects (see schema below) |
owp:qc_criteria |
object | Snapshot of grading/impact thresholds used to produce grades (version, grading rules, impact rules) |
Each entry in owp:huc_summaries has this structure:
| Field | Type | Description |
|---|---|---|
huc8_id |
string | HUC8 identifier |
qc_grade |
string | Data Quality Grade for this HUC (A/B/C/D) |
impact |
string | Impact Score for this HUC (High/Medium/Low) |
metrics.flood_area_km2 |
float | Flood extent area in km² (pixel count × 0.0004) |
metrics.uncertainty_mean |
float | Mean uncertainty over flooded pixels |
metrics.observability_pct |
float | Percentage of valid pixels not excluded (0–100) |
metrics.advisory_noise_pct |
float | Percentage of flooded pixels flagged by advisory layer (0–100) |
metrics.affected_pop |
int | Sum of population raster values over flooded pixels |
metrics.normalized_anomaly_ratio |
float | Ratio of flood pixels to observed-water pixels |
The QC Configuration file sets the thresholds values listed below.
- Grade A: uncertainty > 75, observability > 80%, advisory noise < 5%
- Grade B: uncertainty > 60, observability > 60%, advisory noise < 20%
- Grade C: flood signal present but metrics below B thresholds
- Grade D: incomplete data, observability < 50%, or advisory noise > 50%
- High: affected population > 100 or flood area > 5 km²
- Medium: affected population > 10 or flood area > 1 km²
- Low: below Medium thresholds
Shared utilities are in ingest/utils.py (S3Utils, RasterUtils) and ingest/flows.py (FlowfileUtils):
from ingest.utils import S3Utils
s3_utils = S3Utils(boto3.client('s3'))
# List subdirectories
dirs = s3_utils.list_subdirectories(bucket, prefix)
# List files by extension
files = s3_utils.list_files_with_extensions(bucket, prefix, ['.tif', '.gpkg'])
# Generate asset href
href, is_valid = s3_utils.generate_href(bucket, key, link_type='uri')
# Upload collection
s3_utils.update_collection(collection, 'collection-id', catalog_path, bucket)from ingest.utils import RasterUtils
# Count non-zero pixels
pixel_count = RasterUtils.count_pixels(raster_path)
# Get max value
max_value = RasterUtils.get_max_value(raster_path)
# Get WKT2 projection
wkt2 = RasterUtils.get_wkt2_string(raster_path)
# Create thumbnail
RasterUtils.create_preview(raster_path, preview_path)from ingest.flows import FlowfileUtils
# Download flowfiles from S3
flowfile_dfs = FlowfileUtils.download_flowfiles(bucket, keys, s3_client)
# Extract statistics
flowstats = FlowfileUtils.extract_flowstats(flowfile_dfs)
# Create flowfile object for STAC
flowfile_obj = FlowfileUtils.create_flowfile_object(ids, flowstats, columns)The STAC catalog is stored in S3 with this structure:
s3://fimc-data/benchmark/stac-bench-cat/
├── catalog.json # Root catalog
├── ble-collection/
│ ├── collection.json
│ └── <huc8>-ble/
│ └── <huc8>-ble.json # Item
├── iceye-collection/
│ ├── collection.json
│ └── <event-id>/
│ └── <event-id>.json # Item
├── gfm-collection/
│ ├── collection.json
│ └── DFO-<event>_tile-<sentinel-id>/
│ └── DFO-<event>_tile-<sentinel-id>.json # Item
├── gfm-expanded-collection/
│ ├── collection.json
│ └── GFM-expanded_<sentinel-id>/
│ └── GFM-expanded_<sentinel-id>.json # Item
└── assets/
├── derived-asset-data/ # Cached metadata
│ ├── ble_collection.parquet
│ ├── iceye_collection.parquet
│ ├── gfm_collection.parquet
│ └── gfm_expanded_collection.parquet
└── thumbnails/ # Generated thumbnails
- Follow existing patterns in collection modules
- Use type hints where appropriate
- Document functions with docstrings
- Log important operations with
loggingmodule
When adding new properties to schemas:
- Add property definition to item schema
- Update collection schema if needed
- Document in schema README
- Update ingestion code to populate property
- Validate generated STAC items
Issue: NoCredentialsError when running ingestion
- Solution: Configure AWS credentials (
aws configure) or copy and paste AWS credentials into the shell you are running the collection creation code from.
Issue: Parquet file conflicts
- Solution: Use
--reprocess_assetsflag to regenerate cached data
Issue: S3 permission errors
- Solution: Ensure IAM role has read/write access to bucket
Issue: Validation errors
- Solution: Check schema compatibility and required fields
All ingestion scripts log to console. For debugging:
import logging
logging.basicConfig(level=logging.DEBUG)When contributing new data sources or features:
- Follow the established module structure
- Create comprehensive schemas
- Document thoroughly (code comments, READMEs)
- Test with real data
- Update this README
- STAC Specification
- STAC Extensions
- PySTAC Documentation
- FEMA BLE Data
- GLOFAS GFM
- ICEYE Flood Monitoring
[Specify license information here]
@dylanlee
@robgpita
@biplovbhandari