mrkeyoor.com_
Sun 20 Sept 11:42 UTC
PyPIDataupdated 20 Sept 2026

shapely review

Shapely wraps GEOS for planar geometry in Python. Points, lines, polygons, and collections support predicates, overlays, buffering, measurement, simplification, repair, serialization, and spatial indexing. Call methods on one geometry or use module-level NumPy ufuncs over arrays; GEOS performs the heavy work and Shapely releases the GIL for many operations. Version 2.1.2 adds wheels for Python 3.14 and keeps GEOS 3.13.1 bundled in those wheels. The package does not track coordinate reference systems or provide dataset file I/O. Distances and areas use the numeric units of the input coordinates, so longitude and latitude need projection or geodesic handling elsewhere.

Verdict

Use Shapely for in-memory planar geometry and vectorized GEOS operations. Reach for GeoPandas when geometry belongs in a table, and use pyproj before trusting measurements derived from geographic coordinates.

We installed it

Lab card: what happened when we installed shapelyScreenshot of shapely documentation
Install✓ · 1s2 packages on disk · 68 MB
Importimport shapely in 0.38s · compiled extensions · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does shapely install cleanly?

Yes. In a fresh container with an empty cache, pip install shapely finished in 1 seconds, leaving 2 packages and 68 MB on disk. pip-audit reported no known vulnerabilities.

What does shapely need to run?

Python >=3.10, and a platform wheel with compiled extensions. In our run import shapely succeeded in 0.38s.

shapely or geopandas: which should you use?

geopandas: Use it when geometry rows also need attribute columns, CRS metadata, file I/O, and spatial joins. Use Shapely for in-memory planar geometry and vectorized GEOS operations.

When should you not use shapely?

You need geodesic distance, area, or buffering on longitude and latitude. Shapely treats those coordinates as a flat Cartesian plane and knows no CRS.

API stability4/5The 2.x object and ufunc APIs are consistent across current releases, and minor updates tend to add GEOS-backed operations or platform support. The migration from 1.8 was substantial: geometries became immutable, multi-part iteration moved to `.geoms`, and several old array and ctypes paths disappeared. Projects already on 2.x face ordinary deprecation work, while 1.x applications need an explicit migration and regression tests for result types.
Docs4/5The hosted manual documents geometry classes, vectorized functions, broadcasting, STRtree, serialization, multithreading, installation, and the 1.8-to-2.0 migration. Function pages commonly state required GEOS versions and show array behavior. The planar and unit-free model is documented, but it remains easy for a newcomer to miss because CRS handling belongs to other packages. Examples also cannot replace domain choices about a suitable projection.
Maintenance4/5Version 2.1.2 was published on 2025-09-24 with Python 3.14 wheels, and the repository was still receiving changes on 2026-08-20. GitHub reports 236 open issues and pull requests. Releases are less frequent than the main-branch activity, which can delay fixes reaching PyPI, but the project continues to track new Python, NumPy, and GEOS combinations and remains foundational to the Python geospatial stack.
Ecosystem5/5The supplied snapshot records roughly 18.7 million weekly downloads. GeoPandas, pyogrio, database adapters, and many GIS libraries accept or return Shapely geometry objects, while the geo interface supports exchange without a hard dependency. GEOS also aligns core predicates and overlays with other systems built on the same engine. CRS, dataset I/O, raster analysis, and geodesic calculations remain separate layers, which keeps Shapely focused but requires companion packages.

Use it if

  • Your code needs GEOS predicates and overlay operations on points, lines, polygons, or geometry collections.
  • Arrays of geometries should use broadcasting ufuncs instead of a Python loop around scalar methods.
  • Candidate searches need an immutable STRtree before an exact contains, intersects, nearest, or distance test.
  • A library must exchange geometry objects with GeoPandas, pyogrio, PostGIS adapters, or tools using the geo interface.
Skip it if

Setup reality

We installed Shapely 2.1.2 in a clean Python 3.12 Bookworm container. Installation succeeded in 1 second, left 2 packages, and used 68 MB. Its metadata declares 9 direct dependencies, requires Python 3.10 or newer, and the distribution contains compiled .so extensions. It uses the BSD 3-Clause license and did not ship py.typed in our environment. import shapely completed in 0.38 seconds. pip-audit reported 0 known vulnerabilities. The large disk footprint relative to the package count comes from native geometry code and NumPy.

Official wheels bundle GEOS, and 2.1.2 added Python 3.14 wheels built with GEOS 3.13.1. Check shapely.geos_version when a function has a minimum GEOS requirement. Source installs need a compatible GEOS development library and compiler. Mixing a pip wheel with conda or system geospatial libraries can load different GEOS copies into one process, so keep Shapely, GeoPandas, GDAL-facing packages, and their native dependencies on one packaging channel when possible.

Shapely never assigns a CRS or changes units. Project longitude and latitude with pyproj before planar measurement, using an appropriate local or equal-area CRS for the question. Geometry constructors also allow invalid topology; call is_valid and inspect explain_validity at ingestion boundaries. make_valid may return a MultiPolygon or GeometryCollection, which downstream schemas must accept. Multi-part geometries are immutable in 2.x and expose parts through .geoms. STRtree indexes the geometry array it was built from and returns indices, so keep that source array stable for the tree's lifetime.

Patterns

Construct common geometry types create-geometries

from shapely import Point, LineString, Polygon, box, MultiPolygon

p = Point(2.0, 3.0)
line = LineString([(0, 0), (1, 1), (2, 0)])
square = box(0, 0, 10, 10)                      # minx, miny, maxx, maxy
ring_with_hole = Polygon(
    shell=[(0, 0), (0, 10), (10, 10), (10, 0)],
    holes=[[(2, 2), (2, 4), (4, 4), (4, 2)]],
)
print(square.area, ring_with_hole.area)

Geometry objects are immutable in 2.x. Coordinates carry no CRS or unit metadata, and polygon holes need rings that make sense within the shell.

Choose the right spatial predicate spatial-predicates

square.contains(p)      # strictly inside, boundary excluded
square.covers(p)        # inside or on the boundary
square.intersects(line)
square.touches(line)    # boundaries meet, interiors do not
square.disjoint(line)

# full DE-9IM when the named predicates are not precise enough
square.relate(line)                 # e.g. '1010F0212'
square.relate_pattern(line, 'T*F**F***')

contains excludes a geometry that lies only on the boundary, while covers includes it. Use relate patterns when a named predicate does not express the required topology.

Broadcast operations over geometry arrays vectorized-operations

import numpy as np
import shapely

points = shapely.points(np.random.rand(1_000_000, 2) * 100)
region = shapely.box(10, 10, 20, 20)

inside = shapely.contains(region, points)      # bool array, loop in C
areas = shapely.area(shapely.buffer(points[:1000], 0.5))
merged = shapely.union_all(shapely.buffer(points[:1000], 0.5))

Module-level functions follow NumPy broadcasting and keep the element loop in C. `shapely.points` expects coordinate arrays, which avoids constructing each Point in Python.

Query an STRtree by index spatial-index

from shapely import STRtree

tree = STRtree(parcels)                     # array of polygons

# which parcels intersect each building?
building_idx, parcel_idx = tree.query(buildings, predicate="intersects")

# nearest parcel to each building, with distance
idx, dist = tree.query_nearest(buildings, return_distance=True)

Results are integer positions into the input arrays. A query without a predicate returns bounding-box candidates; provide a predicate for exact GEOS filtering.

Prepare a repeated predicate target prepare-geometry

import shapely

shapely.prepare(coastline)          # mutates in place, builds an internal index
hits = shapely.contains(coastline, points)
shapely.destroy_prepared(coastline)

prepare changes internal state in place and helps supported predicates that reuse the same left-hand geometry. It does not accelerate overlay operations.

Validate and repair topology repair-invalid-geometry

from shapely import is_valid, make_valid, set_precision
from shapely.validation import explain_validity

bowtie = Polygon([(0, 0), (2, 2), (2, 0), (0, 2)])
print(is_valid(bowtie))          # False
print(explain_validity(bowtie))  # 'Self-intersection[1 1]'

fixed = make_valid(bowtie)       # returns a MultiPolygon here
snapped = set_precision(fixed, 0.001)

make_valid may return a different geometry family, including a collection. Validate the repaired type before storage; set_precision changes coordinates and should use a domain-appropriate grid.

Serialize individual geometries read-write-formats

import shapely

wkt = shapely.to_wkt(square, rounding_precision=6)
wkb = shapely.to_wkb(square, hex=True)
gj = shapely.to_geojson(square)

back = shapely.from_wkt(wkt)
from_db = shapely.from_wkb(row["geom"])
lenient = shapely.from_wkt(bad_text, on_invalid="warn")

These functions handle geometry values rather than datasets. Set WKT precision deliberately, and choose an on_invalid policy before parsing untrusted bulk input.

Project before planar measurement reproject-coordinates

from pyproj import Transformer
from shapely.ops import transform

# WGS84 lon/lat to Web Mercator metres
to_metres = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
projected = transform(to_metres.transform, lonlat_geom)
print(projected.length)   # metres, not degrees

always_xy keeps longitude in the x position expected by Shapely. EPSG:3857 is only an illustration; choose a local or equal-area CRS when accuracy matters.

Simplify or densify in coordinate units measure-and-simplify

import shapely

shapely.length(road)
shapely.area(parcel)
shapely.distance(parcel, well)

small = shapely.simplify(coastline, tolerance=50, preserve_topology=True)
dense = shapely.segmentize(road, max_segment_length=10)
hull = shapely.concave_hull(points_multi, ratio=0.3)   # GEOS 3.11+

Every distance argument uses the current coordinate units. Topology-preserving simplification costs more but avoids some invalid outputs; segmentize adds vertices before curved reprojection.

Access multi-part members and coordinates access-parts-and-coordinates

for poly in multi.geoms:            # NOT: for poly in multi
    print(poly.exterior.coords[:])
    for hole in poly.interiors:
        print(len(hole.coords))

import shapely
coords = shapely.get_coordinates(geoms_array)     # (N, 2) float array
parts = shapely.get_parts(geoms_array)            # explode multiparts

Use `.geoms` for multi-part members in 2.x. `get_coordinates` flattens input boundaries, so request source indices when coordinates must be mapped back to their geometries.

Apply overlay and buffer operations overlay-operations

import shapely

union = shapely.union_all(parcels)                # one call, not a fold
clipped = shapely.intersection(parcel, flood_zone)
remaining = shapely.difference(parcel, easement)
ring = shapely.buffer(road, 25).difference(shapely.buffer(road, 15))
edge = shapely.buffer(site, -5, join_style="mitre")  # negative shrinks

Use union_all for a collection rather than folding binary unions in Python. Negative buffers can produce empty results, and overlays may return collections when dimensions mix.

Locate positions along a line linear-referencing

from shapely.ops import nearest_points, substring, split

d = road.project(incident)              # distance along the line
snapped = road.interpolate(d)           # point at that distance
half = road.interpolate(0.5, normalized=True)

a, b = nearest_points(road, incident)
segment = substring(road, 100, 250)
pieces = split(road, crossing_line)

project returns distance along the line, while interpolate returns a point at that distance. Normalized mode uses a fraction; split still requires an actual intersection.

Alternatives

PackageRegistryPick it when
geopandasPyPIUse it when geometry rows also need attribute columns, CRS metadata, file I/O, and spatial joins.
pyprojPyPIUse it for CRS transformations and ellipsoidal distance or area calculations before or alongside planar operations.
pyclipperPyPIUse it for integer-coordinate polygon clipping and offsetting when the Clipper algorithm matches the domain.
trimeshPyPIUse it for three-dimensional meshes, scenes, and solids; Shapely's operations are planar.

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.