Short answer

A GeoTIFF is an ordinary TIFF image with a set of standardised tags that tell software where on Earth the pixels sit and in which coordinate system. The georeferencing lives in two pieces: a transform from pixel coordinates (row, column) to map coordinates, and a coordinate reference system definition (usually an EPSG code). Strip those tags and you have a plain picture; keep them and any GIS can place the raster correctly, sample its values, and reproject it. It is the de facto interchange format for DEMs, satellite imagery, hillshades, and any continuous raster surface.

The format underneath

TIFF (Tagged Image File Format) stores an image as a sequence of tags — width, height, bits per sample, samples per pixel, compression, the pixel data itself. GeoTIFF, originally an OGC/community specification and now an OGC standard, adds geospatial tags without changing the base format. That backward compatibility is the whole point: a GeoTIFF opens in any TIFF viewer, which simply ignores the tags it does not understand.

The geographic information is carried by a small number of tags:

  • ModelPixelScaleTag and ModelTiepointTag — together they give the pixel size and a tie point, defining an affine transform (the common case: axis-aligned, north-up).
  • ModelTransformationTag — a full affine matrix, used when the raster is rotated or sheared.
  • GeoKeyDirectoryTag (plus GeoDoubleParams/GeoAsciiParams) — the GeoKeys, which encode the CRS, typically by an EPSG code such as ProjectedCSTypeGeoKey = 32631 (UTM 31N) or a full WKT/PROJ string for custom systems.

A sidecar .tfw world file can supply the transform when tags are absent, but a properly tagged GeoTIFF is self-describing — the georeferencing travels inside the file, which is why incomplete transfers do not silently strip it the way they can with a shapefile's many components.

Reading what is actually in the file

gdalinfo is the first command to run on any raster:

gdalinfo dem.tif

Look for:

  • Driver: GTiff/GeoTIFF — confirms it is a GeoTIFF.
  • Coordinate System / AUTHORITY["EPSG","32631"] — the CRS. If this is missing, the raster is not georeferenced and will not overlay anything.
  • Pixel Size = (2.000, -2.000) — resolution in CRS units; the negative Y is normal (rows increase downward).
  • Origin and the corner coordinates — where it sits on Earth.
  • Band 1 ... Type=Float32, NoData Value=-9999 — data type and the NoData sentinel.
  • Block=512x512 — present when the file is internally tiled.
  • Overviews: 4096x4096, 2048x2048 ... — pre-computed pyramids, if any.

If gdalinfo reports "Coordinate System is '\" the file lacks a CRS and you must assign one with gdal_edit.py -a_srs EPSG:32631 dem.tif — but only if you know what it truly is. Assigning the wrong EPSG is worse than none.

Tiling, overviews, compression, and NoData

These four properties determine whether a GeoTIFF performs well.

  • Tiling. By default GDAL writes stripped TIFFs (whole rows). For analysis and web use, write tiled GeoTIFFs (-co TILED=YES, typically 512×512 blocks) so software reads a window without decoding the whole image.
  • Overviews. Lower-resolution copies for fast zoomed-out display. Build them internally with gdaladdo -r average dem.tif 2 4 8 16. The resampling method matters: average for continuous data, nearest for categorical.
  • Compression. LZW, DEFLATE, and ZSTD are lossless — mandatory for elevation or measurement rasters where a single altered value is a wrong number. JPEG is lossy and acceptable only for visual imagery, never for analysis bands. Add PREDICTOR=2 (integers) or PREDICTOR=3 (floats) to improve lossless ratios.
  • NoData. Continuous rasters need a NoData value so that voids, sea, or clipped edges are not treated as real elevations. A DEM that uses 0 for both sea level and missing data will produce nonsense slope and hillshade at the gaps.

A typical analysis-ready write:

gdal_translate src.tif dem.tif \
  -co TILED=YES -co BLOCKXSIZE=512 -co BLOCKYSIZE=512 \
  -co COMPRESS=DEFLATE -co PREDICTOR=3 -a_nodata -9999
gdaladdo -r average dem.tif 2 4 8 16

The Cloud Optimized GeoTIFF

A COG is still a fully valid GeoTIFF — anything that reads GeoTIFF reads a COG. The difference is internal byte layout: tiles and overviews are ordered, and a header at the front lists their offsets, so a client can issue an HTTP range request for exactly the bytes of one tile in one zoom level without downloading the file. That is what makes terabyte rasters on S3 viewable in a browser. GDAL has a dedicated driver:

gdal_translate src.tif out.tif -of COG -co COMPRESS=DEFLATE

For large raster archives and web visualisation, COG has largely become the default delivery form of GeoTIFF.

Common pitfalls and why they happen

  • Missing CRS. A GeoTIFF exported from a pipeline that dropped the GeoKeys looks fine alone but will not register. gdalinfo reveals it instantly.
  • No NoData on a DEM. Voids get read as elevation zero, poisoning every derivative. Set NoData explicitly.
  • Stripped, uncompressed output. GIS tools default to strips; the result reads slowly and is large. Always tile and compress analysis rasters.
  • Lossy compression on analysis data. JPEG compression on a DEM alters values invisibly until someone measures from it.
  • Datum assumed from the EPSG code alone. The horizontal CRS does not tell you the vertical datum of elevation values; that must be documented separately.
  • Big-picture sluggishness from no overviews. Without gdaladdo, zoomed-out display rereads full resolution every redraw.

QA and validation

  • Run gdalinfo -stats and confirm the value range is physical (a DEM reading −9999 to 4000 m means NoData is leaking into stats — fix the NoData flag).
  • Overlay against a known reference to confirm registration before trusting the CRS.
  • Validate COG structure with python -m rio_cogeo validate out.tif (rio-cogeo) when delivering for web use.
  • Check the data type matches the content: integer DEMs lose sub-metre detail; use Float32 for fine elevation.

Bathyl perspective

We treat a GeoTIFF as a contract: the CRS, transform, NoData, and vertical datum should be readable from the file and its metadata alone, with no tribal knowledge required. A raster that needs a Slack message to explain its coordinate system is not finished. The format is generous enough to be self-describing — we make sure ours actually are.

Related reading

Sources