Short answer

gdalinfo is the single fastest way to find out whether a raster is what it claims to be. In one call it reports the driver, dimensions, coordinate reference system, geotransform, corner coordinates, per-band data type and NoData, color interpretation, overviews, internal block size, and metadata. Before any conversion, reprojection, or analysis, run gdalinfo and read it line by line — most raster failures are visible in this output and cost nothing to catch here, versus hours to debug later.

The discipline is simple: never assume a raster's CRS, resolution, or value range. gdalinfo is how you replace assumptions with facts in under a second.

A full read of the output

Run the richer form:

gdalinfo -stats dem.tif

Here is how to interpret the blocks that matter.

Driver and size

Driver: GTiff/GeoTIFF
Size is 10980, 10980

The driver tells you the actual format regardless of file extension — a .tif reported as Driver: GTiff is fine, but a download that reports Driver: HFA/Erdas Imagine despite a .tif name signals a mislabelled file. Size is W, H is in pixels.

Coordinate system

Coordinate System is:
PROJCRS["WGS 84 / UTM zone 31N", ... ID["EPSG",32631]]

The ID["EPSG",32631] at the end is the authoritative answer to "what CRS is this?". If this block reads Coordinate System is: (unknown), the raster has no spatial reference — it will not overlay anything, and you must assign or recover the CRS before use.

Geotransform: Origin and Pixel Size

Origin = (600000.000, 5800020.000)
Pixel Size = (10.000, -10.000)

Origin is the map coordinate of the upper-left corner of the upper-left pixel. Pixel Size is (X, Y); the negative Y is normal because raster rows increase downward (north to south). Read these together:

  • Equal magnitudes (10, -10) means square pixels — expected for most analytical rasters.
  • Mismatched magnitudes (e.g. 30, -25) means anisotropic pixels, which will distort slope and area calculations unless intended.
  • A positive Y pixel size means the raster is stored bottom-up and is often a sign of a broken geotransform.

Corner coordinates

Corner Coordinates:
Upper Left  (  600000.000, 5800020.000) (  3d 0' 0.00"E, 52d20'...N)

GDAL prints both the native CRS coordinates and their lon/lat equivalent. A fast sanity check: do the lon/lat corners land where you expect on Earth? Corners at (0,0) lon/lat for a dataset that should be in the Alps means the georeferencing is wrong.

Bands, data type, NoData, statistics

Band 1 Block=512x512 Type=Float32, ColorInterp=Gray
  Minimum=210.4, Maximum=2104.9, Mean=812.3, StdDev=341.0
  NoData Value=-9999
  • Type=Float32 confirms you are not about to truncate continuous data by casting to Byte.
  • Block=512x512 is the internal tiling. Block=W x 1 means strip storage — bad for streaming and slow for windowed reads.
  • NoData Value=-9999 must be present for DEMs and masked imagery. If it is missing, the statistics line was computed over fill pixels and the Minimum/Mean are meaningless.
  • Statistics only appear with -stats (or if pre-computed). A Minimum of -9999 next to a missing NoData declaration is the classic "NoData is leaking into the data" symptom.

Useful flags

  • -stats — compute and print min/max/mean/stddev (writes a .aux.xml sidecar to cache them).
  • -hist — per-band histogram, useful for spotting bimodal value distributions or saturated tails.
  • -mm — force a min/max scan without full statistics.
  • -checksum — a per-band checksum, ideal for verifying a format-only conversion preserved values.
  • -json — structured output for automation (see below).
  • -nogcp -nomd — suppress ground control points and metadata when you only want the geometry summary.
  • -proj4 / -wkt_format WKT2 — control how the CRS is printed.

Worked example: a CI gate in JSON

Scraping human text is fragile. Use JSON:

gdalinfo -json -stats dem.tif > info.json

Then assert the things that must be true with jq:

# CRS must be UTM 31N
jq -e '.stac.["proj:epsg"] == 32631' info.json

# NoData must be declared on band 1
jq -e '.bands[0].noDataValue != null' info.json

# Pixel size must be 10 m square
jq -e '.geoTransform[1] == 10 and .geoTransform[5] == -10' info.json

geoTransform in JSON is the six-element array [originX, pixelWidth, rowRotation, originY, columnRotation, pixelHeight]. Elements [2] and [4] are rotation terms; for a north-up raster they must both be 0 — a non-zero value means the raster is rotated/skewed and most tools will mishandle it.

Common pitfalls and why they happen

  • Trusting the file extension over the driver line. Vendors and download portals frequently mislabel containers; the Driver: line is ground truth.
  • Reading statistics without checking NoData. GDAL caches stats in a .aux.xml file; if that was computed before NoData was set, the cached Mean is wrong. Delete the sidecar and re-run -stats after assigning NoData.
  • Ignoring block size. A raster that loads fine in QGIS can be unusably slow behind a tile server because it is strip-stored. The Block= field warns you before deployment.
  • Missing overviews. No Overviews: line under a band means zoomed-out views will read full resolution. Add them with gdaladdo for any raster used interactively.
  • Assuming north-up. Non-zero rotation terms in the geotransform are rare but catastrophic if missed, because area and slope tools silently assume an axis-aligned grid.

Validation checklist

Before a raster enters a pipeline, confirm from gdalinfo:

  1. Driver matches the expected format.
  2. CRS has an EPSG identity, not "(unknown)".
  3. Pixel size is the resolution you expect, with the correct sign and square where it should be.
  4. Rotation terms are zero.
  5. NoData is declared and is not inside the valid value range.
  6. Data type matches the analysis (Float for continuous, integer for classified).
  7. Block size is tiled and overviews exist for interactive use.
  8. Corner lon/lat land in the right part of the world.

Bathyl perspective

We make gdalinfo -json the first step of every raster ingest, wired into a CI check that fails the build when CRS, NoData, or pixel size drift from spec. Catching a mislabelled CRS or a leaking NoData value at ingest is cheap; catching it after it has propagated into a published map is not.

Related reading

Sources