Access, index, and analyze NASA GEDI LiDAR data with H3 hexagonal spatial indexing and Dask-distributed processing
Project description
gedih3
Turn billions of NASA GEDI LiDAR footprints into analysis-ready spatial datasets — from the command line or Python.
GEDI (Global Ecosystem Dynamics Investigation) is NASA's premier spaceborne LiDAR mission, measuring forest structure and carbon stocks globally. Its raw data comprises thousands of large HDF5 files organized by satellite orbit — not by geography. Navigating quality flags, extracting variables of interest, and running spatial queries over billions of footprints requires significant engineering effort and domain expertise.
gedih3 handles all of that. It converts orbit-organized GEDI HDF5 files into a spatially-indexed GeoParquet database built with expert-curated presets and pre-configured quality filtering, then provides a complete toolchain for querying, aggregating, and exporting to GeoTIFF, GeoParquet, and other common formats.
What is GEDI-H3?
gedih3 is built on four components that together make billion-shot GEDI analysis tractable:
| Component | Role |
|---|---|
| GEDI | NASA ISS-mounted LiDAR measuring forest height, biomass, and canopy structure at ~25 m footprints globally |
| H3 | Uber's hexagonal spatial indexing system — the primary database index enabling fast regional queries |
| Dask | Distributed Python computing — scales from a laptop to an HPC cluster without changing your code |
| earthaccess | NASA's official library for Earthdata authentication, search, and download |
Why gedih3?
Working with GEDI at scale is genuinely hard:
- Orbit-organized, not spatially indexed — files are sorted by acquisition time, not geography. A regional query means scanning thousands of files.
- Complex HDF5 containers — deeply nested structure with hundreds of variables per beam, requiring specialized tools to navigate correctly.
- Quality filtering is non-trivial — each product has its own flags; best practices require combining multiple criteria correctly to avoid biased results.
- Variable overload — L2A alone provides 300+ variables per beam. Choosing the right ones for your analysis requires domain expertise.
- Scale — the full dataset runs to terabytes and billions of rows. Without spatial indexing and distributed processing, even simple analyses can take days.
gedih3 addresses all of this.
Key Features
- Expert-curated
minimalanddefaultvariable presets for all GEDI products (L1B, L2A, L2B, L4A, L4C) - Pre-configured quality filtering applied with a single flag (
-y | --quality) - H3 hexagonal spatial indexing (levels 0–15) with partition pruning — only tiles intersecting your region are read
- Incremental database: add new orbits, time periods, or variables without rebuilding; interrupted builds resume automatically
- Memory-efficient builds: streams HDF5 data beam-by-beam, writes partitions independently, merges at row-group level
- Network-efficient downloads: S3 streaming with on-the-fly variable subsetting and automatic resume
- Build once, iterate fast: once built, extract/aggregate/rasterize run in seconds to minutes
- Dask-distributed processing; works on laptops, workstations, and HPC clusters without code changes
- command-line tools covering the full pipeline from download to analysis ready data outputs
- Full Python API with custom aggregation functions (any callable, e.g., per-hexagon regression)
- DuckDB compatible for spatial SQL querying, including larger-than-memory datasets
- Multi-format export: GeoTIFF (with compression, tiling, time-series), GeoParquet, and others
- Ancillary data fusion: sample external rasters and join vector polygons at GEDI footprint level
How it works
gedih3 is designed around a simple principle: build once, iterate fast.
-
Build (
gh3_build): Reads raw HDF5 files beam-by-beam via Dask, assigns each shot an H3 spatial index, and writes partitioned GeoParquet — one file per spatial tile per year. A build log tracks every ingested granule. Interrupted builds resume from where they stopped. New data merges into the existing database. -
Query (
gh3_load/gh3_extract): The H3 partition structure resolves which files to read via index arithmetic — no scanning. Only partitions whose H3 cells intersect your region are opened. -
Aggregate (
gh3_aggregate): Every shot carries an H3 cell ID, so aggregation to any coarser resolution is a local groupby within each partition — no data shuffle across parallel workers.
Quick Start
# Install with pip — self-contained, no system libraries needed
pip install gedih3
# ...or with conda, recommended for HPC and shared clusters
git clone https://github.com/tiagodc/GEDI-H3.git
cd GEDI-H3
conda env create -f environment.yml
conda activate gedih3
GDAL, GEOS, PROJ and HDF5 all ship prebuilt inside the wheels, so
pip installneeds nothing installed system-wide. See the installation guide for platform coverage and the optional GDAL Python bindings.
No configuration needed. All outputs default to ~/gedi_data/.
# 1. Download GEDI data for a region (W,S,E,N)
gh3_download -r "-51,0,-50,1" -l2a minimal -l4a minimal
# 2. Build the H3 spatial database
gh3_build -r "-51,0,-50,1" -l2a minimal -l4a minimal
## Optionally build DuckDB metadata table (Ducklake)
gh3_build_ducklake
# 3. See which variables are available
gh3_read_schema
# 4. Extract with quality filtering
gh3_extract -y -l agbd_l4a rh_098_l2a -o extracted/
# 5. Aggregate to ~36 km² hexagons
gh3_aggregate -d extracted/ -h3 6 -a mean -o aggregated/
# 6. Export as GeoTIFF
gh3_rasterize -d aggregated/ -o rasters/ --compress LZW
Run any tool with
--helpfor the full list of options and flags:gh3_build --help gh3_aggregate --help
CLI Tools
| Tool | Purpose |
|---|---|
gh3_download |
Download GEDI HDF5 data from NASA DAACs |
gh3_build |
Build H3-indexed Parquet database from HDF5 files |
gh3_build_ducklake |
Build DuckDB metadata database for SQL queries |
gh3_extract |
Extract and filter shots to flat Parquet files |
gh3_aggregate |
Aggregate to coarser H3 resolution |
gh3_rasterize |
Export aggregated data as GeoTIFF |
gh3_update |
Merge new variables into an existing dataset |
gh3_from_img |
Sample external raster values at GEDI shot locations |
gh3_from_polygon |
Join vector polygon attributes to GEDI shots |
gh3_list_resolutions |
View H3 and EGI resolution level tables |
gh3_read_schema |
Inspect schemas and browse variables from any file or database |
Common Flags
-r, --region Spatial filter: bbox "W,S,E,N", vector file, or ISO3 country code
-t0, -t1 Temporal filters (YYYY-MM-DD)
-l2a, -l4a, ... Product variables: 'minimal', 'default', or explicit variable names
-y, --quality Apply pre-configured quality filters
-q, --query Pandas query string filter
-N, -T, -M Dask workers, threads per worker, memory per worker
-v / -vv / -Q Verbosity: INFO / DEBUG / quiet
Python API
All CLI functionality is available from Python — with the added benefit of chaining operations in memory without saving intermediate files to disk.
import gedih3.gh3driver as gh3
from gedih3 import raster
# Load data from the H3 database
ddf = gh3.gh3_load(
source='~/gedi_data/h3/',
columns=['agbd_l4a', 'rh_098_l2a'],
region='-51,0,-50,1', # bbox, shapefile, or ISO3
query='quality_flag_l2a == 1',
)
# Aggregate to H3 level 6 (~36 km²) and export as GeoTIFF
agg = gh3.gh3_aggregate(ddf, target_res=6, agg='mean').compute()
raster.export_raster(raster.h3_to_raster(agg), 'agbd_mean.tif', compress='LZW')
Custom Aggregation Functions
The Python API accepts any callable as the aggregation function. Each H3 hexagon's data is passed as a DataFrame, enabling analyses not possible from the CLI:
import numpy as np, pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
def fit_regression(df):
"""Fit height → biomass regression per hexagon."""
mask = ~(df['agbd_l4a'].isna() | df['rh_098_l2a'].isna())
X, y = df.loc[mask, 'rh_098_l2a'].values.reshape(-1, 1), df.loc[mask, 'agbd_l4a'].values
if len(X) < 2:
return pd.DataFrame({'r2': [np.nan], 'n': [len(df)]})
model = LinearRegression().fit(X, y)
return pd.DataFrame({'r2': [r2_score(y, model.predict(X))], 'n': [len(df)]})
# Per-hexagon regression across all partitions, in parallel
results = gh3.gh3_aggregate(ddf, target_res=6, agg=fit_regression)
results.compute()
Spatial Indexing
H3 Hexagonal Index
H3 is the primary spatial index. The database uses a dual-level H3 structure: a coarse partition level (default: level 3, ~12,000 km²) for file organization, and a fine index level (default: level 12, ~307 m²) for shot-level precision.
| Level | Avg. Area | Typical Use |
|---|---|---|
| 3 | ~12,393 km² | Partition level (database tiles) |
| 6 | ~36 km² | Regional aggregation |
| 9 | ~0.105 km² | Local analysis |
| 12 | ~307 m² | Index level (shot precision) |
Note: H3 parent hexagons are not perfectly geometrically inclusive of their children — a known property of hexagonal grids.
gh3_aggregatehandles this correctly and consistently.
EGI Square-Pixel Index (Advanced)
For workflows that require alignment with standard raster grids or the GEDI L4B gridded product, gedih3 supports the EASE Grid Index (EGI) — square pixels on EASE-Grid 2.0 (EPSG:6933).
| Level | Pixel Size | Typical Use |
|---|---|---|
| 3 | ~25 m | GEDI footprint level |
| 6 | ~1 km | GEDI L4B baseline |
| 8 | ~10 km | Wall-to-wall analysis |
Note: Lower EGI level = finer resolution (opposite of H3).
# EGI extraction and aggregation
gh3_extract -d ~/gedi_data/h3/ -egi 6 -o extracted_egi/
gh3_aggregate -d ~/gedi_data/h3/ -egi 6 -a mean -o aggregated_egi/
Architecture
---
config:
theme: dark
layout: dagre
---
flowchart TB
n1["☁️ NASA DAAC"] --> n2["🌐 earthaccess"]
n2 --> n17["⬇️ gh3_download"]
n17 --> n4["🗂️ HDF5 Files"]
n4 --> C["⚙️ gh3_build"]
C --> n3["💽 H3 Database\n(Parquet)"]
n3 --> D["gh3_extract"] & n10["gh3_read_schema"]
D --> SHOTS["📂 Flat Dataset"]
SHOTS --> n6["gh3_aggregate"] & RAST["gh3_rasterize"]
n6 --> AGG["📊 Aggregated Dataset"]
AGG --> RAST
RAST --> TIF["🗺️ GeoTIFF"]
SHOTS <--> n8["gh3_from_img"] & n9["gh3_from_polygon"]
n15["External Raster"] -.-> n8
n16["External Vector"] -.-> n9
n1@{ shape: db}
n2@{ shape: com-link}
n4@{ shape: das}
n3@{ shape: disk}
SHOTS@{ shape: das}
AGG@{ shape: das}
TIF@{ shape: das}
n15@{ shape: das}
n16@{ shape: das}
n15:::fade
n16:::fade
classDef fade stroke:#757575,color:#757575
linkStyle 13 stroke:#757575,fill:none
linkStyle 14 stroke:#757575,fill:none
GEDI Products Supported
| Product | Description |
|---|---|
| L1B | Geolocated waveforms |
| L2A | Elevation and height metrics (RH percentiles, canopy height) |
| L2B | Canopy cover and vertical structure profiles |
| L4A | Footprint-level aboveground biomass (AGBD) |
| L4C | Footprint-level structural complexity (WSCI) |
For variable details, run gh3_read_schema or see gedi.umd.edu.
Configuration
gedih3 works with zero configuration. All outputs default to ~/gedi_data/.
To customize storage locations, set environment variables or create ~/.gedih3.env:
GH3_DEFAULT_DOWNLOAD_DIR=/data/gedi # root for all gedih3 files
GH3_DEFAULT_H3_DIR=/data/gedi/h3_db # H3 database location
GH3_DEFAULT_SOC_DIR=/data/gedi/soc # downloaded HDF5 files
GH3_DEFAULT_TMP_DIR=/data/gedi/tmp # temporary storage
Configuration priority (highest to lowest): CLI arguments → environment variables → ~/.gedih3.env → package defaults.
Tutorials
See the tutorials/ directory:
tutorial_cli_pipeline.sh— End-to-end CLI workflowtutorial_python_api_pipeline.py— Complete Python API examplestutorial_duckdb_basics.ipynb-- DuckDB query examples
Requirements
- Python >= 3.12
- NASA Earthdata account (free, required for downloading GEDI data)
- Key dependencies:
dask,geopandas,h3,pyarrow,h5py,rioxarray,earthaccess - Optional dependencies: duckdb
See pyproject.toml for the full dependency list.
Acknowledgements
gedih3 was developed as a supporting tool in the course of research funded by the National Aeronautics and Space Administration (NASA) contract NNL 15AA03C for the development and execution of the GEDI mission, and NASA FINNEST grant 80NSSC22K1543. This software was not a contracted deliverable of these projects but emerged from work conducted under them.
Copyright and License
Copyright (C) 2026, University of Maryland. All Rights Reserved.
Authors: Tiago de Conto, Amelia Grace Holcomb (University of Maryland).
gedih3 is source available, not open source. It is distributed under the UMD Source Available Non-Commercial End User License Agreement — see the LICENSE and NOTICE files for the full terms. In summary:
- Free for non-commercial use — research, education, and other non-revenue-generating purposes.
- Redistribution of unmodified copies is permitted, including through public package repositories and their mirrors, provided the LICENSE and all required notices travel with every copy.
- Modifications must be contributed back — fork the repository and open a pull request. Distributing modified copies as a separate release is not permitted.
- Commercial use requires a separate license from the University of Maryland.
For commercial licensing inquiries, please contact the University of Maryland's UM Ventures office at umdtechtransfer@umd.edu.
Project details
Release history Release notifications | RSS feed
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 gedih3-0.14.3.tar.gz.
File metadata
- Download URL: gedih3-0.14.3.tar.gz
- Upload date:
- Size: 477.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7399a4cde45c014aad627d9449bad60acd5258b54fb3c097772c16f2f11579c2
|
|
| MD5 |
d0f87265f0a9c73ec2a453ee17242bd9
|
|
| BLAKE2b-256 |
069be15a9753871d1d8bc84da0e81ae81d9b99bb3d92ea82bcb4a00b32b15150
|
Provenance
The following attestation bundles were made for gedih3-0.14.3.tar.gz:
Publisher:
release.yml on tiagodc/GEDI-H3
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gedih3-0.14.3.tar.gz -
Subject digest:
7399a4cde45c014aad627d9449bad60acd5258b54fb3c097772c16f2f11579c2 - Sigstore transparency entry: 2219875427
- Sigstore integration time:
-
Permalink:
tiagodc/GEDI-H3@e7c16354690943eecaf595f8fcd237bcaaaf37cf -
Branch / Tag:
refs/tags/v0.14.3 - Owner: https://github.com/tiagodc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e7c16354690943eecaf595f8fcd237bcaaaf37cf -
Trigger Event:
push
-
Statement type:
File details
Details for the file gedih3-0.14.3-py3-none-any.whl.
File metadata
- Download URL: gedih3-0.14.3-py3-none-any.whl
- Upload date:
- Size: 369.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1150345f249316b3d750d6a6ef27594713359f0a96ea6b8134e480f83c4f5363
|
|
| MD5 |
f8fdd17efb139eddb0be693f86256566
|
|
| BLAKE2b-256 |
1d635fdb0835ec242cea2492f118ea46dca2dc9f69625e121a1c420bf36539d5
|
Provenance
The following attestation bundles were made for gedih3-0.14.3-py3-none-any.whl:
Publisher:
release.yml on tiagodc/GEDI-H3
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gedih3-0.14.3-py3-none-any.whl -
Subject digest:
1150345f249316b3d750d6a6ef27594713359f0a96ea6b8134e480f83c4f5363 - Sigstore transparency entry: 2219875451
- Sigstore integration time:
-
Permalink:
tiagodc/GEDI-H3@e7c16354690943eecaf595f8fcd237bcaaaf37cf -
Branch / Tag:
refs/tags/v0.14.3 - Owner: https://github.com/tiagodc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e7c16354690943eecaf595f8fcd237bcaaaf37cf -
Trigger Event:
push
-
Statement type: