GeoPandas
Overview
GeoPandas is the core library for geospatial vector data processing in Python, extending pandas DataFrame with support for geometry columns. The core classes GeoDataFrame(inherits pandas.DataFrame) and GeoSeries(inherits pandas.Series) provide geometric operations through Shapely, coordinate system management through pyproj, and file IO through pyogrio/fiona.
Dependencies
| Core Dependencies |
Minimum Version |
Purpose |
pandas |
>= 2.2.0 |
DataFrame base class |
shapely |
>= 2.1.0 |
Geometry objects and operations (GEOS) |
pyproj |
>= 3.7.0 |
CRS handling and coordinate transformation |
pyogrio |
>= 0.8 |
Default file IO engine (GDAL/OGR) |
numpy |
>= 2 |
Array operations |
| Optional Dependencies |
Purpose |
matplotlib >= 3.9 |
Static map plotting .plot() |
folium |
Interactive maps .explore() |
mapclassify >= 2.7 |
Choropleth classification schemes |
fiona >= 1.8.21 |
Alternative file IO engine |
pyarrow >= 15.0.0 |
Parquet/Feather/Arrow support |
geopy |
Geocoding |
SQLAlchemy >= 2.0 + GeoAlchemy2 |
PostGIS read/write |
xyzservices |
Tile basemap services |
Global Configuration
import geopandas as gpd
gpd.options.display_precision # int|None, WKT 小数位数 (0-16)
gpd.options.io_engine # None|"pyogrio"|"fiona"
Core Class API
GeoDataFrame
gpd.GeoDataFrame(data=None, *args, geometry=None, crs=None, **kwargs)
Alternative Constructors
| Methods |
Signature |
GeoDataFrame.from_dict() |
(data, geometry=None, crs=None, **kwargs) |
GeoDataFrame.from_file() |
(filename, **kwargs) |
GeoDataFrame.from_features() |
(features, crs=None, columns=None) |
GeoDataFrame.from_postgis() |
(sql, con, geom_col="geom", crs=None, ...) |
GeoDataFrame.from_arrow() |
(table, geometry=None, to_pandas_kwargs=None) |
Core Attributes
| Attribute |
Description |
.geometry |
Active geometry column (GeoSeries) |
.crs |
Coordinate reference system (pyproj.CRS) |
.active_geometry_name |
Active geometry column name |
.total_bounds |
Overall bounds [minx, miny, maxx, maxy] |
.sindex |
Spatial index (STRtree) |
Geometry Management
| Methods |
Signature |
set_geometry() |
(col, drop=None, inplace=False, crs=None) |
rename_geometry() |
(col, inplace=False) |
set_crs() |
(crs=None, epsg=None, inplace=False, allow_override=False) |
to_crs() |
(crs=None, epsg=None, inplace=False) |
estimate_utm_crs() |
(datum_name="WGS 84") → pyproj.CRS |
Spatial operations (GeoDataFrame level)
| Methods |
Signature |
dissolve() |
(by=None, aggfunc="first", as_index=True, sort=True, method="unary", grid_size=None) |
explode() |
(column=None, ignore_index=False, index_parts=False) |
sjoin() |
(df, how="inner", predicate="intersects", lsuffix="left", rsuffix="right") |
sjoin_nearest() |
(right, how="inner", max_distance=None, distance_col=None, exclusive=False) |
clip() |
(mask, keep_geom_type=False, sort=False) |
overlay() |
(right, how="intersection", keep_geom_type=None, make_valid=True) |
Serialization Output
| Methods |
Description |
to_file(filename, driver=None, ...) |
Shapefile/GPKG/GeoJSON etc. |
to_parquet(path, compression="snappy", geometry_encoding="WKB", ...) |
GeoParquet |
to_feather(path, ...) |
Feather |
to_json(na="null", show_bbox=False, to_wgs84=False, ...) |
GeoJSON string |
to_geo_dict(...) |
Python dict (FeatureCollection) |
to_wkb(hex=False) |
DataFrame (WKB) |
to_wkt() |
DataFrame (WKT) |
to_postgis(name, con, schema=None, if_exists="fail", ...) |
PostGIS |
to_arrow(geometry_encoding="WKB", ...) |
Arrow Table |
GeoSeries
gpd.GeoSeries(data=None, index=None, crs=None, **kwargs)
Alternative Constructors
| Methods |
Signature |
GeoSeries.from_wkb() |
(data, index=None, crs=None, on_invalid="raise") |
GeoSeries.from_wkt() |
(data, index=None, crs=None, on_invalid="raise") |
GeoSeries.from_xy() |
(x, y, z=None, index=None, crs=None) |
GeoSeries.from_file() |
(filename, **kwargs) |
GeoSeries.from_arrow() |
(arr, **kwargs) |
Coordinate accessors (Point only)
.x, .y, .z, .m → pandas.Series
Shared geometry attributes and methods (GeoDataFrame.geometry / GeoSeries)
Scalar attributes → pandas.Series
| Attribute |
Return type |
Description |
.area |
float |
Area (in CRS units) |
.length |
float |
Length/perimeter |
.geom_type |
str |
Geometry type name |
.is_valid |
bool |
Geometry validity |
.is_empty |
bool |
Empty geometry check |
.is_simple |
bool |
Does not self-intersect |
.has_z |
bool |
Contains z coordinate |
.bounds |
DataFrame |
Per-geometry (minx, miny, maxx, maxy) |
.total_bounds |
array |
Overall (minx, miny, maxx, maxy) |
Geometry attributes → GeoSeries
.boundary, .centroid, .convex_hull, .envelope, .exterior
Unary geometry methods → GeoSeries
| Methods |
Signature |
buffer() |
(distance, quad_segs=None, **kwargs) |
simplify() |
(tolerance, preserve_topology=True) |
representative_point() |
() |
make_valid() |
(method="linework", keep_collapsed=True) |
normalize() |
() |
reverse() |
() |
segmentize() |
(max_segment_length) |
force_2d() |
() |
force_3d() |
(z=0) |
offset_curve() |
(distance, quad_segs=8, join_style="round", mitre_limit=5.0) |
minimum_rotated_rectangle() |
() |
minimum_bounding_circle() |
() |
maximum_inscribed_circle() |
(tolerance) |
concave_hull() |
(ratio, allow_holes) |
extract_unique_points() |
() |
remove_repeated_points() |
(tolerance=0.0) |
line_merge() |
(directed=False) |
set_precision() |
(grid_size, mode="valid_output") |
orient_polygons() |
(exterior_cw=False) |
constrained_delaunay_triangles() |
() |
Binary predicates → Series[bool]
contains(other, align=None) covers(other, align=None)
covered_by(other, align=None) crosses(other, align=None)
disjoint(other, align=None) intersects(other, align=None)
overlaps(other, align=None) touches(other, align=None)
within(other, align=None) contains_properly(other, align=None)
dwithin(other, distance, align=None)
geom_equals(other, align=None) geom_equals_exact(other, tolerance, align=None)
geom_equals_identical(other, align=None)
relate_pattern(other, pattern)
Binary geometry operations → GeoSeries
difference(other, align=None) intersection(other, align=None)
symmetric_difference(other, align=None) union(other, align=None)
shortest_line(other, align=None) snap(other, tolerance, align=None)
shared_paths(other, align=None)
Distance methods → Series[float]
distance(other, align=None)
hausdorff_distance(other, align=None)
frechet_distance(other, align=None)
Linear referencing
interpolate(distance, normalized=False) → GeoSeries (线上的点)
project(other, normalized=False) → Series[float] (沿线距离)
Aggregation methods
union_all(method="unary", grid_size=None) → 单个 Shapely geometry
intersection_all() → 单个 Shapely geometry
Affine transformations → GeoSeries
affine_transform(matrix)
translate(xoff=0.0, yoff=0.0, zoff=0.0)
rotate(angle, origin="center", use_radians=False)
scale(xfact=1.0, yfact=1.0, zfact=1.0, origin="center")
skew(xs=0.0, ys=0.0, origin="center", use_radians=False)
transform(transformation, include_z=False)
Other Methods
| Methods |
Returns |
Description |
clip_by_rect(xmin, ymin, xmax, ymax) |
GeoSeries |
Rectangle clipping |
relate(other, align=None) |
Series[str] |
DE-9IM matrix |
count_coordinates() |
Series[int] |
Number of coordinate pairs |
count_geometries() |
Series[int] |
Number of parts of a multi-part geometry |
get_coordinates() |
DataFrame |
Extract all coordinates |
hilbert_distance() |
Series |
Hilbert curve distance |
is_valid_reason() |
Series[str] |
Invalid reason |
Spatial index (SpatialIndex)
sindex = gdf.sindex
sindex.query(geometry, predicate=None, sort=False, distance=None)
sindex.nearest(geometry, max_distance=None, return_distance=False, return_all=True, exclusive=False)
sindex.valid_query_predicates # 有效谓词集合
sindex.size # 几何体数量
sindex.is_empty # 是否为空
IO module
File read/write
gpd.read_file(filename, bbox=None, mask=None, columns=None, rows=None, engine=None, **kwargs)
gdf.to_file(filename, driver=None, schema=None, index=None, **kwargs)
gpd.list_layers(filename) # → DataFrame (layer_name, geometry_type)
Supported file formats (extension → driver):
| Extension |
Driver |
.shp |
ESRI Shapefile |
.json, .geojson |
GeoJSON |
.geojsonl, .geojsons |
GeoJSONSeq |
.gpkg |
GPKG (GeoPackage) |
.gml, .xml |
GML |
.gpx |
GPX |
.csv |
CSV |
.fgb |
FlatGeobuf |
.dxf |
DXF |
.tab, .mif, .mid |
MapInfo File |
Engine options:"pyogrio"(default) or "fiona". Supports all GDAL/OGR drivers.
GeoParquet / Feather
gpd.read_parquet(path, columns=None, storage_options=None, bbox=None, **kwargs)
gpd.read_feather(path, columns=None, **kwargs)
gdf.to_parquet(path, compression="snappy", geometry_encoding="WKB",
write_covering_bbox=False, schema_version=None, **kwargs)
gdf.to_feather(path, compression=None, schema_version=None, **kwargs)
GeoParquet specification version:"1.0.0", "1.1.0" etc. Geometry encoding:"WKB"(default),"geoarrow"。
Arrow (GeoArrow)
gdf.to_arrow(geometry_encoding="WKB", interleaved=True, include_z=None)
GeoDataFrame.from_arrow(table, geometry=None, to_pandas_kwargs=None)
gs.to_arrow(geometry_encoding="WKB", interleaved=True, include_z=None)
GeoSeries.from_arrow(arr, **kwargs)
PostGIS
gpd.read_postgis(sql, con, geom_col="geom", crs=None, index_col=None, ...)
gdf.to_postgis(name, con, schema=None, if_exists="fail", index=False, ...)
Requires:SQLAlchemy + GeoAlchemy2 + psycopg/psycopg2。
WKB/WKT and coordinate construction
gpd.points_from_xy(x, y, z=None, crs=None) # → GeometryArray
GeoSeries.from_wkb(data, crs=None, on_invalid="raise")
GeoSeries.from_wkt(data, crs=None, on_invalid="raise")
GeoSeries.from_xy(x, y, z=None, crs=None)
Tools module (tools)
Spatial join
gpd.sjoin(left_df, right_df, how="inner", predicate="intersects",
lsuffix="left", rsuffix="right", distance=None, on_attribute=None)
how: "inner", "left", "right"
predicate: "intersects", "within", "contains", "contains_properly", "overlaps", "crosses", "touches", "covers", "covered_by", "dwithin"
on_attribute: additional non-spatial join filter column names
Nearest neighbor spatial join
gpd.sjoin_nearest(left_df, right_df, how="inner", max_distance=None,
distance_col=None, exclusive=False)
Spatial overlay
gpd.overlay(df1, df2, how="intersection", keep_geom_type=None, make_valid=True)
how: "intersection", "union", "identity", "symmetric_difference", "difference"
Clip
gpd.clip(gdf, mask, keep_geom_type=False, sort=False)
mask: GeoDataFrame, GeoSeries, (Multi)Polygon, or (minx, miny, maxx, maxy) tuple
Geocoding
gpd.tools.geocode(strings, provider=None, **kwargs) # → GeoDataFrame
gpd.tools.reverse_geocode(points, provider=None, **kwargs) # → GeoDataFrame
Requires geopy. Default provider: "photon". Returns a GeoDataFrame containing geometry and address columns.
Other tools
gpd.tools.collect(x, multi=False) # 收集几何体为多部件
gpd.show_versions() # 打印依赖版本信息
Visualization
Static plotting (matplotlib)
# GeoSeries
gs.plot(cmap=None, color=None, ax=None, figsize=None, aspect="auto")
# GeoDataFrame — 支持分级设色
gdf.plot(column=None, cmap=None, color=None, ax=None, figsize=None,
legend=False, scheme=None, k=5, vmin=None, vmax=None,
markersize=None, categories=None, classification_kwds=None,
missing_kwds=None, legend_kwds=None, **style_kwds)
scheme parameter requires mapclassify, supports "quantiles", "equal_interval", "fisher_jenks" and other classification schemes.
Interactive maps (folium/leaflet)
gdf.explore(column=None, cmap=None, color=None, m=None,
tiles="OpenStreetMap", tooltip=True, popup=False,
categorical=False, legend=True, scheme=None, k=5,
marker_type=None, marker_kwds={}, style_kwds={},
tooltip_kwds={}, popup_kwds={}, legend_kwds={}, **kwargs)
Common patterns quick reference
Create GeoDataFrame
import geopandas as gpd
from shapely.geometry import Point, Polygon, LineString
# 从文件读取
gdf = gpd.read_file("data.shp")
gdf = gpd.read_file("data.gpkg", layer="my_layer")
gdf = gpd.read_parquet("data.parquet")
# 从字典创建
gdf = gpd.GeoDataFrame(
{"name": ["A", "B"], "value": [1, 2],
"geometry": [Point(0, 0), Point(1, 1)]},
crs="EPSG:4326"
)
# 从 pandas DataFrame + 坐标列
import pandas as pd
df = pd.DataFrame({"lon": [116.4, 121.5], "lat": [39.9, 31.2], "city": ["北京", "上海"]})
gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.lon, df.lat), crs="EPSG:4326")
# 从 WKT/WKB
gs = gpd.GeoSeries.from_wkt(["POINT (0 0)", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"], crs=4326)
# 从 GeoJSON FeatureCollection
gdf = gpd.GeoDataFrame.from_features(feature_collection["features"], crs="EPSG:4326")
CRS operations
gdf = gdf.set_crs("EPSG:4326") # 赋值 CRS(数据本身不变)
gdf = gdf.to_crs("EPSG:3857") # 投影变换(坐标重算)
gdf = gdf.to_crs(epsg=32650) # 按 EPSG 代码投影
utm = gdf.estimate_utm_crs() # 自动检测 UTM 分区
Note:set_crs() only marks the CRS without transforming coordinates;to_crs() actually performs coordinate transformation. Before area/distance calculations, be sure to project to an appropriate projected coordinate system (such as UTM).
Spatial analysis
# 空间连接
joined = gpd.sjoin(points_gdf, polygons_gdf, how="inner", predicate="within")
# 最近邻连接
nearest = gpd.sjoin_nearest(gdf1, gdf2, max_distance=1000, distance_col="dist_m")
# 空间叠加
result = gpd.overlay(gdf1, gdf2, how="intersection")
# 裁剪
clipped = gpd.clip(gdf, mask_polygon)
clipped = gpd.clip(gdf, (xmin, ymin, xmax, ymax)) # 矩形快速裁剪
# 按属性融合
dissolved = gdf.dissolve(by="province", aggfunc="sum")
# 多部件炸开
exploded = gdf.explode(index_parts=False)
# 缓冲区、简化
gdf["buffer_100m"] = gdf.geometry.buffer(100)
gdf["simplified"] = gdf.geometry.simplify(tolerance=50)
Geometric predicates and measures
gdf.geometry.intersects(other_geom) # Series[bool]
gdf.geometry.contains(other_geom) # Series[bool]
gdf.geometry.within(other_geom) # Series[bool]
gdf.geometry.distance(other_geom) # Series[float]
gdf.geometry.area # Series[float]
gdf.geometry.length # Series[float]
gdf.geometry.centroid # GeoSeries
gdf.geometry.bounds # DataFrame
gdf.total_bounds # array [minx, miny, maxx, maxy]
Data output
gdf.to_file("output.gpkg", driver="GPKG", layer="result")
gdf.to_file("output.geojson", driver="GeoJSON")
gdf.to_parquet("output.parquet")
geojson_str = gdf.to_json()
gdf.to_postgis("table_name", engine, if_exists="replace")
Visualization
# 静态地图
ax = gdf.plot(column="population", cmap="YlOrRd", legend=True, figsize=(12, 8))
# 多图层叠加
ax = base_gdf.plot(color="lightgrey", figsize=(12, 8))
points_gdf.plot(ax=ax, color="red", markersize=5)
# 交互式地图
m = gdf.explore(column="population", cmap="viridis",
tooltip=["name", "population"], tiles="CartoDB positron")
Common errors and solutions
| Problem |
Cause |
Solution |
| Area/distance is 0 or extremely small |
CRS is a geographic coordinate system (degrees) |
gdf.to_crs(gdf.estimate_utm_crs()) compute after |
sjoin returns no results |
The CRS of the two GDFs is inconsistent |
Unify CRS:gdf2 = gdf2.to_crs(gdf1.crs) |
ValueError: GeoDataFrame does not support multiple columns using the geometry column name 'geometry' |
Multiple columns named geometry |
Rename conflicting columns or use set_geometry() to specify |
to_file attribute is truncated |
Shapefile field name limit of 10 characters |
Use GeoPackage (.gpkg) or GeoJSON |
set_crs vs to_crs confusion |
set_crs only assigns without transforming |
To transform an existing CRS, use to_crs(); to mark a missing CRS, use set_crs() |
| Shapefile does not support null values/datetime |
Shapefile format limitation |
Use the GeoPackage format |
DriverError cannot be written |
Driver not supported or file is in use |
Check driver name spelling, close already opened files |
align=True causes unexpected NaN |
Binary operations align by index by default |
Pass align=False or reset the index |
Performance tips
- GeoParquet is 5-10 times faster for reading and writing than Shapefile/GeoJSON; prefer using it
- Spatial indexing
.sindex automatically builds an STRtree,query() and nearest() can greatly accelerate spatial queries
- Rectangular clipping
clip_by_rect() or clip(gdf, (xmin,ymin,xmax,ymax)) is faster than polygon clipping
pyogrio engine(default) is 2-5 times faster than fiona 2-5x faster
- For large datasets, use
read_file(rows=slice(0, 1000)) to preview first
dissolve() Use method="coverage" is faster for polygons known to be non-overlapping
- Avoid row-by-row looping over geometry operations; use vectorized methods (such as
buffer(), intersection() , etc.)