9.1 Buffers
The simplest spatial operation — zones of influence around points, lines, and polygons.
Key takeaways
- A buffer is the set of points within a distance d of a feature.
- Buffers must be computed in metric units — always reproject or use geographic types first.
- Buffer-then-dissolve is the classic pattern for aggregating overlapping zones.
Introduction
The buffer is the simplest and most-used operation in spatial analysis. Around a point it produces a circle; around a line, a strip; around a polygon, an expanded polygon. Buffers capture "zones of influence" — exactly the relationships analysts care about.
The definition
For a feature F and a distance d (positive or negative), the buffer B(F, d) is:
$$B(F, d) = {p : \text{dist}(p, F) \le d}$$
- Positive d grows the feature.
- Negative d shrinks a polygon (erodes boundaries inward).
- d = 0 returns the feature itself (but cleaned up — buffer(0) is sometimes used to fix invalid geometries).
Units matter
The distance is in the CRS's unit. In EPSG:4326 (degrees), ST_Buffer(geom, 1) produces a "buffer" 1 degree wide — maybe 100 km at the equator, 50 km at 60° latitude, and a shape that's a rectangle more than a circle.
Always buffer in a metric CRS or use the geography type:
1-- Correct: project first
2ST_Buffer(ST_Transform(geom, 3857), 1000)Geometric detail
A buffer around a point is perfectly circular — represented by many vertices approximating a circle. PostGIS's default is 8 segments per quarter-circle (32 per full circle). You can increase this:
ST_Buffer(point, 500, 'quad_segs=16') -- smoother circle, 64 verticesFor lines and polygons, you can control the end cap (round / flat / square) and join style (round / mitre / bevel):
ST_Buffer(line, 100, 'endcap=flat join=mitre')Flat endcaps make strip-like buffers; mitre joins produce sharp corners.
Common use cases
Service areas (flat-earth approximation)
"Every hospital serves everyone within 5 km". A quick and intuitive proxy; for a more accurate service area, use network analysis (Module 13).
Riparian zones
"Buffer every river by 100 m to define an environmentally sensitive zone":
1SELECT ST_Union(ST_Buffer(geom::geography, 100)::geometry) AS riparian
2FROM rivers;Noise zones
"Buffer every highway by a distance that varies with traffic":
1SELECT ST_Buffer(geom::geography,
2 CASE traffic_class
3 WHEN 'heavy' THEN 150
4 WHEN 'medium' THEN 75
5 ELSE 30
6 END
7 )::geometry AS noise_zone
8FROM highways;Variable-width buffers like this are one of PostGIS's superpowers.
Impact buffers
"All buildings within 250 m of a planned construction site":
1SELECT b.*
2FROM buildings b
3WHERE ST_DWithin(b.geom::geography,
4 (SELECT geom FROM sites WHERE id = 42)::geography,
5 250);Often cheaper to use ST_DWithin than to materialise a buffer polygon.
Buffer then dissolve
Buffers often overlap. Dissolving merges them into one polygon:
1SELECT ST_Union(ST_Buffer(geom::geography, 500)::geometry) AS all_zones
2FROM facilities;Dissolving is essential when you want "the total area within 500 m of any facility", not individual overlapping circles.
Negative buffers
Shrinking a polygon inward is useful for:
- Core areas — "the part of a forest polygon more than 50 m from any edge".
- Safety margins — "the part of a field more than 10 m from a road".
- Cleaning — a small negative then positive buffer can remove slivers.
Negative buffers can produce empty geometries (the whole polygon disappears) — check for that.
Asymmetric buffers
Some tools let you buffer only one side of a line or apply different widths to left/right. QGIS singleSidedBuffer or PostGIS ST_OffsetCurve handle this.
Performance considerations
Buffers are moderately expensive for complex geometries. Tips:
- Simplify geometries before buffering if millimetre precision isn't needed.
ST_DWithinavoids materialising buffers when you just need a proximity test.- Index buffered geometries if you query them many times: store the buffer in its own column.
Worked example — population within 5 km of a transit line
1WITH transit_buffer AS (
2 SELECT ST_Union(ST_Buffer(geom::geography, 5000)::geometry) AS geom
3 FROM transit_lines
4)
5SELECT SUM(
6 cb.population *
7 ST_Area(ST_Intersection(cb.geom, tb.geom)) /
8 ST_Area(cb.geom)
9) AS population_in_corridor
10FROM census_blocks cb
11CROSS JOIN transit_buffer tb
12WHERE ST_Intersects(cb.geom, tb.geom);Area-weighted aggregation handles blocks that are only partly within the buffer. Module 16 covers areal interpolation formally.
For communication, export the buffer and affected features to GeoJSON and open them in Atlas. The visual check is simple but powerful: stakeholders can see the zone, click affected features, and spot obvious issues like an unexpectedly large buffer caused by CRS or unit mistakes.
Self-check exercises
1. Your buffer around a lat/lon point looks like an ellipse, not a circle, on the map. Why?
The input CRS is geographic (degrees). 1 degree of longitude is ~111 km near the equator but shrinks toward the poles, so a 0.01°-radius "circle" looks squashed east-west at high latitudes. Reproject to a metric CRS (UTM or Web Mercator locally) or cast to ::geography and back to get true-metre buffers.
2. When is ST_DWithin better than buffering for a distance query?
When you only need to know whether features are within a distance, not to produce the buffer polygon. ST_DWithin uses the spatial index and doesn't materialise the buffered geometry, making it faster and lower memory. Use buffering when you need the zone itself as input to another operation (intersection, display).
3. A query returns "no results" from a negative buffer on some polygons. What's happening?
A negative buffer larger than the polygon's minimum half-width shrinks the polygon to nothing — an empty geometry. It's correct behaviour: narrow features have no interior far enough from the edge. Filter results with ST_IsEmpty or NOT ST_IsEmpty depending on intent.
Summary
- Buffers define zones of influence around features.
- Always in metric units — reproject or use geography types.
- Dissolve buffers to merge overlaps.
- Negative buffers reveal core areas; asymmetric buffers handle one-sided cases.
Further reading
- PostGIS Manual —
ST_Bufferreference. - Turf.js —
turf.bufferfor JavaScript. - Geocomputation with R — vector operations chapter.
- Esri — Buffer (Analysis) tool reference.