Short answer

gdal_translate is GDAL's raster format-conversion and subsetting utility. It changes the container format (GeoTIFF, COG, PNG, VRT, NetCDF), the pixel data type, the band selection, the value range, and the compression of a raster — but it deliberately does not reproject. The moment you need a different coordinate reference system you switch to gdalwarp. Think of gdal_translate as a faithful copy with controlled edits, and gdalwarp as a resampling engine.

A useful mental model: gdal_translate works in the source pixel grid. Every output pixel maps to source pixels by simple selection or rescaling, never by a coordinate transform. That is why it is the right tool for "give me a DEFLATE-compressed COG of this DEM" or "extract bands 4-3-2 as an 8-bit JPEG-compressed image", and the wrong tool for "put this UTM raster into Web Mercator".

The command anatomy

The basic form is:

gdal_translate [options] source.tif destination.tif

The options that actually change pipeline behaviour, roughly in order of how often they matter:

  • -of <FORMAT> — output driver. GTiff, COG, VRT, PNG, JPEG, NetCDF, EHdr. The driver decides which -co options are legal.
  • -ot <TYPE> — output data type: Byte, Int16, UInt16, Float32, Float64. Changing this without -scale will truncate or overflow values.
  • -b <n> — band selection, repeatable. -b 1 -b 2 -b 3 reorders or subsets bands.
  • -scale [src_min src_max [dst_min dst_max]] — linear rescale of values, almost always paired with -ot Byte.
  • -srcwin / -projwin — crop by pixel window or georeferenced window.
  • -a_nodata <value> / -a_srs <crs>assign (not change) NoData or CRS metadata.
  • -co KEY=VALUE — creation options passed to the driver (compression, tiling, predictor).
  • -r <method> — resampling, but only used together with -outsize.

Worked example 1: a Cloud Optimized GeoTIFF from a survey DEM

You have a 1 m LiDAR DEM, dem_raw.tif, as an untiled Float32 GeoTIFF, and you want a COG that streams efficiently from object storage.

gdal_translate dem_raw.tif dem_cog.tif \
  -of COG \
  -co COMPRESS=DEFLATE \
  -co PREDICTOR=3 \
  -co BLOCKSIZE=512 \
  -co RESAMPLING=BILINEAR \
  -co OVERVIEW_RESAMPLING=BILINEAR

Why each flag matters:

  • PREDICTOR=3 is the floating-point predictor. For continuous elevation it typically improves DEFLATE ratios by 20-40% versus no predictor. For categorical Byte data you would use PREDICTOR=2 (horizontal differencing) or none.
  • BLOCKSIZE=512 sets the internal tile size. 256 or 512 are the common choices; larger tiles mean fewer HTTP range requests when a viewer pans, at the cost of reading more data per request.
  • BILINEAR resampling is correct for continuous surfaces. Never use it for thematic/classified rasters — use NEAREST there to avoid inventing class codes that do not exist.

The COG driver writes the overviews and arranges the IFD layout so that an HTTP client can read the header, then fetch only the tiles it needs. You can confirm validity with the validate_cloud_optimized_geotiff.py script shipped with GDAL.

Worked example 2: scaling Float32 to Byte for a web preview

A continuous DEM cannot be drawn as 8-bit imagery without a value mapping. First read the real range:

gdalinfo -stats dem_cog.tif

Suppose it reports Minimum=412.0, Maximum=1987.0. To produce an 8-bit grayscale preview:

gdal_translate dem_cog.tif dem_8bit.tif \
  -ot Byte \
  -scale 412 1987 0 255 \
  -co COMPRESS=DEFLATE

The critical detail is that -scale does not clamp by default: a pixel below 412 maps to a value below 0 and one above 1987 to a value above 255, then the -ot Byte cast truncates it — add -exponent 1 if you want the input genuinely clipped to 412-1987 first. And if you let GDAL pick the range with a bare -scale, it uses the band min/max, which means a single spurious -9999 NoData value still stored as data will blow out the entire stretch. Set NoData first, or pass explicit limits. For perceptually even relief, consider -exponent 0.5 for a gamma-style nonlinear stretch instead of a flat linear one.

Worked example 3: band subsetting and cropping

To pull a true-colour subset (bands 4,3,2 in a Landsat-style stack) over a study window expressed in map coordinates:

gdal_translate scene.tif rgb_subset.tif \
  -b 4 -b 3 -b 2 \
  -projwin 500000 4650000 520000 4630000 \
  -co COMPRESS=DEFLATE -co TILED=YES

-projwin takes upper-left X/Y then lower-right X/Y in the raster's own CRS, so the Y values descend. Use -srcwin xoff yoff xsize ysize when you think in pixels instead. Both cut on existing pixel boundaries — there is no resampling and therefore no positional smearing.

translate vs warp vs the SQL path

NeedTool
Format change, compression, COGgdal_translate
Data type / value rescalegdal_translate -ot -scale
Band selection, simple cropgdal_translate -b -srcwin
Reproject / resample to new CRSgdalwarp
Mosaic many tilesgdalwarp or gdalbuildvrt
Store raster in a databaseraster2pgsql into PostGIS

A common pipeline is gdalbuildvrt to stitch a virtual mosaic, then a single gdal_translate -of COG to materialise it — the VRT step costs almost no disk, and the translate step does the one expensive read/write.

Common pitfalls and why they happen

  • Changing -ot without -scale. Casting Float32 reflectance (0.0-1.0) to Byte gives an image of all zeros, because the cast truncates toward zero. The fix is -scale 0 1 0 255.
  • Assuming -a_srs reprojects. The -a_ prefix means "assign/override metadata". It relabels the CRS tag without touching pixels. If the pixels are genuinely in another CRS, this corrupts georeferencing silently. Use gdalwarp -t_srs instead.
  • Forgetting NoData on the way out. If the source NoData is not declared, -scale includes it in the stretch and overviews average across it, producing dark halos at edges. Add -a_nodata or carry it through.
  • JPEG compression on data you will analyse. -co COMPRESS=JPEG is lossy. Fine for an RGB visual product, never for a DEM or any band you will run analysis on. Use DEFLATE, LZW, or ZSTD for analytical rasters.
  • Untiled output feeding a tile server. Without -co TILED=YES (implicit for COG) the file is stored in strips, so every tile request reads whole scanlines. Always tile rasters destined for streaming.

Validation

After conversion, do not trust file existence as success:

gdalinfo -stats -checksum dem_cog.tif

Confirm the band count, the data type, the CRS string, the NoData value, the min/max, and that overviews are present. Compare the checksum or statistics against the source where the operation should be value-preserving (a format-only change should leave statistics unchanged). For COGs, run the validator. When the output feeds an automated pipeline, capture the exact command in version control so the artefact is reproducible from the source.

Bathyl perspective

We treat gdal_translate as the boundary control on a raster pipeline: it is where data type, compression, and NoData get pinned down before anything downstream consumes the file. Getting those three right at conversion time prevents the slow, hard-to-trace failures — washed-out stretches, lossy analysis inputs, broken streaming — that surface much later in a client deliverable.

Related reading

Sources