Choosing Overview Levels and Resampling for Archived Imagery

Overviews are the reason a COG can answer a zoomed-out question cheaply, and the two parameters that govern them — how many levels, and how pixels are combined at each — are set once at conversion and cannot be changed without rewriting the object. This walkthrough is for the archivist or engineer specifying those parameters for a raster collection, and it covers the case that catches people out: categorical rasters, where the default resampling method invents land-cover classes that do not exist in the source.

How Far Down to Build

The rule that works for archives is to build until the whole scene fits inside roughly one internal tile. Stopping early leaves a gap where a zoomed-out request must decode full-resolution data; going further adds levels nobody reads.

Overview pyramid depth for a large orthophoto Seven pyramid levels for a 24,000 by 18,000 pixel scene with 512-pixel tiles, showing tile counts falling from 1,632 at full resolution to a single tile at level six, and the cumulative size overhead. 24,000 × 18,000 px · 512 px tiles · cumulative overhead in parentheses level 0 1,632 tiles level 1 408 tiles (+25%) level 2 108 tiles (+31%) level 3 28 tiles (+32.6%) level 4 8 tiles (+33.0%) levels 5–6 2 then 1 tile — stop here (+33.3%) Nearly all the overhead is level 1. Levels 2 onward are almost free, which is why stopping early saves nothing and costs reads.

Step-by-Step Procedure

Step 1 — Derive the level count from the scene dimensions

# Levels needed so the smallest overview fits within one 512-px tile
python3 - <<'PY'
import math
w, h, tile = 24000, 18000, 512
levels = math.ceil(math.log2(max(w, h) / tile))
print(f"{w}x{h} with {tile}px tiles -> {levels} overview levels")
PY
# 24000x18000 with 512px tiles -> 6 overview levels

Step 2 — Choose resampling from what the pixels mean

This is the decision that produces silently wrong data when it is made by default. Averaging a land-cover raster whose values are class codes produces new codes: average a pixel of class 3 (grassland) with one of class 7 (water) and the overview shows class 5 (industrial).

# Continuous data — elevation, reflectance, temperature
gdal_translate dem_n5432.tif dem_cog.tif -of COG \
  -co OVERVIEW_RESAMPLING=AVERAGE -co PREDICTOR=3 -co COMPRESS=DEFLATE

# Categorical data — land cover, classification masks, zoning
gdal_translate landcover_2024.tif landcover_cog.tif -of COG \
  -co OVERVIEW_RESAMPLING=NEAREST -co PREDICTOR=2 -co COMPRESS=DEFLATE

# Categorical where the dominant class should survive the zoom-out
gdal_translate landcover_2024.tif landcover_mode_cog.tif -of COG \
  -co OVERVIEW_RESAMPLING=MODE -co PREDICTOR=2 -co COMPRESS=DEFLATE
Resampling methods applied to a categorical raster Three methods applied to a source window containing land-cover classes 3, 7 and 11: averaging produces invented intermediate classes, nearest keeps one sampled class, and mode keeps the dominant class. Source window: classes 3 (grass), 7 (water), 11 (built) — codes, not quantities source 4×4 3 3 7 7 3 3 7 11 3 7 11 11 3 3 7 11 AVERAGE 3 → 5 → 9 classes 5 and 9 exist in the legend — and are not what is on the ground NEAREST takes one pixel's value never invents a class minority classes may vanish MODE most frequent class wins never invents a class best for categorical The failure is silent: an averaged land-cover overview renders beautifully and is wrong at every zoom level except full resolution. Nodata handling compounds it — averaging a nodata sentinel such as 255 into real classes produces values far outside the legend.

Step 3 — Verify what the overviews actually contain

Do not trust the parameter; read the pixels back. For a categorical raster, the set of distinct values in the overview must be a subset of the source’s legend.

from osgeo import gdal
import numpy as np

ds = gdal.Open("landcover_cog.tif")
band = ds.GetRasterBand(1)
legend = set(np.unique(band.ReadAsArray()).tolist())

for i in range(band.GetOverviewCount()):
    ov = set(np.unique(band.GetOverview(i).ReadAsArray()).tolist())
    invented = ov - legend
    print(f"overview {i}: {len(ov)} classes, invented={sorted(invented) or 'none'}")

Expected output reports invented=none at every level. Any invented value means the resampling method was wrong for this raster, and the file must be rewritten — the overviews cannot be replaced in place without disturbing the COG layout.

Validation & Verification

gdalinfo -json landcover_cog.tif \
  | jq '{overviews: (.bands[0].overviews | map(.size)), block: .bands[0].block}'

Expected output lists overview sizes halving at each step down to a level smaller than the tile size, and a block size matching the conversion’s setting. Sizes that stop halving early indicate the pyramid was truncated; a single overview far smaller than the rest usually means a pre-existing pyramid was inherited rather than regenerated.

Troubleshooting

Symptom Root cause Fix
Overview shows classes absent from the legend AVERAGE used on categorical data Rewrite with MODE; verify with the distinct-value check
Halo of odd values along scene edges Nodata averaged into real pixels Declare nodata before conversion so resampling excludes it
Elevation overviews look terraced NEAREST used on continuous data Use AVERAGE; nearest discards the smoothing zoom-out needs
Only two overview levels present Level count derived from a default, not the dimensions Compute levels from the scene size and pass them explicitly
Smallest overview still 8,000 px wide Pyramid stopped short of the tile size Add levels; the marginal size cost past level 2 is under 1%

Operational Execution Checklist

What the Overview Choice Costs a Mosaic

Overviews are read far more often than full-resolution pixels, because most viewing happens zoomed out. That makes the resampling choice a visible property of the archive rather than an internal detail — it is what a user sees on first contact with almost every scene.

How resampling choice shows up in a mosaic view Three mosaic outcomes: average resampling on elevation blends smoothly, nearest resampling terraces and shows seams, and mode resampling keeps categorical boundaries crisp. data + methodwhat the zoomed-out view showsverdict elevation + AVERAGE continuous data, smoothed smooth gradients, seams invisible between scenes correct elevation + NEAREST a common default terracing, and visible seams where neighbours sampled differently wrong, and obvious land cover + MODE categorical, dominant class crisp class boundaries, no invented intermediate classes correct The middle row is the one that generates support tickets: the data is fine and the archive looks broken.

Where a collection is mosaicked routinely, review a zoomed-out render of two adjacent scenes as part of accepting the conversion. It takes a minute, it catches the resampling mistake immediately, and it catches it before the whole collection has been written with the wrong setting.

Frequently Asked Questions

Do overviews need to be rebuilt if the source data is corrected?

Yes — overviews are derived data and become stale the moment the full-resolution pixels change. Because a COG’s layout puts them before the image data, updating them means rewriting the file, which is one more reason corrections should be applied before the object is tiered and locked rather than after.

Is there a case for storing overviews as a separate file?

External overviews in a .ovr sidecar were the norm before COG and still work, but they reintroduce the multi-file problem that lifecycle rules handle badly — the sidecar and the raster can be tiered separately and land in different classes. For archival use, internal overviews inside a single self-contained object are the better trade.

How much do overviews cost in a compressed archive?

Roughly a third of the full-resolution size, and slightly less in practice because overviews compress better than the source: they are smoother, so a predictor plus DEFLATE finds more redundancy. Against the read savings, it is the least contentious storage overhead in an imagery archive.

Can different bands of one raster need different resampling?

Occasionally, and it is worth checking on multi-band products that mix continuous and categorical layers — a stack carrying reflectance bands plus a quality-assurance mask, for instance. Where the tooling cannot apply per-band methods, splitting the categorical band into its own file is cleaner than compromising on one method for both.

Do overviews affect how the file is validated?

They are part of what a structural check looks at: a COG validator reports both their presence and whether they are correctly ordered relative to the full-resolution data. A scene with overviews that were appended after the fact often fails the layout check even though the pyramid itself is fine, which is one of the commonest advisory findings in inherited collections.

Should the number of levels be uniform across a collection?

The depth should follow each scene’s dimensions, which means the level count varies with scene size — a uniform count would leave large scenes short and small ones with pointless extra levels. What should be uniform is the rule: build until the smallest overview fits within one internal tile.

Should overviews be validated separately from the full-resolution data?

Yes, for categorical rasters, where the distinct-value check on each level is the only thing that catches a wrong resampling method. For continuous data the presence and count of levels is sufficient, since averaging cannot produce values outside the source range. The asymmetry is worth encoding in the gate rather than applying the stricter check everywhere.

What happens if a scene is too small to need overviews?

Nothing needs to be built, and the validator should not require them. A scene whose full resolution already fits within one or two internal tiles is its own overview, and forcing levels onto it adds metadata for no benefit. Deriving the level count from the dimensions handles this automatically, which is another reason not to fix it as a constant.

Can overviews be added to an existing archived COG?

Not without rewriting the file, because a COG’s layout places them before the image data. External overviews in a sidecar are technically possible and reintroduce the multi-file problem lifecycle rules handle badly. For an archived scene the practical answer is to record the gap and address it at the next rewrite.

Is bilinear resampling ever the right choice?

For continuous data where averaging softens edges too much — a hillshade, for instance. It is a reasonable middle ground and is not appropriate for categorical data for the same reason averaging is not.

Up one level: Cloud-Optimized GeoTIFF Conversion Pipelines.