GeoPandas "Geometry Is in a Geographic CRS" Warning: How to Fix It

Problem statement

When you calculate area, length, or distance on a GeoDataFrame, GeoPandas may print a warning like this:

UserWarning: Geometry is in a geographic CRS. Results from 'area' are likely incorrect.
Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.

The same warning also appears for other metric operations. In practice, it shows up in one of these ways:

  • you call .area and get a number in confusing, very small units
  • you call .length and the result does not match map distances
  • you call .distance() between two features and get a value in degrees, not meters
  • .buffer() produces a shape that looks stretched or wrong
  • .centroid triggers the same warning on a geographic CRS

This matters because the geometry is stored in degrees of longitude and latitude, usually EPSG:4326 (WGS84). Degrees are an angular unit, not a linear one. One degree of longitude is a different real-world distance at the equator than near the poles, so any area, length, or distance computed directly on degrees is geometrically wrong.

The warning is usually caused by one of these situations:

  • the layer is in EPSG:4326 and you measured without reprojecting
  • the data was loaded from GeoJSON, which defaults to longitude/latitude
  • the source CRS is geographic and no projected CRS was applied first
  • a metric operation such as area, length, distance, buffer, or centroid ran on angular coordinates
  • the result was interpreted as meters when it was actually in degrees

Quick answer

If GeoPandas warns that your geometry is in a geographic CRS:

  1. check the CRS with gdf.crs to confirm it is geographic, such as EPSG:4326
  2. reproject to a projected CRS with to_crs() before measuring
  3. choose an appropriate projected CRS, such as a UTM zone or an equal-area CRS
  4. run .area, .length, or .distance() on the reprojected data
  5. interpret the result in the linear units of the projected CRS, usually meters
import geopandas as gpd

gdf = gpd.read_file("data/zones.shp")  # EPSG:4326, in degrees

# Reproject to a projected CRS before measuring
gdf_proj = gdf.to_crs(gdf.estimate_utm_crs())

gdf_proj["area_m2"] = gdf_proj.area
print(gdf_proj["area_m2"].head())

The warning disappears because the geometry is now in meters, and the area values are correct.

Picking the right projected CRS

Flowchart — Choosing a projected CRS for measurement based on the area you cover.
Choosing a projected CRS for measurement based on the area you cover.

Step-by-step solution

Step 1: Confirm the CRS is geographic

A geographic CRS stores coordinates as longitude and latitude in degrees. Check it directly.

import geopandas as gpd

gdf = gpd.read_file("data/zones.shp")
print(gdf.crs)
print(gdf.crs.is_geographic)

If gdf.crs.is_geographic returns True, the geometry is in degrees and metric operations will be incorrect. For EPSG:4326, is_geographic is True.

If gdf.crs is None, that is a different problem: GeoPandas has no CRS at all, and you need to assign the correct one with set_crs() before reprojecting.

Step 2: Choose a projected CRS

A projected CRS uses linear units such as meters. The right choice depends on your data:

  • UTM zone: good general-purpose choice for local or regional data; units are meters
  • Equal-area CRS: best when you need correct area across a large region
  • National grid: use the official projected CRS for your country if you have one

Let GeoPandas pick a suitable UTM zone automatically with estimate_utm_crs().

utm_crs = gdf.estimate_utm_crs()
print(utm_crs)

This inspects the data extent and returns the matching UTM CRS, for example EPSG:32633 for UTM zone 33N.

Step 3: Reproject with to_crs()

Use to_crs() to transform the coordinates into the projected CRS. This changes the coordinate values from degrees to meters.

gdf_proj = gdf.to_crs(gdf.estimate_utm_crs())
print(gdf_proj.crs.is_geographic)  # False

Do not use set_crs() here. set_crs() only relabels the existing coordinates; it does not transform them. Using set_crs() would keep the degree values but pretend they are meters, which is wrong.

Step 4: Run the measurement

With the data in a projected CRS, the metric operations are valid and the warning is gone.

gdf_proj["area_m2"] = gdf_proj.area
gdf_proj["perimeter_m"] = gdf_proj.length

print(gdf_proj[["area_m2", "perimeter_m"]].head())

Step 5: Interpret the units

The units come from the projected CRS, not from GeoPandas. For UTM and most metric grids, the units are meters, so:

  • .area returns square meters
  • .length returns meters
  • .distance() returns meters

Convert if you need other units.

gdf_proj["area_km2"] = gdf_proj["area_m2"] / 1_000_000
print(gdf_proj["area_km2"].head())

Code examples

Example 1: Reproduce and fix the warning

import geopandas as gpd

gdf = gpd.read_file("data/zones.shp")
print(gdf.crs)  # EPSG:4326

# This triggers the warning because the CRS is geographic
areas_wrong = gdf.area

# Fix: reproject first, then measure
gdf_proj = gdf.to_crs(gdf.estimate_utm_crs())
areas_correct = gdf_proj.area

print("Wrong (degrees^2):", areas_wrong.iloc[0])
print("Correct (m^2):", areas_correct.iloc[0])

Example 2: Distance between two points in meters

import geopandas as gpd
from shapely.geometry import Point

points = gpd.GeoSeries(
    [Point(12.4924, 41.8902), Point(2.2945, 48.8584)],
    crs="EPSG:4326",
)

# Reproject to an equal-area CRS for a stable distance over a wide extent
points_proj = points.to_crs("EPSG:3035")  # ETRS89 LAEA Europe, meters

distance_m = points_proj.iloc[0].distance(points_proj.iloc[1])
print(f"Distance: {distance_m / 1000:.1f} km")

Example 3: Buffer in meters, then return to WGS84

import geopandas as gpd

gdf = gpd.read_file("data/stations.shp")  # EPSG:4326

# Reproject so the buffer distance is in meters
gdf_proj = gdf.to_crs(gdf.estimate_utm_crs())

# 500 meter buffer around each station
gdf_proj["geometry"] = gdf_proj.buffer(500)

# Optionally reproject back for web mapping
gdf_4326 = gdf_proj.to_crs("EPSG:4326")
print(gdf_4326.crs)

Buffering in EPSG:4326 would treat 500 as 500 degrees, which is meaningless. Always buffer in a projected CRS.

Example 4: Correct area for a large region with an equal-area CRS

import geopandas as gpd

counties = gpd.read_file("data/us_counties.shp")  # EPSG:4326

# NAD83 CONUS Albers is equal-area, ideal for area across the continental US
counties_albers = counties.to_crs("EPSG:5070")

counties_albers["area_km2"] = counties_albers.area / 1_000_000
print(counties_albers[["NAME", "area_km2"]].head())

For area calculations over a wide region, an equal-area CRS such as EPSG:5070 is more accurate than a single UTM zone, which only stays accurate within its own zone.

Explanation

Coordinates only have meaning relative to a CRS. A geographic CRS such as EPSG:4326 describes positions as angles: longitude and latitude in degrees. A projected CRS flattens the curved earth onto a plane and describes positions as linear distances, usually meters.

When you call .area on a geographic CRS, GeoPandas computes a planar area using the degree values as if they were on a flat grid. But degrees are not constant in size. One degree of latitude is roughly the same distance everywhere, while one degree of longitude shrinks toward the poles. So the same shape produces different "area" numbers depending on where it sits, and none of them are square meters. That is why GeoPandas warns you and recommends reprojecting.

Reprojecting with to_crs() transforms the geometry into linear units, so area, length, distance, and buffer all behave as expected. The trade-off is that every flat projection distorts something. UTM keeps distances and areas accurate within a narrow zone. An equal-area projection keeps area correct across a wide region but distorts shape. Choose the projection that preserves the property you care about for your task.

Note that EPSG:3857 (Web Mercator) is a projected CRS in meters, so it silences the warning, but it is not equal-area and badly distorts area and distance away from the equator. Do not use it for measuring area or distance; use UTM or an equal-area CRS instead.

Edge cases or notes

None CRS is a different problem

If gdf.crs is None, GeoPandas cannot reproject at all. Assign the correct source CRS with set_crs() first, then reproject with to_crs().

Web Mercator silences the warning but is still wrong

EPSG:3857 is projected, so the warning disappears, but it heavily distorts area and distance at higher latitudes. Use it for tiled web maps, not for measurement.

estimate_utm_crs() uses the data extent

estimate_utm_crs() picks a UTM zone based on the centroid of the data bounds. If your data spans several UTM zones, a single zone will be inaccurate at the edges. Use an equal-area CRS for wide extents.

Mixing CRS still requires alignment

Reprojecting one layer for measurement does not change other layers. Before joins or overlays, make sure every layer shares the same CRS with to_crs().

The warning is informational, not an error

GeoPandas still returns a number when it warns. The risk is silently using a wrong value, so treat the warning as a prompt to reproject rather than something to ignore.

Combining geometries before measuring

If you dissolve or merge geometries first, do it in the projected CRS too. Use union_all() on a projected GeoSeries so the resulting area is in meters.

For the core workflow, see How to Calculate Area and Distance in GeoPandas (Correctly) and How to Reproject Spatial Data in Python (GeoPandas).

Related task guides:

For the underlying concepts, read Coordinate Reference Systems (CRS) Explained for Python GIS and EPSG Codes Explained: How to Choose the Right CRS in Python.

FAQ

Why does GeoPandas say my geometry is in a geographic CRS?

Because the layer stores coordinates as longitude and latitude in degrees, usually EPSG:4326. Area, length, and distance computed on degrees are not in meters and are geometrically incorrect, so GeoPandas warns you to reproject first.

How do I get rid of the warning the right way?

Reproject to a projected CRS with to_crs() before measuring, for example gdf.to_crs(gdf.estimate_utm_crs()). Then call .area, .length, or .distance() on the reprojected data.

Can I just use EPSG:3857 to remove the warning?

It silences the warning because it is projected, but Web Mercator distorts area and distance away from the equator. Use a UTM zone or an equal-area CRS such as EPSG:5070 for measurements.

What units are my results in after reprojecting?

The units come from the projected CRS. Most metric CRS, including UTM and equal-area grids, use meters, so .area returns square meters and .length and .distance() return meters.

Does the warning mean my data is corrupted or stored incorrectly?

No. The warning is purely about the operation, not the data. Storing geometry in EPSG:4326 is normal and correct for many purposes; the warning only fires because you ran a metric operation (area, length, distance, buffer, centroid) on angular coordinates, and it disappears once you reproject with to_crs().

How do I suppress the warning if I genuinely know what I'm doing?

You can wrap the call in warnings.catch_warnings() and filter UserWarning, but only do this when the result truly does not need linear units. In almost all cases the correct fix is reprojecting with to_crs(), not silencing the message.

Does .centroid give a wrong answer in a geographic CRS, or just warn?

The centroid is computed on the planar degree coordinates, so its position is slightly off from the true geodetic centroid, and it warns for that reason. For accurate centroids, reproject with to_crs() first, compute .centroid, then reproject back to EPSG:4326 if you need lon/lat output.

Can I measure on EPSG:4326 without reprojecting by using a geodesic method instead?

Yes. For a small number of measurements you can compute geodesic area or distance directly on the ellipsoid, for example with pyproj.Geod, which avoids any projection distortion. For bulk vectorized work, reprojecting to a projected CRS with to_crs() is usually simpler and fast enough.