GEDI vs ICESat-2 vs Airborne Lidar for Biomass
The three lidar sources available for forest biomass are usually presented as a resolution-versus-cost trade, which is the least useful framing of the three differences that matter. They differ in what physical quantity they record, in how their samples are distributed in space, and in how well each sample is located on the ground — and those three properties, not resolution, decide whether a source can support a given claim. This guide compares them within biomass estimation from lidar and SAR fusion in the spatial modeling and carbon stock validation stack.
None of the three measures biomass. All three measure something about the vertical arrangement of surfaces that intercept a laser pulse, and biomass is inferred from that by a model calibrated against destructive or allometric field data. The choice between them therefore determines the quality of the predictor going into that model, and a source that produces an excellent predictor in dense tropical forest can produce a nearly useless one in open woodland. Choosing on the basis of a published accuracy figure from a different biome is the most common error in this decision.
Root Cause Analysis
Three properties explain nearly every disagreement between these sources when they are compared over the same forest.
Footprint size interacts with canopy heterogeneity. A 25 m GEDI footprint integrates whatever falls inside it — canopy, gap, understory, and terrain slope together. In closed tropical forest that averaging is benign, because the footprint is smaller than the scale over which structure varies. In fragmented or open canopy it is not: the same returned waveform can be produced by a uniformly medium-height stand and by a tall stand next to a clearing, and the biomass implied by those two is very different. This is why GEDI’s published accuracy degrades sharply outside dense forest, and it is a property of the geometry rather than of the processing.
Terrain slope contaminates the vertical profile. A footprint on a slope receives ground returns spread over a vertical range equal to the footprint width times the tangent of the slope — on a 20-degree slope, a 25 m footprint smears the ground return across about nine metres. That smear is indistinguishable from low vegetation in the waveform, and it biases height metrics upward. Every serious GEDI workflow filters or corrects for slope, and workflows that do not produce a biomass map whose errors correlate with topography, which a verifier can and will see.
Geolocation error moves the sample away from the thing it is supposed to describe. GEDI footprint geolocation is accurate to roughly ten metres, ICESat-2 rather better, airborne lidar to well under a metre when the trajectory solution is good. Ten metres does not sound like much until the footprint is being matched to a field plot of similar size, at which point a substantial fraction of the calibration pairs describe partially different pieces of forest. The resulting model looks noisier than the instrument is, and the noise is attributed to the instrument rather than to the matching.
The practical consequence is that the three sources are not substitutes at all. Airborne lidar produces a map. GEDI and ICESat-2 produce samples that can calibrate or validate a map made from something else — usually SAR or optical imagery — and treating a spaceborne lidar product as if it were a map is the single most common structural error in this area.
Diagnostic Pipeline / Pre-Flight Validation
Before selecting a source, establish whether the site’s geometry and the claim’s requirements are compatible with it. The checks below are the ones that reject a source outright, and running them takes minutes against a decision that costs a survey.
from dataclasses import dataclass
import structlog
log = structlog.get_logger()
@dataclass(frozen=True)
class SiteProfile:
"""Physical properties of the project area that constrain source choice."""
name: str
centroid_lat: float
area_ha: float
mean_slope_deg: float
canopy_closure: float # 0–1, fraction of area with closed canopy
mean_patch_ha: float # mean contiguous forest patch size
revisit_needed_days: int # how often the claim must be refreshed
@dataclass(frozen=True)
class SourceVerdict:
source: str
usable: bool
role: str # map | calibration | validation | none
reasons: tuple[str, ...]
# Instrument geometry. These are structural properties, not tuning knobs.
GEDI_LAT_LIMIT = 51.6
GEDI_FOOTPRINT_M = 25.0
GEDI_GEOLOC_M = 10.0
ICESAT2_FOOTPRINT_M = 14.0
ICESAT2_REPEAT_DAYS = 91
def assess_gedi(site: SiteProfile) -> SourceVerdict:
reasons: list[str] = []
usable = True
if abs(site.centroid_lat) > GEDI_LAT_LIMIT:
return SourceVerdict(
"gedi", False, "none",
(f"site latitude {site.centroid_lat:.1f} is outside the "
f"±{GEDI_LAT_LIMIT}° orbital coverage",),
)
if site.mean_slope_deg > 20.0:
reasons.append(
f"mean slope {site.mean_slope_deg:.0f}° smears the ground return "
f"across ~{GEDI_FOOTPRINT_M * site.mean_slope_deg / 57.3:.0f} m; "
"slope correction is mandatory, not optional"
)
if site.canopy_closure < 0.6:
usable = False
reasons.append(
f"canopy closure {site.canopy_closure:.2f} is below 0.6 — the "
"footprint averages canopy and gap, and height metrics stop "
"discriminating biomass"
)
# A footprint that is large relative to the patch mostly measures the edge.
footprint_ha = 3.14159 * (GEDI_FOOTPRINT_M / 2) ** 2 / 10_000
if site.mean_patch_ha < footprint_ha * 20:
usable = False
reasons.append(
f"mean patch {site.mean_patch_ha:.2f} ha is small relative to the "
f"{footprint_ha:.3f} ha footprint; most shots straddle an edge"
)
return SourceVerdict(
"gedi", usable, "calibration" if usable else "none", tuple(reasons)
)
def assess_airborne(site: SiteProfile) -> SourceVerdict:
reasons: list[str] = []
# Airborne is always geometrically capable; the constraint is temporal.
if site.revisit_needed_days < 365:
reasons.append(
f"claim needs refreshing every {site.revisit_needed_days} d; "
"an airborne campaign is a single-date map and must be paired "
"with a satellite source for change"
)
return SourceVerdict("airborne", True, "map", tuple(reasons))
def select_sources(site: SiteProfile) -> dict[str, SourceVerdict]:
verdicts = {
"gedi": assess_gedi(site),
"airborne": assess_airborne(site),
}
for name, v in verdicts.items():
log.info(
"source.assessed", site=site.name, source=name,
usable=v.usable, role=v.role, reasons=v.reasons,
)
return verdicts
The check that rejects most often is patch size relative to footprint. A project made of narrow riparian strips or smallholder mosaics will fail it, and no amount of processing recovers a signal the geometry never captured.
Deterministic Transformation Logic
Where two sources are combined — the usual arrangement, with spaceborne lidar calibrating a wall-to-wall SAR or optical predictor — the joining step is where most of the recoverable error lives. Two rules make that step defensible.
The first is that a lidar shot and a field plot may only be paired when their geolocation uncertainties overlap by less than a stated fraction of the footprint. Pairing everything within some generous radius inflates the apparent model error and, worse, biases it: mismatched pairs are more likely in heterogeneous forest, so the model is systematically less well constrained exactly where biomass varies most.
The second is that aggregation to the reporting unit happens after prediction, never before. Averaging height metrics across a stand and then applying an allometric relationship is not the same as applying the relationship per shot and then averaging, because the relationship is non-linear. The difference is a real bias with a predictable sign, and it is trivially avoided.
import math
from dataclasses import dataclass
@dataclass(frozen=True)
class Shot:
"""One spaceborne lidar footprint with its geolocation uncertainty."""
shot_id: str
lon: float
lat: float
geoloc_sigma_m: float
rh98_m: float
quality_flag: int
slope_deg: float
@dataclass(frozen=True)
class Plot:
"""One field inventory plot with a measured biomass density."""
plot_id: str
lon: float
lat: float
radius_m: float
agb_mg_ha: float
survey_year: int
def metres_between(a_lon: float, a_lat: float, b_lon: float, b_lat: float) -> float:
"""Local planar distance. Adequate at plot separations; do not use at scale."""
mean_lat = math.radians((a_lat + b_lat) / 2)
dx = (b_lon - a_lon) * 111_320 * math.cos(mean_lat)
dy = (b_lat - a_lat) * 110_540
return math.hypot(dx, dy)
def pair_shots_to_plots(
shots: list[Shot],
plots: list[Plot],
*,
max_slope_deg: float = 20.0,
overlap_fraction: float = 0.5,
) -> list[tuple[Shot, Plot, float]]:
"""Pair each shot with at most one plot, refusing marginal geometry.
A pair is admitted only when the centre separation leaves the footprint
and the plot overlapping by more than `overlap_fraction`, allowing for
the shot's own geolocation sigma. Unpaired shots are dropped, not
relaxed into the set with a wider radius.
"""
pairs: list[tuple[Shot, Plot, float]] = []
for shot in shots:
if shot.quality_flag != 1:
continue
if shot.slope_deg > max_slope_deg:
continue
best: tuple[Plot, float] | None = None
for plot in plots:
d = metres_between(shot.lon, shot.lat, plot.lon, plot.lat)
# Effective tolerance: how far centres may be and still overlap.
tolerance = (
(GEDI_FOOTPRINT_M / 2 + plot.radius_m) * (1 - overlap_fraction)
- shot.geoloc_sigma_m
)
if tolerance <= 0:
continue
if d <= tolerance and (best is None or d < best[1]):
best = (plot, d)
if best is not None:
pairs.append((shot, best[0], best[1]))
return pairs
def predict_then_aggregate(
shots: list[Shot], coef_a: float, coef_b: float
) -> dict[str, float]:
"""Apply the allometry per shot, then aggregate. Never the reverse.
agb = a * rh98 ** b is non-linear, so mean(f(x)) != f(mean(x)). Averaging
height first understates biomass in a stand with mixed heights, by an
amount that grows with the variance of the heights.
"""
if not shots:
return {"n": 0, "agb_mean_mg_ha": 0.0, "agb_naive_mg_ha": 0.0}
per_shot = [coef_a * (s.rh98_m ** coef_b) for s in shots]
mean_height = sum(s.rh98_m for s in shots) / len(shots)
return {
"n": float(len(shots)),
"agb_mean_mg_ha": sum(per_shot) / len(per_shot),
# Reported only so the bias is visible in the log, never used.
"agb_naive_mg_ha": coef_a * (mean_height ** coef_b),
}
Logging both the correct aggregate and the naive one during development is worth the two extra lines: on a stand with mixed heights the gap is often several percent, which is the same order as the effect a project is trying to detect.
Compliance Gating & Audit Trail Generation
A verifier assessing a biomass figure derived from lidar will ask four questions, and a pipeline that cannot answer them from its own records will be asked to answer them from memory.
Which source produced each prediction, and at what date. A biomass map calibrated with 2021 airborne lidar and reported as a 2026 stock is making an unstated persistence assumption; recording the acquisition date per contributing observation makes the assumption explicit and the gap auditable.
How many calibration pairs survived the geometry filter, and how many were rejected. A model fitted on 40 admitted pairs out of 900 candidate shots is a different object from one fitted on 700, and the rejection rate is the single most informative number about whether the site suits the source.
What the slope and quality filters removed. Filters that remove a large, spatially clustered fraction of the shots leave a calibration set that is unrepresentative of the project area, and the resulting model is being extrapolated to terrain it never saw.
Whether any reporting unit received a prediction with no supporting observation within a stated distance. Interpolating across a gap is legitimate; doing so without recording it is not, and the gap map is what turns an interpolated figure into a stated assumption.
Production Integration
In production the arrangement that survives audit is almost always the same shape: airborne lidar over a modest calibration area, a wall-to-wall predictor from SAR or optical imagery, and spaceborne lidar as an independent check on the predictor across the rest of the project. That configuration gives a map with a defensible error estimate, a change signal that refreshes at the satellite cadence, and a validation source that was not used in fitting — which is the property verifiers actually care about.
The temptation to skip the airborne leg is strong because it is the expensive one, and it is sometimes right to skip it: for a jurisdictional estimate where the claim is a population total rather than a map, a well-designed GEDI sample is both cheaper and statistically cleaner than a map of unknown local accuracy. The mistake is skipping it for a project-scale claim and then presenting an interpolated surface as though it were measured. See fusing lidar point clouds with SAR for biomass estimation for the fusion mechanics, and troubleshooting lidar-SAR coregistration drift for what goes wrong when the two layers are not aligned as well as the metadata claims.
Frequently Asked Questions
Can GEDI be used to make a biomass map directly?
Not without an interpolation step that changes what the product is. GEDI samples along orbital tracks with large unsampled gaps between them, so any wall-to-wall surface derived from GEDI alone is the output of a spatial model fitted to those samples, and its accuracy away from the tracks is governed by that model rather than by the instrument. This is legitimate when it is labelled as such and its interpolation error is reported. It becomes a problem when the resulting raster is described as a lidar biomass map, because a reader reasonably assumes lidar measured the pixels.
Why does ICESat-2 need along-track aggregation when GEDI does not?
Because they measure differently. GEDI records the full returned waveform for each footprint, so a single shot already contains a complete vertical profile. ICESat-2 detects individual photons, and a single photon carries almost no information — canopy height only emerges once enough photons are aggregated along track to separate the canopy surface from the ground surface statistically. That aggregation length, typically tens to a hundred metres, becomes the effective resolution, and it is longer for the weak beams than the strong ones.
How much does terrain slope actually cost?
Enough to dominate the error budget in hilly terrain. The ground return spreads vertically by roughly the footprint width times the tangent of the slope, so a 25 m footprint on a 25-degree slope smears ground echoes across about twelve metres — comparable to the canopy height being measured. Height metrics inflate, biomass inflates with them, and the inflation correlates with topography, which makes it visible as a pattern rather than as noise. Filtering above about 20 degrees is common; correcting rather than filtering preserves more shots but requires a terrain model good enough to trust.
Is airborne lidar from five years ago still usable?
For structure that has not changed, yes; the difficulty is knowing which parts have not. Airborne lidar ages in an uneven way: undisturbed stands grow slowly and predictably, while disturbed areas change completely. The workable approach is to pair the old lidar with a satellite change layer covering the interval, treat unchanged areas as still described by the lidar, and exclude changed areas from calibration entirely. Applying a blanket growth increment across the whole survey is the approach that fails review, because it assumes exactly the thing the change layer was there to establish.
Which source is best for detecting degradation rather than deforestation?
Airborne lidar, by a wide margin, and it is one of the few things only lidar can do well. Degradation removes biomass without removing canopy cover, so optical change detection sees little and SAR sees an ambiguous signal. A repeat airborne survey measures the vertical structure directly and shows the loss unambiguously. Spaceborne lidar cannot substitute here because its tracks do not repeat over the same footprints, so there is no before-and-after pair at a given location — only two samples of a population, which detects landscape-scale change but not stand-scale degradation.
Does combining GEDI and ICESat-2 improve a calibration set?
It increases the sample size, and it adds a subtlety that has to be handled: the two instruments produce height metrics that are not interchangeable. A GEDI RH98 and an ICESat-2 canopy height from photon aggregation are different quantities derived by different means, and pooling them without a cross-calibration term treats a systematic offset as random scatter. Fit an instrument term, or fit separate models and compare — but do not concatenate the two into one column and proceed.
What sample size makes a spaceborne lidar calibration defensible?
Fewer than a hundred admitted pairs is difficult to defend for anything other than a coarse stratum estimate, and the number matters less than its spatial and structural spread. A thousand pairs all drawn from mature closed canopy constrain the model only over mature closed canopy, and the biomass classes that matter most for a crediting claim — regrowth, degraded stands, the low end — are usually the least represented. Report the distribution of the calibration set across biomass classes alongside the count, and expect a verifier to look at the sparse end.
Related guides
- Biomass Estimation from Lidar SAR Fusion — the parent topic and the fusion architecture this decision feeds.
- Fusing Lidar Point Clouds with SAR for Biomass Estimation — the mechanics of extending a lidar calibration wall-to-wall.
- Troubleshooting Lidar SAR Coregistration Drift — what the geolocation tolerances in this guide are protecting against.
- Designing Field Plot Sampling for Model Validation — how to build the plot set the calibration pairs come from.