Choosing a DTM Resolution from Point Density
TL;DR: A DTM cell should be no smaller than about the nominal ground point spacing, 1/√(ground density). Measure it: for each candidate resolution, count the share of cells containing at least one class 2 return, overall and under canopy. Choose the finest resolution at which most open-ground cells are supported (typically 90 % or more) and interpolated voids stay acceptable in vegetated areas — usually 0.5 m for dense drone data, 1 m for QL1/QL2 airborne data, 2 m or coarser for sparse legacy collections.
# Context and Motivation
This guide is part of DTM Raster Generation. Cell size is the most consequential number in a DTM. Too coarse and the terrain model smooths away ditches, kerbs and small channels. Too fine and most cells contain no ground return at all, so their values are interpolated from neighbours several cells away: the raster looks detailed but the detail is invented, and file size grows with the square of the refinement. The right answer depends on how many ground returns there are, not on how many points the dataset has overall — and ground density under canopy can be a small fraction of the headline density.
Measuring support per candidate resolution takes one pass over the ground points and turns the choice into an evidence-based decision.
# Prerequisites and Assumptions
- Ground-classified tiles (class 2) representative of the project’s land cover.
- PDAL and NumPy.
- A land-cover or canopy mask if you want the support broken down by vegetation.
- The specification’s DEM requirements; the USGS Lidar Base Specification, for example, sets DEM cell sizes by quality level (1 m for QL1 and QL2 bare-earth DEMs).
# Step-by-Step Implementation
# Step 1 — Measure ground density
Count class 2 returns over the tile’s land area. Ground point spacing ≈ 1/√density.
# Step 2 — Grid ground returns at candidate resolutions
For each candidate cell size (0.25, 0.5, 1, 2 m), compute the share of cells with at least one ground return.
# Step 3 — Break down by land cover
Compute the same share in open areas and under canopy separately. Open-ground support decides the resolution; canopy support tells you how much of the DTM will be interpolated.
# Step 4 — Apply a support threshold
Choose the finest resolution where open-ground support is at least about 90 percent.
# Step 5 — Record the choice
Store the resolution, the support figures and the void fraction in the DTM metadata or delivery report.
# Complete Working Example
"""Ground support per candidate DTM resolution, open ground versus canopy."""
from __future__ import annotations
import json
import numpy as np
import pdal
TILE = "tiles/t_0431.laz"
CANDIDATES = (0.25, 0.5, 1.0, 2.0)
p = pdal.Pipeline(json.dumps({"pipeline": [
TILE,
{"type": "filters.range", "limits": "Classification![7:7],Classification![18:18]"},
{"type": "filters.hag_nn", "count": 2},
]}))
p.execute()
a = p.arrays[0]
ground = a[a["Classification"] == 2]
x0, y0 = a["X"].min(), a["Y"].min()
w, h = a["X"].max() - x0, a["Y"].max() - y0
# Canopy mask at 2 m: any return higher than 2 m above ground in the cell.
tall = a[a["HeightAboveGround"] > 2.0]
cm = np.zeros((int(h // 2) + 1, int(w // 2) + 1), bool)
cm[((tall["Y"] - y0) // 2).astype(int), ((tall["X"] - x0) // 2).astype(int)] = True
density = len(ground) / (w * h)
print(f"ground density {density:.2f} /m², nominal ground spacing {1 / np.sqrt(density):.2f} m")
print(f"{'res':>5} {'open':>7} {'canopy':>7} {'overall':>8}")
for res in CANDIDATES:
rows, cols = int(h // res) + 1, int(w // res) + 1
hit = np.zeros((rows, cols), bool)
hit[((ground["Y"] - y0) // res).astype(int), ((ground["X"] - x0) // res).astype(int)] = True
yy, xx = np.meshgrid(np.arange(rows) * res + res / 2, np.arange(cols) * res + res / 2, indexing="ij")
canopy = cm[(yy // 2).astype(int).clip(0, cm.shape[0] - 1), (xx // 2).astype(int).clip(0, cm.shape[1] - 1)]
print(f"{res:>5} {hit[~canopy].mean():>7.1%} {hit[canopy].mean():>7.1%} {hit.mean():>8.1%}")Illustrative output for a mixed tile flown at about 12 pts/m²:
ground density 5.40 /m², nominal ground spacing 0.43 m
res open canopy overall
0.25 38.2% 11.9% 31.0%
0.5 78.6% 31.4% 65.7%
1.0 97.9% 62.8% 88.3%
2.0 100.0% 91.0% 97.5%At 1 m, 98 percent of open cells hold a real ground return — a well-supported DTM. At 0.5 m, a fifth of open cells would be interpolated.
# Key Parameter Table
| Ground density (pts/m²) | Ground spacing | Typical DTM resolution | Notes |
|---|---|---|---|
| 0.5 | 1.4 m | 2 m | Sparse legacy or QL3 collections |
| 2 | 0.7 m | 1 m | QL2 open ground |
| 5–8 | 0.35–0.45 m | 0.5–1 m | QL1, dense airborne |
| 20+ | 0.2 m | 0.25 m | Drone LiDAR, open sites |
| canopy share | — | same grid | Report interpolated fraction separately |
# Verification
- Support threshold met on open ground for the chosen resolution.
- Void and interpolation map. Write a companion raster flagging cells without a ground return; it documents where values are interpolated.
- Detail check. Compare hillshades at the chosen and next-finer resolution; if the finer one shows only texture, not new landforms, the choice is right.
# Gotchas and Edge Cases
Headline density is not ground density. A 20 pts/m² forest flight may deliver 2 pts/m² of ground. Always measure class 2 only.
Specification versus physics. A contract may demand 0.5 m DTMs from data that supports 1 m. Deliver what is asked, but report support so users know how much is interpolated.
Overlap stripes. Sidelap doubles ground density in stripes, so support varies across a tile. Evaluate outside overlap too, or accept that the finest resolution is only supported in stripes.
Rasterization radius. With writers.gdal, a larger radius or window_size fills more empty cells from neighbours. That is interpolation by another name; it does not change what the data supports.
# Frequently Asked Questions
What DTM resolution should I use for LiDAR?
The finest resolution at which most open-ground cells contain at least one real ground return, which is roughly the nominal ground point spacing. For typical QL2 airborne data that is about 1 metre; dense drone LiDAR can support 0.25 to 0.5 metres.
How do I calculate ground point spacing?
Divide the number of ground-classified returns by the land area to get ground density, then take one over its square root. Measure it separately under canopy, where it is usually much lower.
Is a finer DTM always better?
No. Below the ground point spacing, most cells are interpolated, so the raster looks detailed without containing more information, while storage and processing grow four-fold with each halving of the cell size.
Why does the USGS require 1 metre DEMs?
The Lidar Base Specification pairs DEM cell size with the quality level’s point density: QL1 and QL2 data support a 1 metre bare-earth DEM across most land cover. Check the edition your project follows for exact requirements.
# Related
- DTM Raster Generation — building the raster
- Generating a DTM GeoTIFF with writers.gdal — the writer settings
- Filling NoData Voids in DTM Rasters — what happens to unsupported cells
- Measuring Ground Point Density Under Canopy — the density that decides
- Building a Point Density Raster with PDAL — mapping support spatially