Library / SDK
shapely/shapely avatar
shapely/shapely

Shapely: one geometry library wrapped two ways, and a GIL release with teeth

Manipulation and analysis of geometric objects

4,542 stars648 forksPythonBSD-3-Clause

At a glance

What is it?
Shapely wraps GEOS and exposes it twice: a scalar Geometry interface and NumPy ufuncs for arrays, which is why the same buffer call can be written as a loop or as one vectorised expression. The parts worth reading closely are the warnings, because the GIL release that makes threading work and the array mutation rule that makes it crash are documented in adjacent paragraphs.
Who is it for?
Fit if you are doing planar geometry in Python and want the same code path for one shape and for ten thousand. Skip it if you need a data format reader, a coordinate system handler, or a hard guarantee that sharing arrays across threads is safe.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • shapely/shapely4,542 ★ · +28
Star history
Repository2026-09-232026-11-14
shapely/shapely4.5k4.5k

28 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

GEOS does the work, Shapely wraps it two ways

Shapely is a BSD-licensed Python package for manipulation and analysis of planar geometric objects in the Cartesian plane. The geometry itself comes from GEOS, the open-source library described as the engine of PostGIS and a port of JTS, so Shapely is a binding and a convenience layer rather than an implementation. What it adds is two access patterns. The first is a feature-rich `Geometry` interface for singular, scalar geometries, which is what most interactive work uses. The second is higher-performance NumPy ufuncs for operations on arrays of geometries, which is what batch work needs. One scoping sentence matters for planning: Shapely is not primarily focused on data serialization formats or coordinate systems, but can be readily integrated with packages that are. So if your requirement is a GeoJSON reader or a projection engine, that is a companion library decision rather than something Shapely will absorb.

A ufunc is why there is a vectorized path

The README explains the term before using it, which is fair because the performance claim depends on it. A universal function, or ufunc, operates on n-dimensional arrays on an element-by-element basis and supports array broadcasting, and the underlying `for` loops are implemented in C to reduce the overhead of the Python interpreter. The comparison the file then makes is between a manual loop and the vectorized interface, using a NumPy array of points against a box:

python
>>> import shapely
>>> import numpy as np
>>> geoms = np.array([Point(0, 0), Point(1, 1), Point(2, 2)])
>>> polygon = shapely.box(0, 0, 2, 2)

>>> shapely.contains(polygon, geoms)
array([False,  True, False])

Note the argument order, with the polygon first and the array second, and note the result: one call, one array of booleans, one entry per input geometry. Broadcasting means the scalar polygon is accepted where an array of the same shape would be, which is what keeps the call site free of loops. The practical consequence for your own code is that a Python `for` loop over geometries is the slow path, not a stylistic choice.

Releasing the GIL is not free of caveats

Two paragraphs sit next to each other and both matter. The first is the upside: Shapely functions generally support multithreading by releasing the Global Interpreter Lock during execution, and since Python's GIL normally prevents multiple threads computing at the same time, Shapely releases that constraint internally so the heavy lifting done by GEOS can proceed concurrently from a single Python process. The second is the warning, and it is specific rather than general. When sharing a NumPy array of geometries between threads, extreme care must be taken to avoid thread safety issues when mutating arrays, which holds for any NumPy array but even more so for arrays of Shapely geometries. The mechanism behind the crash is given: overwriting a geometry in an array deallocates the old geometry, and if another thread is operating on that geometry at the same time, interpreter crashes will occur. So parallel reads across a shared array are the intended use, and parallel writes into it are the failure mode.

The canonical first call is a buffer

The usage section opens with the example almost every reader has already seen elsewhere, and it is worth reading because the return value teaches the data model:

python
>>> from shapely import Point
>>> patch = Point(0.0, 0.0).buffer(10.0)
>>> patch
<POLYGON ((10 0, 9.952 -0.98, 9.808 -1.951, 9.569 -2.903, 9.239 -3.827, 8.81...>
>>> patch.area
313.6548490545941

Three things follow from four lines. A point becomes a polygon, because buffering a zero-dimensional shape by a distance yields an approximation of a disc with many vertices, which is why the coordinate list is long and truncated in the display. The result's own area is not the mathematical value for a radius of ten, since 313.65 is smaller than the roughly 314.16 a perfect circle would give, and that gap is the polygon approximation. And `area` is an attribute-style access on the geometry rather than a function call, so the object returned by `buffer` is not a raw tuple of coordinates but a geometry with operations attached.

Python 3.11, GEOS 3.10 and NumPy 1.26 are the floor

Shapely 2.2 requires Python 3.11 or newer, GEOS 3.10 or newer, and NumPy 1.26 or newer, and those three numbers are the entire requirement list. The packaging metadata agrees and goes further on one axis: the classifiers declare Python 3.11, 3.12, 3.13, 3.14 and 3.15, and the supported operating systems are Unix, macOS and Microsoft Windows, with a development status of 5, Production/Stable. Installation is recommended from a built distribution rather than a source build, either pip or conda:

bash
$ pip install shapely
# or using conda
$ conda install shapely --channel conda-forge

The channel matters in the conda line, since the project is published on conda-forge. The advanced and platform-specific installation instructions are not in the README but in the installation page of the documentation, which is where anyone building from source or on an unusual platform should be sent.

meson-python builds it, and three version numbers must agree

Shapely is a compiled project with a compiled extension, so its build system is not setuptools. The manifest declares `mesonpy` (from meson-python) as the build backend, with Cython, `meson-python>=0.15.0,!=0.22.0` and `numpy>=1.26,<3` in the build requirements, and the version is dynamic rather than written into the file, which is why `versioneer.py` and `generate_version.py` sit at the repository root. The dependency groups are split by purpose rather than lumped together: a build group with Cython, `meson>=1.2.1`, meson-python, ninja and NumPy, a tests group with pytest, pytest-cov and scipy-doctest, a docs group pinning `numpydoc==1.1.*` alongside matplotlib and sphinx, and a dev group that includes pre-commit and pulls in the build and tests groups. Then there are the comments that matter most, each one saying to keep something in sync: `requires-python` with `min_python_version` in meson.build, the NumPy runtime dependency with `min_numpy_runtime_version` in meson.build, and the build-system requirements with a subset of the build dependency group. Change one and not the others and you get a build that disagrees with its own metadata.

Serialization is delegated, through wkt, wkb and dicts

Shapely does not read or write data files, but it can serialize and deserialize through several well-known formats and protocols. The `shapely.wkb` and `shapely.wkt` modules provide dumpers and loaders inspired by Python's pickle module, and the round trip looks like this:

python
>>> from shapely.wkt import dumps, loads
>>> dumps(loads('POINT (0 0)'))
'POINT (0.0000000000000000 0.0000000000000000)'

The output is worth pausing on: the coordinate is printed with sixteen decimal places, so a WKT string is not byte-identical after a round trip even though the geometry is. If you are hashing text, diffing fixtures or comparing strings, that formatting is a trap. The second integration path is GeoJSON-like dictionaries, using `mapping` and `shape` from `shapely.geometry`:

python
>>> import json
>>> from shapely.geometry import mapping, shape
>>> s = shape(json.loads('{"type": "Point", "coordinates": [0.0, 0.0]}'))
>>> print(json.dumps(mapping(s)))
{"type": "Point", "coordinates": [0.0, 0.0]}

That is the bridge to the rest of the Python GIS stack, and it is the piece that makes Shapely usable inside a pipeline that speaks dicts rather than strings.

The latest tag is a release candidate

The release history starts with 2.2.0 dated 2026-10-07, ahead of 2.2.0rc1 from 2026-09-21 and 2.1.2 from 2025-09-24, so the version named in the requirements section, 2.2, is now a final tag. The last push landed on 2026-10-27 and the repository is not archived. The tree confirms how much of this project is native code rather than Python: a `src` directory holding the extension, `meson.build` at the root, and a `.clang-format` file for the C sources. Performance work is first class as well, with a `benchmarks/` directory and `asv.conf.json` for the asv runner. Documentation lives in `docs/` with `.readthedocs.yml`, and the repository carries `CHANGES.txt`, `CITATION.cff`, `CREDITS.txt`, `.pre-commit-config.yaml`, `.mailmap`, `ci/`, `docker/` and a CODE_OF_CONDUCT. Questions go to GIS StackExchange under the shapely tag and bugs to the issue tracker. Shapely is BSD 3-Clause, and GEOS itself is under the LGPL 2.1, a licensing detail worth keeping in mind for redistribution.

Editorial conclusion

Fit if you are doing planar geometry in Python and want the same code path for one shape and for ten thousand. Skip it if you need a data format reader, a coordinate system handler, or a hard guarantee that sharing arrays across threads is safe. Before you upgrade, check whether you are moving to 2.2, confirm your GEOS and NumPy versions clear the stated floor, and read the array mutation warning before parallelising anything.

Frequently asked questions

what is shapely

Shapely is a BSD-licensed Python package for manipulation and analysis of planar geometric objects in the Cartesian plane. It wraps the GEOS library, which is the engine of PostGIS and a port of JTS, and offers both a scalar Geometry interface and NumPy ufuncs for arrays of geometries.

how to install shapely

Install a built distribution rather than building from source: `pip install shapely`, or `conda install shapely --channel conda-forge`. Shapely 2.2 requires Python 3.11 or newer, GEOS 3.10 or newer, and NumPy 1.26 or newer.

how to use shapely

The canonical example builds an approximately circular patch by buffering a point with `Point(0.0, 0.0).buffer(10.0)`, then reads `patch.area`, which returns 313.6548490545941 rather than the ideal circle value because the result is an approximating polygon.

how to use shapely python

Use the vectorized ufunc interface instead of a manual loop. Build a NumPy array of geometries, pass it with a scalar geometry as the first argument, for example `shapely.contains(polygon, geoms)`, and you get an array of results with one entry per input geometry, with broadcasting handling the scalar argument.

how to install shapely in anaconda

The project is published on conda-forge, so install it from that channel with `conda install shapely --channel conda-forge`. Conda is listed alongside pip as one of the recommended built distributions.

Official sources

  1. License: BSD-3-Clause
  2. Project website
  3. README
  4. Releases
  5. shapely/shapely on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shapely-shapely.svg)](https://hysenlabs.com/projects/shapely-shapely)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/shapely-shapely.svg)](https://hysenlabs.com/projects/shapely-shapely)

Was this useful?