Choosing Block Size and Overview Levels for COGs
Pick the block size from the dominant read, and add overviews until the smallest fits in a block or two:
rio cogeo create input.tif output.tif \
--blocksize 512 \
--overview-level 5 \
--overview-resampling average \
--cog-profile zstd
Those two numbers decide how many HTTP requests every read costs for the life of the file. This page belongs to understanding Cloud-Optimized GeoTIFF structure in Core Raster Fundamentals & STAC Mapping.
Block Size Is a Trade Between Requests and Waste
The opposite case — a large analytical read of, say, 4096 pixels square — flips the argument: with 256-pixel blocks it touches 256 blocks, and even with HTTP range coalescing that is many more requests than the 16 it needs with 1024-pixel blocks. There is no universally correct size, only a correct size for the reads the file will actually receive.
Environment & Setup
| Package | Version pin | Used for |
|---|---|---|
rio-cogeo |
>=5.0 |
Creating and validating COGs with chosen blocks and overviews |
rasterio |
>=1.3.0 |
Inspecting block shapes and overview factors |
numpy |
>=1.23 |
The request-count model |
pip install "rio-cogeo>=5.0" "rasterio>=1.3.0" "numpy>=1.23"
Complete Working Example
import math
import rasterio
from rio_cogeo.cogeo import cog_translate
from rio_cogeo.profiles import cog_profiles
def overview_levels(width: int, height: int, block: int) -> int:
"""Levels needed until the smallest overview fits within two blocks."""
levels, w, h = 0, width, height
while max(w, h) > 2 * block:
w, h = math.ceil(w / 2), math.ceil(h / 2)
levels += 1
return levels
def requests_for(read_px: int, block: int) -> int:
"""Blocks touched by an aligned square read of read_px pixels."""
return math.ceil(read_px / block) ** 2
def make_cog(src_path: str, dst_path: str, *, use: str = "analysis",
categorical: bool = False) -> dict:
block = {"tiles": 256, "analysis": 512, "bulk": 1024}[use]
with rasterio.open(src_path) as src:
levels = overview_levels(src.width, src.height, block)
profile = cog_profiles.get("zstd") | {"blockxsize": block, "blockysize": block}
cog_translate(
src_path, dst_path, profile,
overview_level=levels,
overview_resampling="mode" if categorical else "average",
in_memory=False, quiet=True,
)
return {"block": block, "overview_levels": levels,
"tile_read_requests": requests_for(256, block),
"4k_window_requests": requests_for(4096, block)}
if __name__ == "__main__":
print(make_cog("S2A_36NYF_B08.tif", "S2A_36NYF_B08_cog.tif", use="analysis"))
For a 10,980-pixel band with 512-pixel blocks, overview_levels returns five, giving a smallest level of 344 pixels — a single block. With 256-pixel blocks it returns six. Letting the size of the image and the block determine the depth, rather than hard-coding it, keeps every file in an archive equally efficient at its smallest zoom.
Overview Depth and What It Buys
The storage cost of a full pyramid is about a third of the base image, and the benefit is that every zoomed-out read — a thumbnail, a tile at low zoom, a whole-scene statistic computed from an overview — becomes one or two requests instead of hundreds. That trade is favourable in almost every case; the exception is a file that will only ever be read at full resolution in its entirety, where overviews are pure overhead.
Overview resampling is the other half of the decision and is covered in detail in adding internal overviews with the right resampling: average for continuous data, mode for categorical, never bilinear on classes.
Matching the Archive to Its Consumers
An archive is usually read by more than one kind of consumer, and the block size is best chosen from the one that reads most often. A product that will be browsed on a web map far more than it is analysed benefits from 256-pixel blocks that align with map tiles, as discussed in generating Web Mercator tile pyramids. A product feeding model inference in 512- or 1024-pixel windows benefits from blocks of the same size, so each window is exactly one or four blocks.
When the consumers genuinely conflict, 512 is the compromise that serves both acceptably: a map tile reads one block four times its size, and a 2048-pixel analysis window reads sixteen blocks. Neither is optimal, both are fine, and the archive stays uniform. Mixing block sizes within one archive is worth avoiding, since tools and caches tuned for one size behave inconsistently on another.
Verification
import rasterio
from rio_cogeo.cogeo import cog_validate
with rasterio.open("S2A_36NYF_B08_cog.tif") as src:
assert src.block_shapes[0] == (512, 512), src.block_shapes[0]
factors = src.overviews(1)
assert factors == [2 ** i for i in range(1, len(factors) + 1)], factors
smallest = max(src.width, src.height) / factors[-1]
assert smallest <= 1024, f"smallest overview {smallest:.0f} px — add a level"
valid, errors, warnings = cog_validate("S2A_36NYF_B08_cog.tif")
assert valid, errors
Validation confirms the layout is cloud-optimized; it does not confirm the block size and overview depth match your intent. Asserting both explicitly is what catches a conversion step that silently fell back to defaults. The CI integration of these checks is covered in validating COG structure in CI.
Common Errors
Tile serving is slow despite a valid COG
Blocks are 1024 pixels, so each 256-pixel tile over-reads sixteen-fold. Rewrite with 256 or 512.
Low-zoom reads touch hundreds of blocks
Too few overview levels. Add levels until the smallest fits in a block or two.
The file grew far more than a third
Overviews were stored uncompressed or with a weaker compressor than the base. Use the same profile for all levels.
Block shape reports as strips, not tiles
The file was written untiled and never converted. Create it with rio cogeo create rather than a plain copy.
Frequently Asked Questions
Q: Is 512 always the right block size? It is the right default for mixed analytical use. For a file served mostly as web tiles, 256 aligns each block with a tile and halves the over-read; for files read almost entirely in large windows or whole, 1024 reduces request count.
Q: How many overview levels should a COG have? Enough that the smallest level fits in one or two internal tiles — for a 10,980 pixel Sentinel-2 band with 512 pixel blocks, five levels. Fewer leaves low-zoom reads touching many tiles; more adds nothing useful.
Q: How much do overviews add to file size? About a third for a power-of-two pyramid, because each level is a quarter of the one above and the series one quarter plus one sixteenth and so on sums to one third. Compression changes the absolute size but not that proportion.
Q: Can block size be changed without rewriting the file? No. Block layout is fixed at write time; changing it means reading and rewriting every byte. Choosing it correctly at creation is far cheaper than a later migration across an archive.
Related
- Understanding Cloud-Optimized GeoTIFF Structure — the parent topic.
- Converting a GeoTIFF to a COG with rio-cogeo — the conversion these settings feed.
- Diagnosing Slow COG Reads with GDAL VSI Logging — measuring whether the choice worked.
- Benchmarking COG Read Throughput from Object Storage — the numbers behind the trade-off.