The short answer

A buffer of "0.01 degrees" is not a buffer of any fixed distance, because a degree is an angle, not a length. One degree of latitude is roughly constant at ~111 km, but one degree of longitude shrinks with latitude: ~111 km at the equator, ~78 km at 45°, and zero at the poles. So a buffer computed in a geographic CRS such as WGS84 (EPSG:4326) comes out stretched north–south relative to east–west, the distortion growing the farther you are from the equator. The fix is not subtle: reproject the analysis layer into a projected, metre-based CRS before buffering, or in a database use the geography type so the operation runs on the ellipsoid. Then "500" means 500 metres everywhere.

Why a degree is not a distance

Latitude and longitude are angular coordinates on a sphere/ellipsoid. The ground distance subtended by one degree is:

  • Latitude: ~111,320 m, near-constant from equator to pole (the slight variation comes from the ellipsoid's flattening).
  • Longitude: 111,320 × cos(latitude) metres. The cosine term is the whole problem.

Run the numbers for a "0.01°" buffer:

Latitude0.01° north–south0.01° east–west
0° (equator)~1,113 m~1,113 m
30°~1,113 m~964 m
45°~1,113 m~787 m
60°~1,113 m~557 m

At the equator the buffer is roughly circular; at 60° it is an ellipse half again as tall as it is wide. The geometry library did exactly what you asked — it offset every vertex by 0.01 coordinate units — but the units are degrees, so the result is geometrically inconsistent across the map and useless as a distance buffer.

What actually happens when you buffer in EPSG:4326

GEOS (the engine behind QGIS, GeoPandas, Shapely, and PostGIS geometry) buffers in the layer's coordinate units. Give it WGS84 geometry and a distance of 0.01, and it offsets vertices by 0.01 degrees. The output is an ellipse, oriented to the graticule, that is too narrow east–west at mid and high latitudes. Worse, the error is silent: the layer renders, the polygon looks plausible, and a downstream area or "within 500 m" query inherits the distortion. Most tools will warn you — QGIS's buffer dialog flags a geographic CRS, and PostGIS suggests casting to geography — but the warning is easy to dismiss.

The correct workflow

  1. Identify the CRS of the layer (ogrinfo -al -so, QGIS layer properties, ST_SRID).
  2. Pick a projected CRS in metres appropriate to the extent: a local UTM zone (e.g. EPSG:32632 for 6°–12°E in the northern hemisphere) or a national grid (British National Grid EPSG:27700, etc.). For large or high-latitude areas, use an equidistant or equal-area projection centred on the study area instead of a single UTM zone.
  3. Reproject the analysis layer into that CRS.
  4. Buffer in metres.
  5. Reproject back to the display/delivery CRS if needed.

In GDAL/OGR:

ogr2ogr -t_srs EPSG:32632 sites_utm.gpkg sites_wgs84.gpkg
# then buffer in a tool that takes metres, e.g. via GDAL/ogr SQL or QGIS

In QGIS, reproject with "Reproject Layer", then run "Buffer" with a distance of 500 (now metres). Or in one step use the field-calculator/SQL on a reprojected layer.

In PostGIS there are two clean options. Reproject and buffer in metres:

SELECT ST_Buffer(ST_Transform(geom, 32632), 500) AS buf
FROM sites;

Or stay in lon/lat and let the geography type handle the ellipsoid:

SELECT ST_Buffer(geom::geography, 500) AS buf
FROM sites;

The geography buffer measures 500 m of true ground distance regardless of latitude, which is the most robust choice when your data spans wide latitude ranges or crosses UTM zone boundaries.

In GeoPandas: gdf.to_crs(32632).buffer(500) — never gdf.buffer(0.0045) on a 4326 frame.

Choosing the projected CRS

The buffer is only as accurate as the projection's distance preservation in your area:

  • Single project area: the local UTM zone is the default; distortion stays within ~1 part in 1,000 inside the 6° band.
  • Area spanning multiple UTM zones, or near a zone edge: a custom Transverse Mercator centred on the area, or a national grid, avoids the seam.
  • Continental or polar extents: an azimuthal equidistant projection centred on the region, or the PostGIS geography approach, since no single planar CRS preserves distance over such an extent.

The same logic applies to every metric operation, not just buffers: distance, length, and area are all wrong if computed in degrees.

Common pitfalls and why they happen

  • Buffering directly on a 4326 layer because it loads and displays fine. The angular units make the result an inconsistent ellipse.
  • Approximating a metre buffer as degrees (e.g. "500 m ≈ 0.0045°"). It is only right at one latitude and one direction; everywhere else it drifts.
  • Using Web Mercator (EPSG:3857) for the buffer because the basemap is in it. Web Mercator is metre-based but its scale factor blows up with latitude (~1/cos(lat)); a 500 "metre" buffer at 60° is roughly double the true ground distance. It is for tiling, not measurement.
  • Relying on on-the-fly reprojection. It fixes display only; the analysis still runs in the layer's stored CRS.
  • Crossing a UTM zone boundary with a single zone's CRS, introducing growing distortion past the 6° band — use geography or a custom projection.

Validation

Buffer a point of known location, then measure the radius back in the source CRS against a trusted reference — a 500 m buffer should be 500 m on the ground in every direction. In PostGIS, compare ST_Area(ST_Buffer(geom::geography, 500)) (should be ~π·500² ≈ 785,398 m²) against the same buffer done in degrees (it will not match and will vary with latitude). If the circle looks like an ellipse on an equal-distance reference, you buffered in degrees.

Bathyl perspective

We never run distance, area, or buffer operations in a geographic CRS: the analysis layer is reprojected to a metre-based CRS sized to the extent, or computed on the geography type, and the chosen CRS is recorded with the output. A buffer that claims "500 m" is verified to be 500 m before it reaches a deliverable.

Related reading

Sources