CRS Not Found Error in GeoPandas: Causes and Fixes

Problem statement

A common CRS problem in GeoPandas happens when a layer loads without coordinate reference system metadata. In practice, this usually shows up in one of these ways:

  • gdf.crs returns None
  • gdf.to_crs(...) fails
  • spatial joins, overlays, or distance calculations produce wrong results
  • a shapefile loads, but the projection is missing because the .prj file is absent

This matters because GeoPandas needs a defined CRS to interpret coordinates correctly. If a layer has longitude and latitude values but no CRS, GeoPandas cannot reproject it until the source CRS is defined. If projected coordinates are mislabeled, later analysis will also be wrong.

A missing CRS is usually caused by one of these situations:

  • the source file was saved without projection metadata
  • a shapefile is missing its .prj file
  • a GeoJSON file is assumed to be WGS84 longitude/latitude, but the source data may have been exported incorrectly or interpreted with the wrong CRS
  • the GeoDataFrame was created manually from coordinates and CRS was never assigned
  • an export step dropped CRS metadata
  • data moved between tools such as QGIS, CSV-to-point workflows, and Python scripts without preserving CRS information

Quick answer

If the source data has a known CRS but GeoPandas did not detect it, assign it with set_crs():

gdf = gdf.set_crs("EPSG:4326")

If the data already has a CRS and you need to convert it to another one, use to_crs():

gdf = gdf.to_crs("EPSG:3857")

Use set_crs() only to define the current CRS of existing coordinates. Use to_crs() only to reproject after the source CRS is correct. If the file is missing projection metadata, verify the CRS from documentation or the data provider before assigning anything.

Step-by-step solution

Step 1: Check whether the GeoDataFrame has a CRS

Start by inspecting the CRS directly.

import geopandas as gpd

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

Possible results:

  • valid CRS, for example: EPSG:4326
  • missing CRS: None

If gdf.crs is None, GeoPandas has no projection information for that layer.

Step 2: Identify where the missing CRS problem comes from

Check how the data was created or exported.

Common causes:

  • Shapefile missing .prj: shapefiles store CRS in a separate sidecar file
  • GeoJSON workflow issues: modern GeoJSON generally uses WGS84 longitude/latitude, but exported data can still be mislabeled or interpreted incorrectly
  • Manual geometry creation: points were created from x and y columns, but no CRS was assigned
  • Export loss: a previous script wrote features but dropped CRS metadata

For shapefiles, confirm that files such as these exist together:

  • parcels.shp
  • parcels.shx
  • parcels.dbf
  • parcels.prj

If .prj is missing, GeoPandas may read the geometry but report gdf.crs as None.

Step 3: If the CRS is known, assign it correctly

If you know the original CRS, define it with set_crs().

import geopandas as gpd

gdf = gpd.read_file("data/points_without_prj.shp")

if gdf.crs is None:
    gdf = gdf.set_crs("EPSG:4326")

print(gdf.crs)

You can also assign another known CRS, such as British National Grid:

gdf = gdf.set_crs("EPSG:27700")

Important: set_crs() does not change coordinate values. It only labels the existing coordinates with the CRS they already use.

This is the correct fix when you need to assign a CRS in GeoPandas for data that is already in a known coordinate system.

Step 4: Reproject after assigning the CRS

After the source CRS is correctly defined, reproject with to_crs().

gdf = gdf.set_crs("EPSG:4326")
gdf_projected = gdf.to_crs("EPSG:3857")

This is the usual workflow:

  1. load data
  2. check gdf.crs
  3. assign source CRS with set_crs() if missing
  4. convert to target CRS with to_crs()

If you try to_crs() while gdf.crs is None, you may get errors such as Cannot transform naive geometries. Please set a crs on the object first.

Step 5: Verify the fix

After assigning or reprojecting, confirm the result.

print(gdf_projected.crs)
print(gdf_projected.total_bounds)
print(gdf_projected.head())

Check that:

  • gdf.crs is no longer None
  • bounds look reasonable for the expected CRS
  • coordinates are in the right range

For example:

  • EPSG:4326 should usually have longitude and latitude ranges
  • EPSG:3857 should have meter-based values around thousands or millions

Then save the corrected dataset:

gdf_projected.to_file("output/parcels_3857.shp")

Or save to GeoPackage:

gdf_projected.to_file("output/parcels_3857.gpkg", driver="GPKG")

Code examples

Example 1: Read a file and confirm CRS is missing

import geopandas as gpd

gdf = gpd.read_file("data/roads_missing_prj.shp")
print("CRS:", gdf.crs)

if gdf.crs is None:
    print("No CRS found.")

Example 2: Assign a known CRS with set_crs()

import geopandas as gpd

gdf = gpd.read_file("data/roads_missing_prj.shp")

# Confirmed from the data provider that this layer uses WGS84
gdf = gdf.set_crs("EPSG:4326")

print("Assigned CRS:", gdf.crs)

Example 3: Reproject after assigning CRS

import geopandas as gpd

gdf = gpd.read_file("data/roads_missing_prj.shp")
gdf = gdf.set_crs("EPSG:4326")

roads_utm = gdf.to_crs("EPSG:32633")
print("Reprojected CRS:", roads_utm.crs)

Example 4: Create a GeoDataFrame from coordinates and set CRS

import pandas as pd
import geopandas as gpd

df = pd.DataFrame({
    "site": ["A", "B"],
    "longitude": [12.4924, 12.4964],
    "latitude": [41.8902, 41.9028]
})

gdf = gpd.GeoDataFrame(
    df,
    geometry=gpd.points_from_xy(df["longitude"], df["latitude"]),
    crs="EPSG:4326"
)

print(gdf.crs)

This is the correct pattern when using longitude and latitude columns. Without the crs argument, you may end up with gdf.crs = None later.

Example 5: Handle a shapefile missing its .prj file

from pathlib import Path
import geopandas as gpd

shp_path = Path("data/parcels.shp")
prj_path = shp_path.with_suffix(".prj")

print("PRJ exists:", prj_path.exists())

gdf = gpd.read_file(shp_path)
print("Loaded CRS:", gdf.crs)

# Only assign after confirming the correct CRS from metadata or provider documentation
if gdf.crs is None:
    gdf = gdf.set_crs("EPSG:27700")

print("Final CRS:", gdf.crs)

This is a typical workflow for a shapefile that is missing its .prj file.

Explanation

There are three different states to understand:

  • Missing CRS: gdf.crs is None. GeoPandas does not know what the coordinates mean.
  • Assigned CRS: you use set_crs() to define the CRS of the existing coordinates.
  • Reprojected CRS: you use to_crs() to transform coordinates into a different CRS.

These are not interchangeable.

set_crs() fixes missing metadata. It tells GeoPandas what CRS the current coordinate values already use.

to_crs() transforms coordinates from one CRS into another.

If you use to_crs() before setting the source CRS, GeoPandas cannot transform the geometry because the source coordinate system is unknown.

Assigning the wrong CRS is also dangerous. A layer in meters labeled as EPSG:4326 may look valid in code, but every later measurement, overlay, and spatial join may be wrong.

Edge cases or notes

Do not guess the CRS

Do not assign a CRS just because the layer looks right on a map. Verify with:

  • data provider metadata
  • source documentation
  • known coordinate ranges
  • another trusted GIS project

Shapefile-specific issue: missing .prj

A shapefile stores projection information outside the .shp file itself. If the .prj file is missing, GeoPandas may read geometry but not CRS.

GeoJSON note

Modern GeoJSON generally uses WGS84 longitude/latitude. Even so, exported data can still be mislabeled or interpreted incorrectly in a workflow, so verify before assigning a CRS manually.

Error after to_crs()

If you see errors related to naive geometries, first check:

print(gdf.crs)

If it returns None, assign the source CRS before reprojecting.

Invalid geometries are a separate issue

A missing CRS and invalid geometry are different problems. If reprojection still fails after fixing CRS, check geometry validity:

print(gdf.is_valid.value_counts())

Verify after export

After fixing the layer, save it to a format that preserves CRS and verify after export by reading it back.

For the broader concept, see What Is a CRS in GIS? Coordinate Reference Systems Explained.

Related task guides:

If reprojection still fails, see Cannot Transform Naive Geometries in GeoPandas.

FAQ

Why does gdf.crs return None in GeoPandas?

It usually means the source data has no readable CRS metadata. Common causes are a missing .prj file, manual geometry creation without a CRS, or a previous export that dropped projection information.

What is the difference between set_crs() and to_crs()?

set_crs() defines the current CRS of existing coordinates. It does not move geometry. to_crs() transforms coordinates into a new CRS.

Can I assign a CRS if my shapefile is missing the .prj file?

Yes, but only if you know the correct CRS from a reliable source. In that case, use set_crs() to define it. Do not guess.

Why are my coordinates wrong after fixing the CRS?

The most common reason is that the wrong CRS was assigned with set_crs(). Another possibility is that you reprojected from an incorrect source CRS. Check the original coordinate system first, then assign, then reproject.