GeoTIFF tiling and georeferencing
A GeoTIFF is an ordinary image file with a map reference attached, and tiling cuts a huge scene into small pieces a model can actually load, so both are about connecting pixels to real places without running out of memory.
- 10 min read
- 3 reading levels
- Published
Read these first
On this page 6
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
A GeoTIFF is a satellite image file that also stores exactly where on Earth each pixel sits.
Think about the game Battleship. You call out a grid square, "D-5". Both players instantly agree on exactly which square of the board you mean, because the grid has a shared, fixed reference. Nobody needs to point at the board.
Georeferencing does the same job for a satellite picture. It attaches a shared coordinate system to the image. Pixel "row 3, column 5" always means one exact spot on Earth, in latitude and longitude, no matter who opens the file.
Why it exists
A plain photo has no idea where it was taken. Pixel (0, 0) of a phone photo means nothing outside that one photo.
A satellite image is useless for real work unless a computer can answer "which real place is this pixel". Georeferencing solves that. Alongside the pixel grid, it stores a starting coordinate and how much real distance one pixel step covers. GeoTIFF is the most common file format this information gets stored in — a standard TIFF image with map information built in.
A second, separate problem is size. One full satellite scene can cover thousands of square kilometres. That is far too large to load into memory at once, let alone feed into a model expecting a small, fixed-size image. Tiling solves that by cutting the huge scene into small, equal-sized squares, each handled on its own.
How it works
Full scene Cut into tiles Each tile also knows
(too big to load (256x256, its own real-world
all at once) e.g.) corner coordinateGeoreferencing needs two pieces of information: a starting coordinate for one corner of the image, and how much real-world distance each pixel step covers. Together, they let anyone compute the real coordinate of any pixel in the grid. That is also how a mapping tool draws the image in exactly the right place on a map.
Where you have already seen it
- Any "satellite view" toggle on a maps app, which lines up satellite imagery precisely against roads and place names.
- Disaster response maps that overlay a flood extent, computed from satellite imagery, directly onto a real street map.
- Farm-boundary apps that let a farmer see their exact field outlined against a satellite photo.
An honest warning
A georeferenced file is only as trustworthy as its metadata. Coordinate information can be wrong, corrupted, or in a different reference system than the tool expects. When that happens, the image can silently land in the wrong place on a map, with no visible error. Always sanity-check a new data source against a place you already recognise, before trusting it.
Remember this
- GeoTIFF pairs ordinary pixel data with the real-world coordinates needed to place each pixel on a map.
- Tiling exists purely for practicality — cutting a huge scene into pieces small enough to actually work with.
- Wrong or missing coordinate metadata fails silently, so always check a new file against a known landmark.
What to learn next
- Working with satellite imagery — the band and pixel concepts this lesson builds on.
- How images are stored — the general pixel-grid idea underneath any image file, geo or not.
- Predicting crop yield — one of the models these tiles eventually feed.
Developer — Code and libraries.
Setup
pip install numpyReading and writing real, standards-compliant GeoTIFF files with full coordinate-reference-system support is normally done with rasterio, which depends on the GDAL library and can be awkward to install on a plain laptop. This example builds the core math — the affine transform and the tiling logic — using plain NumPy, so it runs anywhere in under a second. A production pipeline would do the same maths through rasterio, which handles file I/O and coordinate systems for you.
Minimal runnable code
import numpy as np
# A tiny synthetic 8x8 satellite scene (one band, standing in for NDVI x100)
scene = np.arange(64).reshape(8, 8)
print("full scene shape:", scene.shape)
# --- Georeferencing: pixel (row, col) -> real-world (longitude, latitude) ---
# An affine transform needs: the top-left corner coordinate, and pixel size in each direction.
origin_lon, origin_lat = 77.5900, 13.0300 # top-left corner of the scene, near Bengaluru
pixel_size = 0.0001 # degrees per pixel, roughly 11 metres here
def pixel_to_coord(row, col):
lon = origin_lon + col * pixel_size
lat = origin_lat - row * pixel_size # latitude decreases as row increases (top to bottom)
return round(lon, 6), round(lat, 6)
print("\ntop-left pixel (0, 0) ->", pixel_to_coord(0, 0))
print("pixel 3 rows, 5 cols in ->", pixel_to_coord(3, 5))
print("bottom-right pixel ->", pixel_to_coord(7, 7))
# --- Tiling: cut the scene into four 4x4 tiles, small pieces a model can load one at a time ---
tile_size = 4
tiles = []
for r in range(0, scene.shape[0], tile_size):
for c in range(0, scene.shape[1], tile_size):
tile = scene[r:r + tile_size, c:c + tile_size]
top_left_coord = pixel_to_coord(r, c)
tiles.append(tile)
print(f"\ntile at pixel row {r}, col {c} (real-world corner {top_left_coord}):")
print(tile)
print(f"\ntotal tiles: {len(tiles)}, each shaped {tiles[0].shape}")full scene shape: (8, 8) top-left pixel (0, 0) -> (77.59, 13.03) pixel 3 rows, 5 cols in -> (77.5905, 13.0297) bottom-right pixel -> (77.5907, 13.0293) tile at pixel row 0, col 0 (real-world corner (77.59, 13.03)): [[ 0 1 2 3] [ 8 9 10 11] [16 17 18 19] [24 25 26 27]] tile at pixel row 0, col 4 (real-world corner (77.5904, 13.03)): [[ 4 5 6 7] [12 13 14 15] [20 21 22 23] [28 29 30 31]] tile at pixel row 4, col 0 (real-world corner (77.59, 13.0296)): [[32 33 34 35] [40 41 42 43] [48 49 50 51] [56 57 58 59]] tile at pixel row 4, col 4 (real-world corner (77.5904, 13.0296)): [[36 37 38 39] [44 45 46 47] [52 53 54 55] [60 61 62 63]] total tiles: 4, each shaped (4, 4)
What actually happened
pixel_to_coord is the entire idea of georeferencing, in four lines: multiply the pixel offset by the pixel size, and add it to the known corner coordinate. Every real GeoTIFF stores exactly these numbers — an origin and a pixel size — in its metadata, plus a coordinate reference system name.
- Longitude increases with
col, moving right. Latitude decreases withrow, moving down — this sign flip trips up almost everyone the first time, because it is the opposite of how y-coordinates usually behave in maths class. - The tiling loop uses simple slicing,
scene[r:r + tile_size, c:c + tile_size], with no library needed. Each tile keeps its own top-left real-world coordinate, which is exactly what you need to stitch results back onto a map later. - Tagging each tile with its own coordinate matters because a model's predictions on a tile are meaningless without knowing where that tile came from.
Common mistakes
Forgetting the latitude sign flip. Treating row and column symmetrically, without negating the row term for latitude, silently places every tile north of where it actually is. This is the single most common georeferencing bug.
Losing the tile's origin coordinate after cutting. If tiles are cut and shuffled, or saved without their coordinates, there is no way to know where a model's prediction on one tile applies. Always keep the tile's origin attached to the tile itself, not in a separate list that could get out of order.
Tiling without overlap for tasks like object detection. A weed or building sitting exactly on a tile boundary gets cut in half, and a detector can miss it in both halves. Real tiling pipelines commonly overlap neighbouring tiles by a small margin and merge duplicate detections afterward.
Try it yourself
Change tile_size from 4 to 3. The scene will not divide evenly (8 is not a multiple of 3), so the last row and column of tiles will come out smaller than the rest. Print each tile's .shape and see exactly which tiles are affected — this uneven-edge case is a routine annoyance in real tiling code.
What to learn next
- How images are stored — the pixel-grid fundamentals this format builds on.
- Working with satellite imagery — bands and resolution, the other half of understanding a satellite file.
- Predicting crop yield — a model that consumes tiles like these as input.
Researcher — Mathematics and papers.
The affine transform
A GeoTIFF stores a 2D affine transform mapping pixel coordinates to a coordinate reference system (CRS):
x = a * col + b * row + c
y = d * col + e * row + fcol,row— pixel column and row index, with(0, 0)at the top-left.x,y— the real-world coordinate in the file's CRS (commonly longitude/latitude in EPSG:4326, or projected metres in a UTM zone).a,e— pixel width and pixel height (in CRS units per pixel).eis conventionally negative, since row increases downward whiley(northing or latitude) increases upward.b,d— rotation/shear terms, zero for a north-up image, which is the overwhelming majority of distributed satellite products.c,f— the coordinate of the top-left corner of the top-left pixel.
The simplified version in the developer example above (lon = origin_lon + col * pixel_size, lat = origin_lat - row * pixel_size) is this general transform with b = d = 0, a = pixel_size, e = -pixel_size. GDAL and rasterio expose this as the GeoTransform tuple (c, a, b, f, d, e).
Coordinate reference systems
The affine transform alone is insufficient without knowing which CRS the resulting (x, y) is expressed in. GeoTIFF stores this as an EPSG code (a registry-assigned integer identifying a specific CRS, e.g. EPSG:4326 for WGS84 latitude/longitude, or EPSG:32643 for UTM zone 43N, covering part of India). Reprojecting between CRSs — required when combining data from sources using different systems — is a non-trivial operation involving a datum transformation, not a simple coordinate rescale, and is normally delegated to PROJ (the library underlying GDAL's reprojection).
Tiling strategy in production pipelines
Real tiling for ML training and inference typically adds:
- Overlap — tiles share a border strip (commonly 10-20% of tile size) so objects near a boundary appear whole in at least one tile; predictions in the overlap region are merged via non-maximum suppression or averaging.
- Cloud/no-data masking per tile — tiles that are majority cloud or no-data are commonly dropped from training, or weighted down, since they contain no learnable signal for most tasks.
- Cloud-Optimized GeoTIFF (COG) — an internal-tiling variant of GeoTIFF that stores the image as a pyramid of pre-computed tiles at multiple resolutions, enabling a client to fetch only the tiles and zoom level it needs over HTTP range requests, without downloading the full file.
Cost
Tiling is O(n) in the number of pixels — each pixel is read and written exactly once regardless of tile size. Tile size is chosen for compute reasons (matching the input size a model architecture expects, and fitting a training batch in GPU memory) rather than for a fundamental algorithmic reason.
Tooling
rasterio (wrapping GDAL) is the standard for reading and writing georeferenced rasters with full CRS support in Python. rio-tiler and large-image are common libraries specifically for tiled, on-demand access to COGs. GDAL's own command-line tools (gdal_translate, gdalwarp) remain the reference implementation most higher-level Python libraries wrap.
What to learn next
- Working with satellite imagery — bands, resolution and sensor fundamentals this format carries.
- Object detection — a task where the tile-overlap and boundary-object problem matters most.
- Data pipelines — the general discipline of building reliable pipelines like a tiling job.