How to Remove Null and Empty Geometries in GeoPandas

Problem statement

A GeoDataFrame can contain unusable geometry rows in two different ways:

  • null geometries: missing values such as None
  • empty geometries: geometry objects that exist but contain no coordinates

This is a common cleanup step in GIS workflows. You may see these rows after:

  • reading shapefiles or GeoJSON files with incomplete features
  • creating geometries from coordinates where some rows failed
  • running clip(), overlay(), or similar operations that return empty results

If you do not remove these rows, they can cause inconsistent behavior or unnecessary errors in:

  • spatial joins
  • overlays
  • plotting
  • exports to shapefile or GeoJSON
  • downstream validation and automation scripts

The practical goal is to keep only rows with real, usable geometries before analysis or export.

Quick answer

Use a two-step filter:

clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()

This does two things:

  • gdf.geometry.notna() removes null geometries
  • ~clean_gdf.geometry.is_empty removes empty geometries

If you want a clean index after filtering:

clean_gdf = clean_gdf.reset_index(drop=True)

Step-by-step solution

Inspect the GeoDataFrame for null geometries

In GeoPandas, a null geometry is a missing value in the geometry column, usually None.

Use isna() to find them:

null_count = gdf.geometry.isna().sum()
print(f"Null geometries: {null_count}")

You can also preview those rows:

print(gdf[gdf.geometry.isna()])

This matters because null geometry rows often pass through file imports or data merges without obvious errors.

Inspect the GeoDataFrame for empty geometries

An empty geometry is different. It is still a geometry object, but it contains no coordinates.

Use is_empty to identify these rows:

non_null_gdf = gdf[gdf.geometry.notna()]
empty_count = non_null_gdf.geometry.is_empty.sum()
print(f"Empty geometries: {empty_count}")

Preview them if needed:

print(non_null_gdf[non_null_gdf.geometry.is_empty])

This often appears after spatial operations where a feature has no resulting geometry.

Remove both null and empty geometries

To keep only usable geometry rows, filter null geometries first and empty geometries second:

clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()

This is the safest cleanup pattern for GeoPandas scripts because it treats null and empty geometry checks separately.

Reset the index after filtering

After removing rows, the original index may contain gaps. Resetting the index makes exports and later processing cleaner:

clean_gdf = clean_gdf.reset_index(drop=True)

This is especially useful before saving a cleaned layer or looping through rows in an automation workflow.

Confirm the cleaned result

Check row counts before and after cleanup:

print(f"Rows before: {len(gdf)}")
print(f"Rows after: {len(clean_gdf)}")
print(f"Remaining null geometries: {clean_gdf.geometry.isna().sum()}")
print(f"Remaining empty geometries: {clean_gdf.geometry.is_empty.sum()}")

You can also preview the cleaned result:

print(clean_gdf.head())

Code examples

Example 1: Remove null geometries from a GeoDataFrame

This example removes rows where the geometry is missing (None).

import geopandas as gpd
from shapely.geometry import Polygon

gdf = gpd.GeoDataFrame(
    {
        "parcel_id": [1, 2, 3],
        "owner": ["A", "B", "C"],
        "geometry": [
            Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
            None,
            Polygon([(2, 2), (3, 2), (3, 3), (2, 3)])
        ]
    },
    crs="EPSG:4326"
)

clean_gdf = gdf[gdf.geometry.notna()].copy()

print(clean_gdf)

Use this only when the problem is missing geometry values and you know the remaining geometries are not empty.

Example 2: Remove empty geometries from a GeoDataFrame

This example removes geometry objects that exist but are empty.

import geopandas as gpd
from shapely.geometry import Polygon

gdf = gpd.GeoDataFrame(
    {
        "site_id": [101, 102, 103],
        "geometry": [
            Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
            Polygon(),
            Polygon([(3, 3), (4, 3), (4, 4), (3, 4)])
        ]
    },
    crs="EPSG:3857"
)

clean_gdf = gdf[~gdf.geometry.is_empty].copy()

print(clean_gdf)

This is useful when you already know there are no null geometries.

Example 3: Remove both null and empty geometries in one workflow

This is the most practical cleanup pattern for imported GIS data.

import geopandas as gpd
from shapely.geometry import Point, Polygon

gdf = gpd.GeoDataFrame(
    {
        "feature_id": [1, 2, 3, 4],
        "name": ["A", "B", "C", "D"],
        "geometry": [
            Point(0, 0),
            None,
            Polygon(),
            Point(5, 5)
        ]
    },
    crs="EPSG:4326"
)

print("Before cleanup")
print(f"Rows: {len(gdf)}")
print(f"Null geometries: {gdf.geometry.isna().sum()}")

non_null_gdf = gdf[gdf.geometry.notna()]
print(f"Empty geometries: {non_null_gdf.geometry.is_empty.sum()}")

clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()
clean_gdf = clean_gdf.reset_index(drop=True)

print("\nAfter cleanup")
print(f"Rows: {len(clean_gdf)}")
print(f"Null geometries: {clean_gdf.geometry.isna().sum()}")
print(f"Empty geometries: {clean_gdf.geometry.is_empty.sum()}")
print(clean_gdf)

Example 4: Read a file, clean geometries, and save the result

This example shows a realistic preprocessing step before analysis.

import geopandas as gpd

input_file = "data/parcels_raw.geojson"
output_file = "data/parcels_clean.geojson"

gdf = gpd.read_file(input_file)

print("Before cleanup")
print(f"Rows: {len(gdf)}")
print(f"Null geometries: {gdf.geometry.isna().sum()}")

non_null_gdf = gdf[gdf.geometry.notna()]
print(f"Empty geometries: {non_null_gdf.geometry.is_empty.sum()}")

clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()
clean_gdf = clean_gdf.reset_index(drop=True)

print("\nAfter cleanup")
print(f"Rows: {len(clean_gdf)}")
print(f"Remaining null geometries: {clean_gdf.geometry.isna().sum()}")
print(f"Remaining empty geometries: {clean_gdf.geometry.is_empty.sum()}")

clean_gdf.to_file(output_file, driver="GeoJSON")

The same cleanup pattern works for shapefiles:

gdf = gpd.read_file("data/roads_raw.shp")
clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()
clean_gdf = clean_gdf.reset_index(drop=True)
clean_gdf.to_file("data/roads_clean.shp")

Explanation

The key point is that null and empty are not the same condition.

  • isna() checks for missing values in the geometry column, such as None
  • is_empty checks for geometry objects that exist but have no coordinates

That is why dropna() or geometry.notna() alone is not enough for many datasets. A row with an empty polygon is not null, so it stays unless you also filter ~geometry.is_empty.

Filtering in two steps is also more reliable across different GeoPandas and Shapely environments because the empty check only runs on rows that already have geometry objects.

In practice, removing both is a good default before:

  • sjoin()
  • overlay()
  • clip()
  • plotting with .plot()
  • writing cleaned outputs for other GIS tools

This cleanup is also different from geometry validity. A polygon can be non-null and non-empty but still invalid because of self-intersections or topology errors. Removing missing and empty rows is usually the first cleanup step, not the last one.

Edge cases and notes

Null and empty geometries are not the same

Do not assume one check covers both cases. Many imported datasets contain a mix of None values and empty Shapely geometries.

Some workflows may keep empty results temporarily

After clip() or overlay(), empty geometries may be expected as an intermediate result. That is fine, but remove them before final export or downstream analysis.

Geometry validity is separate

A geometry can still be invalid after this cleanup. If you need topology-safe data, check validity next.

CRS issues

Removing null and empty geometries does not change CRS, but you should still confirm the GeoDataFrame has the correct CRS before further analysis:

print(gdf.crs)

If you plan to run distance or area calculations after cleanup, reproject to an appropriate projected CRS first.

Watch for export behavior

Some drivers, file formats, and GIS tools handle missing or empty geometries inconsistently. Cleaning geometry rows before writing files helps avoid silent data loss, write errors, or confusing outputs in QGIS and other GIS software.

Common pitfall

dropna() on the full DataFrame may remove rows for non-geometry nulls you want to keep. If your goal is only geometry cleanup, target the geometry column directly.

For the broader background on how GeoPandas works with spatial data, see How to Reproject Spatial Data in Python (GeoPandas).

If you are working with input and output files around this cleanup step, read How to Read a Shapefile in Python with GeoPandas and How to Export GeoJSON in Python with GeoPandas.

If this cleanup is part of a failing file workflow, start with How to Read a Shapefile in Python with GeoPandas to verify the layer loaded correctly.

FAQ

What is the difference between null and empty geometries in GeoPandas?

A null geometry is a missing value such as None. An empty geometry is a real geometry object with no coordinates. Use isna() for null values and is_empty for empty geometry objects.

Do I need to remove both None and empty geometries before spatial joins?

Usually, yes. Spatial joins, overlays, and plotting are more reliable when the geometry column contains only usable geometries.

Why does isna() not catch empty geometries?

Because empty geometries are not missing values. They are geometry objects, so they are not considered null by pandas or GeoPandas.

Can I save a shapefile or GeoJSON if some geometries are null?

Sometimes yes, but behavior depends on the driver, file format, and GIS tool reading the output. Cleaning null and empty geometries first is safer and more predictable.