To find the correct EPSG code, first decide whether you need display, distance, or area, then let pyproj resolve the UTM zone or national grid for your area of interest and verify the units and axis order before you reproject.

Jump to heading Why this matters

Picking a CRS by guessing — or copying one from an old script — is how layers end up in the wrong place and area statistics come out wrong. The right code depends on both the region and the task: a dashboard covering France wants EPSG:2154 for measurement but EPSG:3857 for its tile map, and a dataset over central Europe wants a different UTM zone than one over the UK. Rather than memorise every zone, you can query the code programmatically for whatever bounding box your data covers, which is far more reliable than eyeballing a zone map. This task sits directly under the CRS & coordinate systems reference, which explains the geographic-versus-projected distinction the lookup depends on.

Resolving an EPSG code from task and regionThe task and a bounding box feed a lookup. A display task resolves to EPSG:3857. A distance task runs a UTM zone query in pyproj and resolves to a local UTM code such as 32633 or a national grid such as 27700. An area task resolves to an equal-area code such as 3035, 5070, or 6933.Task + bboxwhat & wheredisplaydistanceareaWeb MercatorEPSG:3857query_utm_crspyprojLocal UTM / national grid32633 · 27700 · 25832Equal-area3035 · 5070 · 6933

Jump to heading Prerequisites

  • Python 3.10+ with pyproj>=3.5 and geopandas>=0.14
  • A rough bounding box or centre point for your data, in WGS 84 degrees
  • Familiarity with the .crs attribute on a GeoDataFrame, covered in the GeoDataFrame schema reference

Jump to heading Step-by-step solution

Jump to heading Step 1 — Decide what the code is for

The lookup branches on the task. If you only need to draw the data on a web map, you want EPSG:3857 regardless of region and can stop here. If you need distance, you want a local UTM zone or national grid. If you need area, you want an equal-area CRS. Write down the task first, because it changes which of the next steps applies.

python
task = "distance"   # one of: "display", "distance", "area"

Jump to heading Step 2 — Query the UTM zone for your area of interest

For distance work, pyproj can resolve the UTM zone directly from a bounding box. This is the reliable way to get the right zone without reading a map.

python
from pyproj.database import query_utm_crs_info
from pyproj.aoi import AreaOfInterest

# Bounding box around Berlin (lon/lat degrees)
aoi = AreaOfInterest(
    west_lon_degree=13.0, south_lat_degree=52.3,
    east_lon_degree=13.8, north_lat_degree=52.7,
)
utm_list = query_utm_crs_info(datum_name="WGS 84", area_of_interest=aoi)
epsg = int(utm_list[0].code)
print(epsg, utm_list[0].name)     # 32633 WGS 84 / UTM zone 33N

query_utm_crs_info returns the zones intersecting the bounding box, northern hemisphere first. For a compact area you take the first result.

Jump to heading Step 3 — Resolve and inspect a candidate code

Whether the code came from the UTM query, a national-grid choice, or an equal-area choice, load it with CRS.from_user_input (which accepts EPSG integers, strings, and PROJ) and inspect what you got before trusting it.

python
from pyproj import CRS

crs = CRS.from_user_input(epsg)
print(crs.name)                      # WGS 84 / UTM zone 33N
print(crs.axis_info[0].unit_name)    # metre
print(crs.is_projected)              # True
print(crs.area_of_use.bounds)        # (12.0, 0.0, 18.0, 84.0)

The area_of_use.bounds check is the important one: confirm your data actually falls inside that longitude/latitude box, or the projection will distort.

Jump to heading Step 4 — Verify units and axis order, then reproject

Before using the code in the dashboard, confirm the units are metres for measurement work and reproject a copy to sanity-check the coordinate ranges.

python
import geopandas as gpd

gdf = gpd.read_file("berlin_districts.geojson")   # EPSG:4326
metric = gdf.to_crs(epsg)

assert crs.axis_info[0].unit_name == "metre"
# UTM eastings sit near 500,000 at the central meridian
minx, _, maxx, _ = metric.total_bounds
assert 100_000 < minx < 900_000, "eastings out of UTM range — wrong zone?"
gdf["area_km2"] = metric.area / 1_000_000

For quick reference, these are sound defaults for measurement in common regions. Display always uses EPSG:3857.

RegionRecommended EPSGNameNotes
United Kingdom27700OSGB36 / British National GridNational grid, whole country
France (metropolitan)2154RGF93 / Lambert-93National grid
Germany / central EU25832ETRS89 / UTM zone 32N6°E–12°E
Eastern EU / Poland32633WGS 84 / UTM zone 33N12°E–18°E
Europe (area stats)3035ETRS89-extended / LAEA EuropeEqual-area
Contiguous US (area)5070NAD83 / Conus AlbersEqual-area
Global (area stats)6933WGS 84 / EASE-Grid 2.0Equal-area

Jump to heading Verification

Confirm the code round-trips cleanly and produces measurements in the right order of magnitude:

python
from pyproj import CRS

crs = CRS.from_epsg(epsg)
assert not crs.is_deprecated, f"EPSG:{epsg} is deprecated — migrate to its replacement"
assert crs.is_projected, "expected a projected CRS for distance/area work"
# A Berlin district area should be single- to double-digit km², never < 0.01
assert gdf["area_km2"].between(0.01, 10_000).all()

Jump to heading Edge cases and gotchas

  • UTM zone boundaries. A point at 12.0°E sits exactly on the boundary between zones 32N and 33N. query_utm_crs_info may return both; pick the zone that contains the bulk of your data, not a single edge point.
  • Cross-zone areas. If your data spans more than one 6° UTM band, do not force a single zone — distortion grows fast at the edges. Switch to a national grid or the regional equal-area code from the table above.
  • Deprecated codes. Older datasets sometimes carry superseded codes. CRS.from_epsg(code).is_deprecated flags them; migrate to the replacement the warning names so you do not inherit an old datum definition.

Jump to heading FAQ

How do I find the UTM zone for a coordinate in Python?

Use pyproj.database.query_utm_crs_info with an AreaOfInterest bounding box around your point. It returns the matching UTM CRS records, and you read the EPSG code from the first result’s code attribute. UTM zones are 6° of longitude wide, so a bounding box around a single point resolves to exactly one zone.

What EPSG code should I use if my region spans two UTM zones?

Do not force a single UTM zone across a wide region — distortion grows quickly outside a zone’s 6° band. Use a national grid such as EPSG:27700 for the UK, or a regional equal-area projection such as EPSG:3035 for Europe, that is designed to cover the whole area at acceptable distortion.

How do I know if an EPSG code is deprecated?

Build the CRS with pyproj.CRS.from_epsg and check its is_deprecated attribute. Deprecated codes still resolve for backward compatibility, but pyproj flags them and you should migrate to the current replacement code to avoid subtle datum differences.


Back to CRS & Coordinate Systems Reference

Related