GDAL-RASTER-ZONAL-STATS(1)

πŸ“¦
version 3.13.3 Β· 4 versions
, in alpine 3.24, arch rolling, ubuntu stonking

πŸ’– GDAL-RASTER-ZONAL-STATS(1) πŸ’–

okay bestie, welcome to the GDAL manual, and specifically to the gdal-raster-zonal-stats page. this is a GDAL joint. buckle up, we're computing zonal stats and we're gonna slay while we do it. ✨


🌸 NAME 🌸

gdal-raster-zonal-stats β€” Compute raster zonal statistics. πŸ’…

Added in version 3.12.


✨ SYNOPSIS ✨

Usage: gdal raster zonal-stats [OPTIONS] <INPUT> <OUTPUT>

Calculate raster zonal statistics

Positional arguments:
  -i, --input <INPUT>                                  Input raster datasets [required] [not available in pipelines]
  -o, --output <OUTPUT>                                Output vector dataset [required] [not available in pipelines]

Common Options:
  -h, --help                                           Display help message and exit
  --json-usage                                         Display usage as JSON document and exit
  --config <KEY>=<VALUE>                               Configuration option [may be repeated]
  -q, --quiet                                          Quiet mode (no progress bar or warning message) [not available in pipelines]

Options:
  -f, --of, --format, --output-format <OUTPUT-FORMAT>  Output format ("GDALG" allowed) [not available in pipelines]
  --co, --creation-option <KEY>=<VALUE>                Creation option [may be repeated] [not available in pipelines]
  --lco, --layer-creation-option <KEY>=<VALUE>         Layer creation option [may be repeated] [not available in pipelines]
  --overwrite                                          Whether overwriting existing output dataset is allowed [not available in pipelines]
  --update                                             Whether to open existing dataset in update mode [not available in pipelines]
  --overwrite-layer                                    Whether overwriting existing output layer is allowed [not available in pipelines]
  --append                                             Whether appending to existing layer is allowed [not available in pipelines]
                                                       Mutually exclusive with --upsert
  --output-layer <OUTPUT-LAYER>                        Output layer name [not available in pipelines]
  --skip-errors                                        Skip errors when writing features [not available in pipelines]
  -b, --band <BAND>                                    Input band(s) (1-based index) [may be repeated]
  --zones <ZONES>                                      Dataset containing zone definitions [required]
  --zones-band <ZONES-BAND>                            Band from which zones should be read
                                                       Mutually exclusive with --zones-layer
  --zones-layer <ZONES-LAYER>                          Layer from which zones should be read
                                                       Mutually exclusive with --zones-band
  --weights <WEIGHTS>                                  Weighting raster dataset
  --weights-band <WEIGHTS-BAND>                        Band from which weights should be read (default: 1)
  --pixels <PIXELS>                                    Method to determine which pixels are included in stat calculation.. PIXELS=default|fractional|all-touched
  --stat <STAT>                                        Statistic(s) to compute for each zone. STAT=center_x|center_y|count|coverage|frac|max|max_center_x|max_center_y|mean|median|min|minority|min_center_x|min_center_y|mode|stdev|sum|unique|values|variance|variety|weighted_mean|weighted_stdev|weighted_sum|weighted_variance|weights [may be repeated] [required]
  --include-field <INCLUDE-FIELD>                      Fields from polygon zones to include in output [may be repeated]
  --include-geom                                       Include polygon zone geometry in the output
  --strategy <STRATEGY>                                For polygon zones, whether to iterate over input features or raster chunks. STRATEGY=feature|raster (default: feature)
  --chunk-size <CHUNK-SIZE>                            Maximum size of raster chunks read into memory (default: 5%)

Advanced Options:
  --if, --input-format <INPUT-FORMAT>                  Input formats [may be repeated] [not available in pipelines]
  --oo, --open-option <KEY>=<VALUE>                    Open options [may be repeated] [not available in pipelines]
  --output-oo, --output-open-option <KEY>=<VALUE>      Output open options [may be repeated] [not available in pipelines]
  --upsert                                             Upsert features (implies 'append') [not available in pipelines]
                                                       Mutually exclusive with --append

πŸ¦‹ DESCRIPTION πŸ¦‹

gdal raster zonal-stats computes raster zonal statistics β€” which is just a fancy lil way of saying: it summarises the pixel values that live inside zones, where those zones are defined either by polygon features or by a categorical raster. so iconic of it. πŸ‘‘

πŸ–ΌοΈ [image] Mean elevation calculated for each polygon from an elevation raster.

a weighting raster can also be supplied on top of the source raster, bestie, which lets you compute stuff like population-weighted mean weather conditions. obsessed.

all inputs should be handed over in the same spatial reference system. and if the input rasters have different resolutions, they get resampled to a consistent resolution using average resampling. but heads up β€” no reprojection is performed. none. zero. don't come for me. πŸ™ˆ

🌈 Pixel inclusion and weighting 🌈

when the zones are defined using polygons, you've got three methods for deciding which pixels make it into the calculation:

  • default vibe: the geometries get rasterized using default parameters, and the pixels included in that rasterization are used in the calculation with equal weighting.
  • --pixels all-touched: all pixels touched by the polygon are used in the calculation, again with equal weighting.
  • --pixels fractional: the fraction of each pixel that is covered by the polygon is computed, and pixels get weighted according to that fraction.
⚠️ ngl this one's a demand: --pixels fractional requires that GDAL be built against version 3.14 or later of the GEOS library. no GEOS 3.14+, no fractional pixels. periodt.

(part 1 done, bestie β€” more coming, don't go anywhere πŸ’œ)

πŸŽ€ Supported stats πŸŽ€

okay bestie so here's the ✨full menu✨ of stats you can ask for. each one is a lil computation that runs over the cells touching your polygon, and you pick them by name. let me run through every single one:

  • center_x β€” an array holding the cell center x-coordinate for every cell that intersects the polygon. heads up though: each cell center may or may not actually be inside the polygon πŸ’…
  • center_y β€” same vibe, but the cell center y-coordinate for each cell that intersects the polygon. and yeah, each cell center may or may not be inside the polygon πŸ¦‹
  • count β€” the sum of all the cell coverage fractions. that's it, that's the number πŸ“
  • coverage β€” an array with the coverage fraction of every cell that intersects the polygon 🌸
  • frac β€” the fraction of covered cells that are occupied by each distinct raster value, exactly as provided by unique. iykyk ✨
  • max β€” the maximum value of cells that intersect the polygon. and no, it does not take coverage fractions or weighting raster values into account 🫢
  • max_center_x β€” the cell center x-coordinate of the cell that holds the maximum value intersected by the polygon. the center of that cell may or may not be inside the polygon, we don't gatekeep πŸ’…
  • max_center_y β€” the cell center y-coordinate of the cell holding the maximum value intersected by the polygon. once again, that center may or may not be inside the polygon πŸ™ˆ
  • mean β€” the mean value of cells that intersect the polygon, weighted by the percent of each cell that is covered. coverage-aware, she's thorough ⭐
  • min β€” the minimum value of cells that intersect the polygon, and again no coverage fractions and no weighting raster values in the mix πŸ’€
  • min_center_x β€” cell center x-coordinate for the cell containing the minimum value intersected by the polygon. that center may or may not be inside the polygon, either way she's valid 🧚
  • min_center_y β€” cell center y-coordinate for the cell containing the minimum value intersected by the polygon. yep, the center may or may not be inside the polygon 🌈
  • minority β€” the raster value occupying the least number of cells, taking cell coverage fractions into account but not weighting raster values πŸ’œ
  • mode β€” the raster value occupying the greatest number of cells, taking cell coverage fractions into account but not weighting raster values. main character value, honestly πŸ‘‘
  • stdev β€” the population standard deviation of cell values that intersect the polygon, taking the coverage fraction into account πŸ“
  • sum β€” the sum of values of raster cells that intersect the polygon, with each raster value weighted by its coverage fraction. slay πŸ”₯
  • unique β€” an array of the unique raster values for cells that intersect the polygon ✨
  • values β€” an array of raster values for each cell that intersects the polygon πŸŽ€
  • variance β€” the population variance of cell values that intersect the polygon, taking the coverage fraction into account 🌸
  • variety β€” the number of distinct raster values in cells wholly or partially covered by the polygon πŸ¦‹
  • weighted_frac β€” the fraction of covered cells occupied by each distinct raster value, weighted by the value of a second weighting raster. the order corresponds to the values returned by unique πŸ’…
  • weighted_mean β€” the mean value of cells that…

…and the table's still going, bestie 😭 we literally got page-broke mid-row. So here's how that chopped-off row lands, plus the rest of the lineup:

  • (the stat from just above, whose name the page break absolutely ate πŸ’€) β€” …intersect the polygon, weighted by the product over the coverage fraction and the weighting raster.
  • weighted_stdev β€” Weighted version of stdev. πŸ’œ
  • weighted_variance β€” Weighted version of variance. ✨
  • weights β€” Array of weight values for each cell that intersects the polygon. πŸŽ€

And yes β€” this algorithm can be part of a gdal pipeline. πŸ”₯


πŸŽ€ PROGRAM-SPECIFIC OPTIONS πŸŽ€

okay so these are the ones that are literally just for this tool, the main-character flags πŸ‘‘

  • --band, -b <BAND> β€” the input band(s) you wanna process. that's it, that's the vibe.
  • --chunk-size <MEMORY> β€” sets the max size of the raster chunks it reads. you can give it as an amount of memory (500 MB, 2 GB, etc.) or as a percentage of your available RAM (like 10%). pick your fighter πŸ¦‹
  • --include-field <INCLUDE-FIELD> β€” names one or more fields from the zones that get copied over to the output. only works when you're using vector zones, jsyk. and since GDAL 3.13 the special values ALL and NONE are valid too πŸ’…
  • --include-geom β€” drops the zone geometry into the output feature. vector zones only, bestie.

Added in version 3.13. ⭐

  • --pixels <PIXELS> β€” how it decides which pixels actually count for the math: default, all-touched, or fractional.
  • --stat <STAT> β€” name one or more of the Supported stats and it'll compute them per zone. you can stack these, go off queen πŸ‘‘
  • --strategy <STRATEGY> β€” the processing strategy when vector zones are in play: raster or feature. default is --strategy feature, which means GDAL walks over the features in your zone dataset, reads the matching pixels from the raster, and writes stats for that feature. the upside: it never has to load the whole feature dataset into memory at once. the downside: the same pixels can get read more than once if your polygons are big or just not spatially ordered (rude of them, honestly). if you go --strategy raster instead, GDAL loops over chunks of the raster, finds the matching polygon zones, and updates their stats. (chunk size is yours to control with --chunk-size.) that guarantees raster pixels are read exactly once β€” but the same features might get processed multiple times. pick your chaos 🌈
  • --weights <WEIGHTS> β€” optional path to a dataset you want to use for weighting.
  • --weights-band <WEIGHTS-BAND> β€” picks which raster band the weights get read from.
  • --zones <ZONES> β€” path to the dataset that defines your zones. if that dataset has more than 1 raster band or feature layer, you must also pass --zones-band or --zones-layer.
  • --zones-band <ZONES-BAND> β€” which raster band the zones get read from.
  • --zones-layer <ZONES-LAYER> β€” which feature layer the zones get read from.

✨ STANDARD OPTIONS ✨

the shared besties. every gdal tool gets these 🫢

  • --append β€” says whether appending features to existing layer(s) is allowed. it also creates the output dataset for you if it doesn't exist yet. convenient fr.
  • --co, --creation-option <NAME>=<VALUE> β€” lots of formats have optional dataset creation options that let you control little particulars about the file being created. like, the GeoPackage driver has creation options for controlling the version. 🧚

May be repeated.

Which creation options exist depends entirely on the format driver, and some simple formats have none at all. you can list what a format supports with the --formats command line option, but the format's own documentation is the definitive source on driver creation options. see the Vector drivers format-specific docs for the legal creation options per format.

heads up: dataset creation options are not the same thing as layer creation options. do not confuse them, bestie πŸ’€
  • --if, --input-format <format> β€” the format/driver name to try when opening the input file(s). you usually don't need it, but it's there to skip automatic driver detection when that detection picks wrong. repeat it as many times as you want to list several candidate drivers. note that it doesn't force those drivers to open the dataset β€” some drivers have file extension requirements and will simply say no. 🚫

May be repeated.

  • --lco, --layer-creation-option <NAME>=<VALUE> β€” lots of formats have optional layer creation options to control particulars about the layer being created. like, the GeoPackage driver has layer creation options for the feature identifier or geometry column name, setting the identifier or description, that kind of thing. πŸ’…

May be repeated.

Same deal as creation options: what's available varies by format driver, simple formats might have none, --formats lists what's supported but the format documentation is the definitive source, and you should check the Vector drivers format-specific docs for the legal ones.

and again: layer creation options are not dataset creation options. two different girls, two different energies. πŸ™ˆ
  • --oo, --open-option <NAME>=<VALUE> β€” dataset open option (format specific).

May be repeated.

  • -f, --of, --format, --output-format <OUTPUT-FORMAT> β€” which output vector format to use. you can see the allowed values with:
gdal --formats | grep vector | grep rw | sort
  • --output-layer <NAME> β€” the name of the layer the features get written to. if you don't specify it, the output layer names just match the input layer names. iconic and lazy, love that for us ✨
  • --output-open-option, --output-oo <NAME>=<VALUE> β€” dataset open option for the output dataset (format specific).

Added in version 3.12. ⭐

May be repeated.

  • --overwrite β€” lets the program overwrite an existing target file or dataset. without it, gdal just errors out by default if the target already exists. no consent, no overwrite πŸ’€
  • --overwrite-layer β€” whether overwriting the existing output vector layer is allowed.
  • --skip-errors β€” whether failures to write feature(s) should just be ignored.

Added in version 3.12. ⭐

⚠️ careful bestie: this option sets the transaction unit size to one feature at a time, which can cause severe slowdown when you're inserting into databases. it's giving "technically works, painfully slow" 😭
  • --update β€” whether to open an existing output dataset in update mode.
  • --upsert β€” a variant of --append where the OGRLayer::UpsertFeature() operation is used to insert or update features, instead of appending with OGRLayer::CreateFeature().

Added in version 3.12. ⭐

right now it's only implemented in a few drivers: GPKG -- GeoPackage vector, Elasticsearch: Geographically Encoded Objects for Elasticsearch, and MongoDBv3 (drivers that implement upsert expose the GDAL_DCAP_UPSERT capability).

the upsert operation uses the FID of the input feature, when it's set (and the FID column name isn't the empty string), as the key for updating existing features. so it is crucial that the FIDs in your source and target layers actually match. seriously. πŸ‘‘

for the GPKG driver you can also upsert features whose FID is unset or non-significant (the --unset-fid option of gdal vector edit will make it ignore the FID from the source feature), as long as there's a UNIQUE column that isn't the integer primary key.


🌈 RETURN STATUS CODE 🌈

it returns status code 0 when everything slays, and non-zero when something goes wrong. (non-blocking errors that come out as warnings still count as a successful execution, so don't panic over those 🫢)


πŸ’– EXAMPLES πŸ’–

Example 1: Summarize mean elevation within 200m of points of interest

using a nested pipeline for the zone dataset πŸ¦‹

gdal pipeline read dem.tif ! \
    zonal-stats \
      --zones [ read points.geojson ! buffer 200 ] \
      --stat mean ! \
    write \
      --output-format CSV \
      --output /vsistdout/

orrr, use the zone vector dataset as the piped dataset with the _PIPE_ placeholder dataset name πŸ‘€

gdal pipeline read points.geojson ! \
    buffer 200 ! \
    zonal-stats \
      --input dem.tif
      --zones _PIPE_ \
      --stat mean ! \
    write \
      --output-format CSV \
      --output /vsistdout/

Example 2: Create a layer with the highest points in each watershed

gdal pipeline read dem.tif !
    zonal-stats \
      --zones watersheds.shp \
      --stat max_center_x \
      --stat max_center_y ! \
    make-point \
      --x max_center_x \
      --y max_center_y \
      --output-crs EPSG:4326 ! \
    write out.geojson

πŸ‘‘ AUTHOR πŸ‘‘

Dan Baston <dbaston@gmail.com>

1998-2026


that's the whole page bestie, you made it to the end πŸ₯Ήβœ¨ this was GDAL-RASTER-ZONAL-STATS(1), dated Aug 18, 2026 β€” go forth and zone your stats, no cap πŸ’…πŸ’–

πŸ“¦ where this page comes from