Back to Blog
Technical Guide

From ArcPy to GeoPandas: Complete Translation Guide

Technical deep-dive with extensive code examples. Translate vector operations, raster processing, and complete workflows from ArcPy to GeoPandas and Rasterio.

Axis Spatial Team
January 2025
12 min read
Python code for geospatial analysis
ℹ️

When This Guide Applies

This technical guide is for workflow automation projects where migrating from ArcPy to GeoPandas provides ROI benefits: cost reduction, cloud integration, or scaling improvements.

We're tool-agnostic: If ArcPy is the right fit for your automated workflows, we'll use it. Migration is only recommended when it serves automation goals and provides measurable ROI.

See our workflow automation approach →

You have 50+ ArcPy scripts. Your team knows arcpy.da.SearchCursor by heart. The thought of rewriting everything feels overwhelming.

Here's the good news: The translation from ArcPy to GeoPandas is more straightforward than you think. And the performance improvements? 10-100x faster.

Why GeoPandas > ArcPy

Performance Comparison: Real Workflow

ArcPy (Old)

  • ✗ Single-threaded execution
  • ✗ File Geodatabase locks
  • ✗ Memory management issues
  • ✗ Cursor-based processing (slow)
  • ✗ No cloud integration

3.5 hours

GeoPandas (New)

  • ✓ Vectorized operations
  • ✓ Parallel processing with Dask
  • ✓ Efficient memory usage
  • ✓ DataFrame-based (fast)
  • ✓ Cloud-native (S3, ADLS)

12 minutes

Core Translation Patterns

1. Reading Data

ArcPy

import arcpy
cursor = arcpy.da.SearchCursor("parcels.shp", ["SHAPE@", "VALUE"])
for row in cursor:
    geometry = row[0]
    value = row[1]

GeoPandas

import geopandas as gpd
gdf = gpd.read_file("parcels.shp")
# Access geometry and attributes directly
for idx, row in gdf.iterrows():
    geometry = row.geometry
    value = row['VALUE']

2. Spatial Operations

ArcPy (Buffer)

arcpy.Buffer_analysis(
    "parcels.shp",
    "parcels_buffered.shp",
    "100 METERS"
)

GeoPandas (Buffer)

gdf_buffered = gdf.copy()
gdf_buffered['geometry'] = gdf.geometry.buffer(100)
gdf_buffered.to_file("parcels_buffered.shp")

3. Spatial Joins

ArcPy (Spatial Join)

arcpy.SpatialJoin_analysis(
    "parcels.shp",
    "zones.shp",
    "parcels_with_zones.shp"
)

GeoPandas (Spatial Join)

parcels = gpd.read_file("parcels.shp")
zones = gpd.read_file("zones.shp")
result = gpd.sjoin(parcels, zones, how="left", predicate="intersects")
result.to_file("parcels_with_zones.shp")

Raster Processing: Rasterio vs arcpy.sa

For raster operations, Rasterio replaces arcpy.sa (Spatial Analyst). It's faster, more Pythonic, and cloud-native.

ArcPy (Raster Calculation)

from arcpy.sa import *
dem = Raster("dem.tif")
slope = Slope(dem)
slope.save("slope.tif")

Rasterio + NumPy (Raster Calculation)

import rasterio
import numpy as np
from rasterio.warp import calculate_default_transform

with rasterio.open("dem.tif") as src:
    dem = src.read(1)
    # Calculate slope using NumPy
    slope = np.gradient(dem)

    # Write output
    with rasterio.open("slope.tif", "w", **src.profile) as dst:
        dst.write(slope, 1)

Complete Workflow Example

Let's translate a real workflow: identifying parcels at flood risk.

ArcPy Version (Slow)

import arcpy

# Read parcels
parcels = "parcels.shp"
floodplain = "floodplain.shp"

# Buffer floodplain
arcpy.Buffer_analysis(floodplain, "floodplain_buffered.shp", "50 METERS")

# Intersect
arcpy.Intersect_analysis([parcels, "floodplain_buffered.shp"], "at_risk.shp")

# Calculate risk score
arcpy.AddField_management("at_risk.shp", "RISK_SCORE", "DOUBLE")
cursor = arcpy.da.UpdateCursor("at_risk.shp", ["AREA", "RISK_SCORE"])
for row in cursor:
    row[1] = row[0] * 0.1  # Simple risk calculation
    cursor.updateRow(row)
del cursor

# Export
arcpy.FeatureClassToFeatureClass_conversion("at_risk.shp", "output.gdb", "parcels_at_risk")

GeoPandas Version (Fast)

import geopandas as gpd

# Read data
parcels = gpd.read_file("parcels.shp")
floodplain = gpd.read_file("floodplain.shp")

# Buffer floodplain
floodplain_buffered = floodplain.copy()
floodplain_buffered['geometry'] = floodplain.geometry.buffer(50)

# Spatial join to identify parcels at risk
at_risk = gpd.sjoin(parcels, floodplain_buffered, how="inner", predicate="intersects")

# Calculate risk score (vectorized - fast!)
at_risk['RISK_SCORE'] = at_risk.geometry.area * 0.1

# Export to GeoParquet (cloud-native format)
at_risk.to_parquet("parcels_at_risk.parquet")

Performance Comparison

ArcPy Version

47 minutes

10,000 parcels

GeoPandas Version

38 seconds

Same dataset, 74x faster

Common Translation Patterns

Buffer geometries

arcpy.Buffer_analysis
gdf.geometry.buffer()

Clip to boundary

arcpy.Clip_analysis
gpd.clip(gdf, mask)

Dissolve features

arcpy.Dissolve_management
gdf.dissolve(by='field')

Spatial join

arcpy.SpatialJoin_analysis
gpd.sjoin()

Reproject

arcpy.Project_management
gdf.to_crs()

Iterate features

arcpy.da.SearchCursor
gdf.iterrows()

Migration Strategy

  1. Start with high-impact workflows: Identify slow, frequently-run scripts
  2. Create side-by-side versions: Keep ArcPy version as reference during testing
  3. Test with real data: Don't trust toy examples—use production datasets
  4. Measure performance: Document speed improvements to build buy-in
  5. Train your team: Invest in Python/GeoPandas training upfront

Conclusion

The translation from ArcPy to GeoPandas isn't just possible—it's transformative. You'll write cleaner code, run workflows 10-100x faster, and eliminate Esri licensing costs.

At Axis Spatial, we've migrated 200+ ArcPy scripts to GeoPandas. The patterns are predictable, the performance gains are real, and your team will love the modern Python tooling.

Need Help Migrating Your ArcPy Scripts?

We've translated 200+ ArcPy scripts to GeoPandas. Let us help you migrate your workflows with proven patterns and performance benchmarks.

Discuss Your Migration