Hey there!
If you’ve ever explored the world of mapping or spatial data, you’ve probably come across GeoTIFFs. They’re a standard in the industry, with most web maps you’ve seen (from terrain to satellite imagery) being powered by this.
Learning how to work with them, especially how to render them correctly on a web map, is an essential skill.
Recap
Before diving into the code, let’s quickly recap the key concepts.
Raster image
A raster image is made up of pixels arranged in a grid.
Each pixel stores a value (often a color) and together they form the image you see on screen.
Raster images are resolution-dependent: zooming in too far will show individual pixels.
This differs from vector graphics, which define shapes mathematically and can scale without losing quality.
In mapping, raster images often represent much more than color. Each pixel might describe:
- Color intensity – in photos or RGB satellite imagery
- Elevation – in digital terrain or surface models (DTM/DSM)
- Spectral reflectance – in multispectral or scientific data
In short:
Raster = grid of pixel values, each carrying some kind of data.
Bands
A band is a layer of data inside a raster.
Each band represents one type of information for every pixel. for example, color, elevation, or temperature.
Think of it like stacking transparent images on top of each other, where each layer carries a specific variable.
Common examples:
- 1 band: grayscale or elevation
- 3 bands: red, green, blue (RGB)
- 4 bands: RGB + alpha (transparency) or near-infrared
The Rendering Challenge
Multi-band TIFFs (like RGB imagery) often render easily as normal images.
Single-band GeoTIFFs, like elevation models, need pre-processing, usually applying a color ramp to turn numeric values into colors humans can understand.
Without this, your map might look like a dull gray square.
TIFF
TIFF (Tagged Image File Format) is a flexible raster image format. It stores data as grids of pixels and also includes a list of metadata fields called tags.
TIFF Tags
Each TIFF tag describes some property of the image: its dimensions, color depth, or how the data should be interpreted.
A tag consists of:
- Tag ID (numeric identifier)
- Data type (short, long, rational, etc.)
- Value count
- Value(s) or a pointer to the value
Example:
Tag 256 (ImageWidth): 1024
Tag 257 (ImageLength): 2048
Tag 259 (Compression): 1 (no compression)In short: TIFF tags are structured metadata that describe how the image is stored and interpreted.
GeoTIFF
A GeoTIFF is simply a standard TIFF file that includes extra geospatial tags. These crucial tags define:
- How the pixel coordinates map to real-world locations.
- Which Coordinate Reference System (CRS) is being used (e.g., WGS84, UTM).
So:
TIFF = image
GeoTIFF = image + geolocation
Important: Multi-band GeoTIFFs can represent complex data, for example satellite images often include infrared or thermal bands alongside visible ones. A GeoTIFF doesn’t have to be an RGB image at all; it might contain only numerical values like temperature or reflectance.
Serving tiles server-side
Real-world GeoTIFFs are often very large. Trying to load and process them client-side leads to slow, poor performance. The best practice is to pre-process the GeoTIFF into smaller, optimized map tiles server-side.
If your GeoTIFF is small enough, you can use https://github.com/GeoTIFF/georaster-layer-for-leaflet for processing and rendering client-side.
But today, we’ll use GDAL, the Swiss Army knife for geospatial data, to create our tile service.
1. Pre-processing images
A. Tiling a Multi-band (RGB) GeoTIFF
For GeoTIFFs that already contain Red, Green, and Blue bands, the process is straightforward:
1. Reproject to Web Mercator (EPSG:3857)
Reprojecting to Web Mercator is necessary since gdal2tiles.py and Leaflet expect this projection for standard XYZ tile services.
gdalwarp -t_srs EPSG:3857 -r bilinear orthophoto_rgb.tif orthophoto_3857.tif2. Compress for tiling (optional)
gdal_translate -co TILED=YES -co COMPRESS=DEFLATE orthophoto_3857.tif orthophoto_3857_tiled.tifThis improves tile generation speed and reduces memory load.
3. Generate the Tiles
# -z 16-22 specifies the zoom levels
# tiles/ is the directory where your tiles will live
gdal2tiles.py -z 16-22 -r bilinear --xyz orthophoto_3857_tiled.tif tiles/B. Tiling a Single-band GeoTIFF
Single-band images, like a Digital Elevation Model (DEM), require an extra step: colorizing the data. You must apply a color ramp or gradient to map the numeric values (e.g. height) to a visual color before tiling.
1. Colorize the Data
We’ll use the gdaldem color-relief command with a separate color ramp file (here, color_ramp.txt) to create a new RGB GeoTIFF.
color_ramp.txt: (This file defines which raw values get which color)
0 blue
500 green
1500 yellow
3000 red(This is just an example, you’ll probably need something fancier than this)
Apply the color ramp:
gdaldem color-relief input_dem.tif color_ramp.txt colorized_dem.tif2. Reproject and generate tiles
Now that your GeoTIFF is RGB, treat it like any image:
gdalwarp -t_srs EPSG:3857 -r bilinear colorized_dem.tif colorized_3857.tif
gdal2tiles.py -z 5-15 -r bilinear --xyz colorized_3857.tif tiles/2. Serving Tiles with Express
Once the tiles are generated, you need a web server to serve them. This simple Express server handles the common /{z}/{x}/{y}.png tile request pattern.
In production, it’s probably a better option to serve these files using a reserve proxy (Nginx) or a CDN (Cloudflare CDN, AWS CloudFront).
import express from "express";
import path from "node:path";
const PORT = 3000;
const TILES_DIR = path.join(import.meta.dirname, 'tiles');
const app = express();
app.use(
express.static(TILES_DIR, {
maxAge: "30d", // browser caching
etag: true,
})
);
app.listen(PORT, () => {
console.log(`Tile server running at http://localhost:${PORT}/{z}/{x}/{y}.png`);
});3. Rendering tiles in Leaflet
You then consume the tile service using Leaflet’s TileLayer. This example is using react-leaflet, but the principle is the same for vanilla Leaflet.
// ... inside your Leaflet MapContainer ...
<TileLayer
url="http://localhost:3000/{z}/{x}/{y}.png"
opacity={0.7}
/>Tip: gdal2tiles.py also generates sample maps in your tiles directory to help you use Leaflet, Google Maps or OpenLayers.
Why tiles matter
Pre-processing your GeoTIFFs into optimized tiles ensures a fast, scalable, and responsive map experience. Your users get smooth navigation instead of waiting for gigabytes of data to load.
And if you plan to host your data in the cloud, check out Cloud Optimized GeoTIFFs (COGs), a smarter way to store and stream raster data efficiently.
They let you access only the portions you need, instead of downloading entire files.
I’ll dive deeper into COGs, how they work, and how to serve them directly from cloud storage in Part 2.
See ya then!
References
https://leafletjs.com/reference.html#tilelayer
https://gdal.org/en/stable/programs/gdal2tiles.html
https://gdal.org/en/stable/programs/gdaldem.html
https://gdal.org/en/stable/programs/gdalwarp.html
https://github.com/GeoTIFF/georaster-layer-for-leaflet
We want to work with you. Check out our Services page!

