This guide is part of CRS Normalization Across Mixed Datasets, within the broader Automated Vector & Raster Cleaning Workflows reference.
Choosing a Projected CRS for Area and Distance
Every projection distorts something. Choosing one is choosing which distortion the pipeline can live with, and the choice is determined by what is being measured rather than by what looks familiar.
Why the Default Choice Is Usually Wrong
- Degrees are not a length unit. A buffer of 0.001 in EPSG:4326 is 111 metres at the equator and 43 in Norway.
- Web Mercator is a display projection. Its area error is over 400% at 65 degrees latitude, and it is the most common CRS in a spatial stack.
- A single UTM zone does not cover a country. Distortion at the zone edge is around one part in a thousand and grows quickly beyond it.
- The measurement CRS is rarely recorded. An area column with no accompanying CRS cannot be checked, only trusted.
Version and Environment Compatibility
| Component | Version | Note |
|---|---|---|
| pyproj | >=3.6 |
CRS.area_of_use, estimate_utm_crs, geodesic calculations |
| GeoPandas | >=1.0 |
estimate_utm_crs on a frame; to_crs round trip |
| PROJ | >=9.0 |
Modern equal-area definitions and grid transforms |
pip install "geopandas>=1.0" "pyproj>=3.6"Matching the Projection to the Quantity
The measurement_crs Recipe
from __future__ import annotations
import logging
import geopandas as gpd
from pyproj import CRS, Geod
logger = logging.getLogger(__name__)
GEOD = Geod(ellps="WGS84")
# Continental equal-area systems, keyed by a rough extent test.
EQUAL_AREA = {
"world": 6933, # WGS 84 / NSIDC EASE-Grid 2.0 Global
"europe": 3035, # ETRS89-extended / LAEA Europe
"north_america": 5070, # NAD83 / Conus Albers
}
def measurement_crs(gdf: gpd.GeoDataFrame, quantity: str = "area") -> CRS:
"""Return a CRS whose distortion is acceptable for the quantity being measured."""
if gdf.crs is None:
raise ValueError("cannot choose a measurement CRS for a frame with no CRS")
west, south, east, north = gdf.to_crs(4326).total_bounds
span_deg = max(east - west, north - south)
if quantity == "area" and span_deg > 12:
# Wider than about two UTM zones: an equal-area projection is required.
key = "europe" if (-25 < west < 45 and 34 < south < 72) else "world"
chosen = CRS.from_epsg(EQUAL_AREA[key])
logger.info("extent spans %.1f° — using equal-area %s", span_deg, chosen.name)
return chosen
chosen = gdf.estimate_utm_crs()
logger.info("extent spans %.1f° — using local %s", span_deg, chosen.name)
return chosen
def true_area_m2(gdf: gpd.GeoDataFrame) -> gpd.GeoSeries:
"""Area in square metres, measured in a CRS appropriate to the extent."""
return gdf.to_crs(measurement_crs(gdf, "area")).geometry.area
def geodesic_length_m(gdf: gpd.GeoDataFrame) -> list[float]:
"""Length on the ellipsoid — exact, and independent of any projection."""
wgs84 = gdf.to_crs(4326)
return [GEOD.geometry_length(geom) for geom in wgs84.geometry]Key Implementation Notes
- The extent decides, not the country. A twelve-degree span is roughly two UTM zones, beyond which zone distortion exceeds what most area work tolerates.
estimate_utm_crsuses the frame’s centroid. It is the right default for local work and silently wrong for a dataset that straddles a zone boundary — hence the span check first.- Geodesic length needs no projection.
Geod.geometry_lengthcomputes on the ellipsoid and is more accurate than any projected length, at the cost of being slower per feature. - The chosen CRS is logged, not just used. A measurement whose CRS is unrecorded cannot be reproduced or audited, which is the same argument made for
area_crsin the metrics guidance. - Equal-area systems are listed explicitly. A lookup table of a few continental systems is more honest than a formula, because the right choice genuinely depends on the region’s conventions.
- A missing CRS raises. Guessing a measurement system for an unreferenced frame produces numbers that look authoritative and are not.
Verifying the Choice
A projection choice is a hypothesis, and it is cheap to test. Take a feature whose true size is published — an administrative unit with a gazetted area, a survey parcel, a national park — measure it in the candidate CRS, and compare.
Two checks are worth running as assertions rather than as a one-off. Measure the same feature in the chosen CRS and in a geodesic computation on the ellipsoid; agreement within a fraction of a percent confirms the projection is behaving over that extent. And measure a feature at each corner of the dataset’s extent, because distortion grows away from the projection’s centre and the corner cases are where it becomes visible.
Where the pipeline serves published figures, record both the measurement and its CRS in the output. The column costs nothing and it converts “these areas look wrong” from a dispute into a lookup.
Troubleshooting Measurement CRS Choices
| Symptom | Likely cause | Fix |
|---|---|---|
| Areas inflated by 2–5× | Measured in Web Mercator | Reproject to an equal-area system first |
| Areas drift across a country | Single UTM zone over too wide an extent | Use an equal-area or national system |
| Buffers vary in size by latitude | Buffering in degrees | Project to metres before buffering |
| Lengths slightly short | Projected length over a long feature | Compute geodesically on the ellipsoid |
| Values changed after a library upgrade | Different transform path selected | Pin the CRS and the PROJ grids explicitly |
| Two teams report different areas | Different measurement CRSs, both unrecorded | Record the measurement CRS with the value |
Integration Note
Measurement CRS selection belongs in the transform stage, not at ingestion: the storage CRS and the measurement CRS are different decisions, and conflating them forces every consumer to accept one projection’s distortions. Store geometry in the CRS the pipeline standardises on — usually the one described in CRS normalization across mixed datasets — and project transiently for each measurement, recording which system produced each figure.
Distance Is Three Different Questions
“How far apart are these?” hides three computations that differ by enough to matter.
Geodesic distance is the shortest path across the ellipsoid’s surface. It is what a navigator means, it needs no projection, and it is exact to millimetres over any distance. Use it for point-to-point separations and for anything crossing a projection boundary.
Projected distance is the straight line in a plane, which is what a spatial index, a buffer and a nearest-neighbour join all actually compute. It is exact only where the projection’s scale factor is one and close enough within a well-chosen local system. Use it for anything involving geometry operations, because those operations are planar regardless.
Network distance — along roads, rivers or pipes — is unrelated to both and usually the one a stakeholder means. No CRS choice produces it; it needs a routing engine, and confusing it with either of the above is a category error rather than a precision problem.
Stating which of the three a column holds, in its name or its metadata, prevents the most common misinterpretation in spatial analysis: a straight-line proximity figure being read as travel distance.