Build a TIN Interpolation in PyQGIS

A triangulated irregular network connects your sample points into triangles and interpolates inside each one. The result passes exactly through every observation, respects breaklines where you supply them, and stops dead at the edge of the data. That last property is why surveyors reach for it: a TIN never invents terrain beyond where somebody stood with an instrument.

This recipe belongs to Terrain & Interpolation Analysis in PyQGIS. It covers running the interpolation from Python, combining point and line inputs so ridges and river channels are honoured, choosing between the two interpolation methods, and exporting the triangle mesh when the structure itself is the deliverable.

A TIN interpolates inside triangles and refuses to leave themSample points are connected into non overlapping triangles. Any location inside a triangle takes its value from the plane through that triangle's three vertices, so every observation is reproduced exactly. Locations outside the convex hull of the samples fall in no triangle and receive nodata rather than an extrapolated value.Outside the hull there is no triangle, so there is no answerthe triangulationevery vertex is a real measurementthe convex hull limitinterpolatednodatanodatathe surface refuses to extrapolate

Prerequisites

  • QGIS 3.34 LTR (bundled Python 3.12) or newer.
  • Point, line or polygon layers carrying the value to interpolate, in a projected CRS.
  • Enough sample density that a triangle edge is short relative to how quickly the surface changes — a TIN over sparse points is a set of visible facets, not a surface.

Run the interpolation

The parameters mirror the IDW algorithm, including the same encoded input string.

import processing
from qgis.core import QgsProject

layer = QgsProject.instance().mapLayersByName("spot_heights")[0]
index = layer.fields().indexOf("height_m")
spec = f"{layer.source()}::~::0::~::{index}::~::0"

processing.run("qgis:tininterpolation", {
    "INTERPOLATION_DATA": spec,
    "METHOD": 0,                       # 0 linear, 1 Clough-Tocher
    "EXTENT": layer.extent(),
    "PIXEL_SIZE": 5,
    "OUTPUT": "/data/output/surface_tin.tif",
    "TRIANGULATION": "/data/output/triangles.gpkg",
})

Breakdown: METHOD picks between linear interpolation — each triangle is a flat plane, so the surface has visible creases at every edge — and Clough-Tocher, a cubic scheme that produces a smooth surface with continuous slope across the edges. Linear is faster, exactly reproduces the input, and is the right choice when the facets are honest about the data density. Clough-Tocher looks far better and can overshoot, producing values slightly outside the observed range near sharp changes. TRIANGULATION is optional and writes the triangle edges as a line layer, which is worth requesting every time because it makes the sample density visible at a glance.

PIXEL_SIZE matters less than with IDW, because the surface is defined by the triangles rather than by the grid: a fine grid samples the same triangles more densely rather than inventing structure. It still costs time and disk, so match it to the shortest triangle edge you care about.

Add breaklines so the surface respects real edges

A triangulation of points alone will happily run a triangle straight across a river or a retaining wall, averaging the two sides into a slope that does not exist. Line inputs stop that.

points = QgsProject.instance().mapLayersByName("spot_heights")[0]
breaks = QgsProject.instance().mapLayersByName("ridge_lines")[0]

spec = ";".join([
    f"{points.source()}::~::0::~::{points.fields().indexOf('height_m')}::~::0",
    f"{breaks.source()}::~::1::~::-1::~::1",
])

processing.run("qgis:tininterpolation", {
    "INTERPOLATION_DATA": spec,
    "METHOD": 0,
    "EXTENT": points.extent(),
    "PIXEL_SIZE": 5,
    "OUTPUT": "/data/output/surface_break.tif",
})

Breakdown: Several inputs are joined with a semicolon and each carries its own four fields. The second entry uses geometry type 1 for lines, an attribute index of -1, and a final 1 meaning "take the value from the geometry's Z coordinate" — which is how a 3D breakline supplies elevations along its length rather than a single attribute value. The triangulation is then constrained so no triangle edge crosses a breakline, which is exactly what preserves a ridge crest or a channel bottom.

Contour lines make excellent breakline input, and this is the standard route for rebuilding a surface from a scanned map: contour the DEM with CREATE_3D enabled, or digitise contours and populate Z from the elevation attribute, then feed them in as line input.

A breakline stops triangles crossing real edgesWithout a breakline, a triangle spans the valley and the interpolated surface fills it in, removing the channel. With the channel supplied as a line input the triangulation is constrained so that no edge crosses it, and the valley survives in the interpolated surface.Without a constraint, the triangulation fills the valley inpoints onlyedges cross the channelwith the channel as a breaklineno edge crosses it — the valley survives

Linear or Clough-Tocher

The two methods produce visibly different surfaces from identical inputs, and the choice is about what the deliverable claims rather than which looks nicer.

Linear interpolation treats each triangle as a flat plane. Slope is constant within a triangle and changes abruptly at every edge, so a hillshade of a linear TIN shows the triangulation as a pattern of facets. Every output value lies on a plane through three real measurements, which means the surface never asserts anything the data does not directly support. For volumes, cut-and-fill calculations and anything that will be signed off, that guarantee is worth more than smoothness.

Clough-Tocher fits a cubic patch to each triangle with matching slopes across the edges, producing a surface with no visible creases. It still passes through every sample. What it does not guarantee is staying inside the observed range: near a sharp change the cubic can overshoot, producing a small hollow beyond the lowest sample or a bump above the highest. On terrain that is genuinely smooth this is a fair approximation of reality; on stepped or engineered surfaces it is an artefact.

import processing

for method, name in ((0, "linear"), (1, "clough")):
    processing.run("qgis:tininterpolation", {
        "INTERPOLATION_DATA": spec,
        "METHOD": method,
        "EXTENT": layer.extent(),
        "PIXEL_SIZE": 5,
        "OUTPUT": f"/data/output/surface_{name}.tif",
    })

processing.run("native:rasterlayerstatistics", {
    "INPUT": "/data/output/surface_clough.tif",
    "BAND": 1,
    "OUTPUT_HTML_FILE": "TEMPORARY_OUTPUT",
})

Breakdown: Running both and comparing the statistics is the quickest test for overshoot: if the Clough-Tocher minimum sits below the lowest observation or its maximum above the highest, the cubic has extrapolated locally and you now know by how much. On a well-sampled smooth surface the difference is a few centimetres and irrelevant; on sharp data it can be metres, and that number belongs in the decision rather than in a footnote discovered later.

Use the triangulation itself

The triangle layer is more than a diagnostic. Its edge lengths are a direct map of where the data is dense and where the surface is guesswork.

from qgis.core import QgsVectorLayer, QgsProject

tri = QgsVectorLayer("/data/output/triangles.gpkg", "triangulation", "ogr")
QgsProject.instance().addMapLayer(tri)

lengths = sorted(f.geometry().length() for f in tri.getFeatures())
n = len(lengths)
print(f"median edge {lengths[n // 2]:.1f} m, 95th percentile {lengths[int(n * 0.95)]:.1f} m")

Breakdown: The median edge length is a defensible statement of the surface's real resolution, and the 95th percentile shows how bad the sparse regions are. Styling the triangulation with a graduated renderer on length produces a coverage map that is far more informative to a client than a smooth colour-ramped surface, because it shows where the answer came from data and where it came from geometry.

Long thin triangles around the hull edge — slivers — are a known artefact of Delaunay triangulation and produce unreliable interpolation there. Buffering inward from the hull by roughly the median edge length and clipping is the usual tidy-up.

QGIS version compatibility

The examples target QGIS 3.34 LTR (Python 3.12).

QGIS versionPythonNotes
3.16 LTR3.7qgis:tininterpolation with linear and Clough-Tocher methods present.
3.22 LTR3.9TRIANGULATION output parameter stable.
3.28 LTR3.9Breakline constraint handling improved for 3D line inputs.
3.34 LTR3.12Baseline for this page.
3.40+3.12Mesh layers offer an alternative route for storing and rendering the triangulation.

Troubleshooting

  • The output is empty outside a small area. That is the convex hull limit working as designed. Add samples, or accept the boundary.
  • The surface has visible flat facets. Linear interpolation with sparse samples. Switch to Clough-Tocher for appearance, or add data for substance.
  • Clough-Tocher produced values above the highest sample. Cubic interpolation can overshoot near sharp changes. Use linear where the range must be respected.
  • A triangle crosses a river. No breakline was supplied. Add the channel as a line input with Z values.
  • The breakline had no effect. Its spec entry has the wrong geometry-type flag or the use-Z flag is 0 with an attribute index of -1.
  • Slivers around the edge give odd values. Clip inward from the hull by about the median edge length.

Conclusion

Build the spec with the right geometry-type and use-Z flags, supply breaklines for every real discontinuity, prefer linear interpolation when the values must stay within the observed range, and always request the triangulation so the sample density is visible. The hull boundary is not a limitation to work around — it is the surface telling you where the data ends.

Frequently Asked Questions

Can a TIN interpolate from contour lines alone? Yes, and it is a common use. Supply the contours as line input with the elevation in Z. Expect terracing between widely spaced contours, since the triangulation has nothing between them to work with.

How do I convert the surface back to contours? Contour the output raster exactly as you would any DEM. Contouring a TIN-derived surface built from contours will not reproduce the originals precisely, which is a useful reminder of how much the triangulation smoothed.

Is a TIN the same as a mesh layer? Related but not identical. QGIS mesh layers store a triangulation with values at vertices and can carry time steps and multiple datasets. The TIN algorithm produces a raster and, optionally, the triangle geometry — not a mesh layer.

Which method should I use for a survey? Linear, almost always. It reproduces every observation exactly and never invents a value outside the measured range, which is what a survey deliverable is expected to do.