[Git][debian-gis-team/python-shapely][upstream] New upstream version 2.2.0
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Wed Oct 7 18:13:27 BST 2026
Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-shapely
Commits:
28163146 by Bas Couwenberg at 2026-10-07T19:04:09+02:00
New upstream version 2.2.0
- - - - -
19 changed files:
- CHANGES.txt
- CITATION.cff
- docs/conf.py
- docs/environment.yml
- docs/installation.rst
- docs/manual.rst
- docs/release/2.x.rst
- pyproject.toml
- − setup.cfg
- src/shapely/coordinates.py
- src/shapely/lib/geos.c
- src/shapely/lib/geos.h
- src/shapely/lib/geos_funcs_O_b.c
- src/shapely/linear.py
- src/shapely/ops.py
- src/shapely/tests/legacy/test_transform.py
- src/shapely/tests/test_coordinates.py
- src/shapely/tests/test_linear.py
- src/shapely/tests/test_misc.py
Changes:
=====================================
CHANGES.txt
=====================================
@@ -1,7 +1,7 @@
Changes
=======
-2.2.0 (unreleased)
+2.2.0 (2026-10-07)
------------------
Shapely 2.2.0 is a feature release with various new functions,
=====================================
CITATION.cff
=====================================
@@ -2,8 +2,8 @@ cff-version: 1.2.0
message: "Please cite this software using these metadata."
type: software
title: Shapely
-version: "2.1.1"
-date-released: "2025-05-19"
+version: "2.2.0"
+date-released: "2026-10-07"
doi: 10.5281/zenodo.5597138
abstract: "Manipulation and analysis of geometric objects in the Cartesian plane."
repository-artifact: https://pypi.org/project/Shapely
=====================================
docs/conf.py
=====================================
@@ -12,7 +12,7 @@ os.environ["SPHINX_DOC_BUILD"] = "1"
# -- Project information -----------------------------------------------------
project = 'Shapely'
-copyright = '2011-2025, Sean Gillies and Shapely contributors'
+copyright = '2011-2026, Sean Gillies and Shapely contributors'
# The full version, including alpha/beta/rc tags.
import shapely
=====================================
docs/environment.yml
=====================================
@@ -5,12 +5,9 @@ dependencies:
- python=3.12
- geos=3.13
- numpy
- - cython
- sphinx-book-theme
- sphinx-remove-toctrees
- numpydoc=1.1
- matplotlib
- pip
- pkg-config
- - meson
- - meson-python
=====================================
docs/installation.rst
=====================================
@@ -115,13 +115,13 @@ modules happens in editable mode, add ``-Ceditable-verbose=true`` to the command
Conda environments
^^^^^^^^^^^^^^^^^^
-Development within conda environments requires additional packages installed
-with the ``conda`` command, including pre-built distributions of GEOS.
+When developing within a conda environment, it is recommended to install the
+development dependencies using conda, including a pre-built distribution of GEOS.
For example:
.. code-block:: console
- $ conda create -n shapely-dev cython meson meson-python pkg-config ninja numpy geos pip pytest
+ $ conda create -n shapely-dev c-compiler cython meson meson-python pkg-config ninja numpy geos pip pytest
Activate the environment and install shapely in editable mode using ``pip``:
=====================================
docs/manual.rst
=====================================
@@ -318,7 +318,7 @@ Coordinate values are accessed via `coords`, `x`, `y`, `z`, and `m` properties.
>>> point.y
0.0
-Coordinates may also be sliced. `New in version 1.2.14`.
+Coordinates may also be sliced.
.. code-block:: pycon
@@ -386,7 +386,7 @@ The defining coordinate values are accessed via the `coords` property.
>>> list(line.coords)
[(0.0, 0.0), (1.0, 1.0)]
-Coordinates may also be sliced. `New in version 1.2.14`.
+Coordinates may also be sliced.
.. code-block:: pycon
@@ -566,8 +566,6 @@ the :func:`shapely.geometry.box()` function.
Makes a rectangular polygon from the provided bounding box values, with
counter-clockwise order by default.
- `New in version 1.2.9`.
-
For example:
.. code-block:: pycon
@@ -592,8 +590,6 @@ To obtain a polygon with a known orientation, use
oriented counter-clockwise and the interior rings (holes) will be oriented
clockwise.
- `New in version 1.2.10`.
-
.. _collections:
Collections
@@ -979,8 +975,6 @@ example will be shown for each.
region with positive signed area). This method applies to `LinearRing`
objects only.
- `New in version 1.2.10`.
-
.. code-block:: pycon
>>> LinearRing([(1,0), (1,1), (0,0)]).is_ccw
@@ -1854,8 +1848,6 @@ transform (`rotate`, `scale`, etc.). The functions can be used with all
geometry types (except `GeometryCollection`), and 3D types are either
preserved or supported by 3D affine transformations.
-`New in version 1.2.17`.
-
.. function:: shapely.affinity.affine_transform(geom, matrix)
Returns a transformed geometry using an affine transformation matrix.
@@ -2060,85 +2052,159 @@ Other Transformations
Shapely supports map projections and other arbitrary transformations of
geometric objects.
-.. function:: shapely.ops.transform(func, geom)
+.. function:: shapely.transform(geom, func, include_z=False, interleaved=True)
+ :no-index:
- Applies `func` to all coordinates of `geom` and returns a new
- geometry of the same type from the transformed coordinates.
+ Apply a transformation `func` to all the coordinates of `geom`, and return
+ a new geometry of the same type from the transformed coordinates.
- `func` maps x, y, and optionally z to output xp, yp, zp. The input
- parameters may be iterable types like lists or arrays or single values.
- The output shall be of the same type: scalars in, scalars out;
- lists in, lists out.
+ By default, `func` maps a single 2D array of coordinates to a new 2D array
+ of transformed coordinates (``func([[x1, y1], [x2, y2], ...])``).
- `transform` tries to determine which kind of function was passed in
- by calling `func` first with n iterables of coordinates, where n
- is the dimensionality of the input geometry. If `func` raises
- a `TypeError` when called with iterables as arguments,
- then it will instead call `func` on each individual coordinate
- in the geometry.
+ If your function works on separate arrays of x, y, and optionally z
+ coordinates, set `interleaved=False` (``func([x1, x2, ...], [y1, y2, ...])``).
- `New in version 1.2.18`.
+ Full reference: :func:`shapely.transform`
-For example, here is an identity function applicable to both types of input
-(scalar or array).
+For example, here is a simple function that returns shifted coordinates:
.. code-block:: python
- def id_func(x, y, z=None):
- return tuple(filter(None, [x, y, z]))
+ def shift_xy(coords):
+ # NumPy applies this addition element-wise to all coordinates
+ return coords + 2
- g2 = transform(id_func, g1)
+ g2 = shapely.transform(g1, shift_xy)
+or that swaps the x and y coordinates:
-If using `pyproj>=2.1.0`, the preferred method to project geometries is:
+.. code-block:: python
+
+ def swap_xy(x, y):
+ return y, x
+
+ # specify interleaved=False to pass separate x and y arrays to the function
+ g3 = shapely.transform(g1, swap_xy, interleaved=False)
+
+To reproject geometries using ``pyproj``:
.. code-block:: python
import pyproj
- from shapely import Point
- from shapely.ops import transform
+ from shapely import Point, transform
wgs84_pt = Point(-72.2495, 43.886)
wgs84 = pyproj.CRS('EPSG:4326')
utm = pyproj.CRS('EPSG:32618')
- project = pyproj.Transformer.from_crs(wgs84, utm, always_xy=True).transform
- utm_point = transform(project, wgs84_pt)
+ transformer = pyproj.Transformer.from_crs(wgs84, utm, always_xy=True)
+ # specify interleaved=False because the pyproj transform function expects
+ # separate x, y arrays as input
+ utm_point = transform(wgs84_pt, transformer.transform, interleaved=False)
It is important to note that in the example above, the `always_xy` kwarg is
required as Shapely only supports coordinates in X,Y order, and in PROJ 6 the
WGS84 CRS uses the EPSG-defined Lat/Lon coordinate order instead of the
expected Lon/Lat.
-If using `pyproj < 2.1`, then the canonical example is:
+.. function:: shapely.transform_coordseq(geom, func, include_z=False, interleaved=True)
+ :no-index:
-.. code-block:: python
+ Apply a transformation `func` to the coordinate sequences of a geometry.
- from functools import partial
- import pyproj
+ Similar to :func:`shapely.transform`, the transformation function can
+ accept a single 2D array of coordinates or separate x, y, and optionally
+ z arrays (depending on the ``interleaved`` keyword).
- from shapely.ops import transform
+ The difference is that in case of :func:`~shapely.transform_coordseq`,
+ the transformation function is called separately for each coordinate
+ sequence (and thus only receiving the coordinates of one sequence at a
+ time), instead of being called once for all coordinates. For polygons, this
+ means per ring.
- wgs84 = pyproj.Proj(init='epsg:4326')
- utm = pyproj.Proj(init='epsg:32618')
+ As a result, the number of coordinate pairs per coordinate sequence is
+ allowed to change.
- project = partial(
- pyproj.transform,
- wgs84,
- utm)
+ Full reference: :func:`shapely.transform_coordseq`
- utm_point = transform(project, wgs84_pt)
+For example, reducing a linestring to only its first 2 points:
-Lambda expressions such as the one in
+.. code-block:: pycon
-.. code-block:: python
+ >>> import shapely
+ >>> line = LineString([(2, 2), (4, 4), (6, 6)])
+ >>> shapely.transform_coordseq(line, lambda coords: coords[:2])
+ <LINESTRING (2 2, 4 4)>
- g2 = transform(lambda x, y, z=None: (x+1.0, y+1.0), g1)
+The :func:`~shapely.transform` function is the more performant option, so
+whenever your transformation function can be applied to all coordinates
+element-wise at once and does not change the number of coordinate pairs, we
+recommend using that.
-also satisfy the requirements for `func`.
+Finally, there is a third `transform` function, which existed before the
+options described above, and is kept for backwards compatibility but is
+deprecated:
+.. function:: shapely.ops.transform(func, geom)
+
+ Applies `func` to all coordinates of `geom` and returns a new
+ geometry of the same type from the transformed coordinates.
+
+ `func` maps x, y, and optionally z to output xp, yp, zp. The input
+ parameters may be iterable types like lists or arrays or single values.
+ The output shall be of the same type: scalars in, scalars out;
+ lists in, lists out.
+
+ `transform` tries to determine which kind of function was passed in
+ by calling `func` first with n iterables of coordinates, where n
+ is the dimensionality of the input geometry. If `func` raises
+ a `TypeError` when called with iterables as arguments,
+ then it will instead call `func` on each individual coordinate
+ in the geometry.
+
+ .. deprecated:: 2.2.0
+
+ **Updating to use** :func:`shapely.transform` **or** :func:`shapely.transform_coordseq`
+
+ If you are currently using ``shapely.ops.transform``, consider whether your
+ transformation function can be applied to all coordinates at once
+ independently. If that is the case, replace:
+
+ .. code-block:: python
+
+ geom2 = shapely.ops.transform(func, geom1)
+
+ with :func:`shapely.transform`:
+
+ .. code-block:: python
+
+ geom2 = shapely.transform(geom1, func, interleaved=False)
+
+ If your function has custom logic per coordinate sequence or changes the
+ number of coordinate pairs, use :func:`shapely.transform_coordseq`.
+
+ If your function only works on scalar coordinate values, you can define
+ a small wrapper function that accepts an array of coordinates, and
+ applies your scalar function to each coordinate pair individually:
+
+ .. code-block:: python
+
+ import numpy as np
+
+ def func_scalar(x, y, z=None):
+ if x < 0:
+ x += 360
+ return x, y
+
+ def func_wrapped(coords):
+ return np.array([func_scalar(x, y) for x, y in coords])
+
+ geom2 = shapely.transform(geom1, func_wrapped)
+
+ Note: this scalar function is only for illustration purposes, and this
+ specific operation can also be re-written to use vectorized array operations.
Other Operations
================
@@ -2183,8 +2249,6 @@ Sequences of touching lines can be merged into `MultiLineStrings` or
form part of polygon. Invalid ring lines form rings which are invalid
(bowties, etc).
- `New in version 1.2.18.`
-
.. code-block:: pycon
>>> from shapely import polygonize_full
@@ -2233,8 +2297,6 @@ within a rectangle.
not guaranteed to be valid. No exceptions will be raised for topological
errors.
- `New in version 1.7.`
-
.. code-block:: python
>>> from shapely import clip_by_rect
@@ -2404,8 +2466,6 @@ a second geometry with a given tolerance.
The `tolerance` argument specifies the minimum distance between vertices for
them to be snapped.
- `New in version 1.5.0`
-
.. code-block:: pycon
>>> from shapely import snap
@@ -2431,8 +2491,6 @@ linear geometries.
inputs. The second element is a MultiLineString containing shared paths with
the opposite direction for the two inputs.
- `New in version 1.6.0`
-
.. code-block:: pycon
>>> from shapely import shared_paths
@@ -2564,8 +2622,6 @@ Diagnostics
Returns a string explaining the validity or invalidity of the object.
- `New in version 1.2.1`.
-
The messages may or may not have a representation of a problem point that can
be parsed out.
@@ -2640,13 +2696,6 @@ Polylabel
Finds the approximate location of the pole of inaccessibility for a given
polygon. Based on Vladimir Agafonkin's polylabel_.
- `New in version 1.6.0`
-
-.. note::
-
- Prior to 1.7 `polylabel` must be imported from `shapely.algorithms.polylabel`
- instead of `shapely.ops`.
-
.. code-block:: pycon
>>> from shapely.ops import polylabel
@@ -2672,8 +2721,6 @@ cannot add or remove geometries.
References to these geometric objects are kept and stored in the R-tree.
- `New in version 1.4.0`.
-
.. method:: strtree.query(geom)
:noindex:
@@ -2864,8 +2911,6 @@ The GeoJSON-like mapping of a geometric object can be obtained using
counterclockwise, and interior rings (holes) clockwise with
:func:`shapely.orient_polygons`.
- `New in version 1.2.3`.
-
For example, using the same `GeoThing` class:
.. code-block:: pycon
=====================================
docs/release/2.x.rst
=====================================
@@ -4,7 +4,7 @@ Version 2.x
.. _version-2-2-0:
-Version 2.2.0 (2026-09-xx)
+Version 2.2.0 (2026-10-07)
--------------------------
New features
@@ -41,9 +41,9 @@ vectorized functions:
of a geometry (#2313).
- :func:`.coverage_clean`, which returns a cleaned version of geometries
forming a polygonal coverage (#2454) (requires GEOS >= 3.14).
-- :func:`.split` to split a geometry by another geometry (uses GEOS>=3.15,
- falls back to the existing :func:`shapely.ops.split` implementation
- otherwise) (#2477).
+- :func:`~shapely.split` to split a geometry by another geometry (uses
+ GEOS>=3.15, falls back to the existing :func:`shapely.ops.split`
+ implementation otherwise) (#2477).
In addition, two other functions have been added to the top-level namespace:
@@ -67,25 +67,36 @@ Other improvements
reused through a thread-local GEOS context (#2343).
- :func:`.Polygon.from_bounds` now builds the ring in counterclockwise order,
- consistent with :function:`shapely.box`, so the result satisfies the GeoJSON
+ consistent with :func:`shapely.box`, so the result satisfies the GeoJSON
right-hand rule (#2396).
- Added return values to :func:`.prepare` and :func:`.destroy_prepared`. They
now return whether something was done; calling `prepare` twice on a geometry
will first return `True`, and then `False` (#2359).
-- The :func:`.split` and :func:`shapely.ops.split` functions now support
- splitting a (Multi)Polygon by another (Multi)Polygon, by using the boundary
- to split to with (similarly as was already done for splitting a
+- The :func:`~shapely.split` and :func:`shapely.ops.split` functions now
+ support splitting a (Multi)Polygon by another (Multi)Polygon, by using the
+ boundary to split to with (similarly as was already done for splitting a
(Multi)LineString with a polygon) (#2505).
API changes
^^^^^^^^^^^
-- The :func:`shapely.ops.transform`` is deprecated. Use the more specific
+- The :func:`shapely.ops.transform` is deprecated. Use the more specific
:meth:`shapely.transform` (when your function works independently on all
coordinates) or :meth:`shapely.transform_coordseq` (when your function works
- on the coordinate sequence directly) instead (#1902).
+ on the coordinate sequence directly) instead. See
+ :func:`the manual with update advice <shapely.ops.transform>` for more
+ details (#1902).
+
+- The ``multithreading_enabled`` decorator is deprecated, and is also no longer
+ used internally. This decorator added overhead to each function call, while
+ only providing minimal and incomplete safety guarantees around multi-threaded
+ usage of shapely (#2338).
+ When sharing NumPy array of geometries between threads, it is the responsibility
+ of the user to ensure those geometries are not modified at the same time.
+ See the `multithreading section <https://github.com/shapely/shapely#multithreading>`__
+ for more details.
Bug fixes
^^^^^^^^^
@@ -95,6 +106,8 @@ Bug fixes
passing a sequence of non-polygons (#2461).
- Fix a crash in :func:`.coverage_invalid_edges` on invalid (non-geometry)
input (#2483).
+- Fix keyword argument forwarding in :func:`shapely.line_interpolate_point`
+ and :func:`shapely.line_locate_point` (#2480).
Packaging
^^^^^^^^^
=====================================
pyproject.toml
=====================================
@@ -2,7 +2,7 @@
requires = [
# keep in sync with a subset of [dependency-groups.build]
"Cython",
- "meson-python>=0.15.0",
+ "meson-python>=0.15.0,!=0.22.0",
"numpy>=1.26,<3",
]
build-backend = "mesonpy"
@@ -46,7 +46,7 @@ build = [
# keep in sync with a superset of [build-system.requires]
"Cython",
"meson>=1.2.1",
- "meson-python>=0.15.0",
+ "meson-python>=0.15.0,!=0.22.0",
"ninja",
"numpy>=1.26,<3",
]
@@ -184,7 +184,6 @@ ignore = [
[tool.ruff.lint.isort]
combine-as-imports = true
-extra-standard-library = ["packaging"]
[tool.ruff.lint.pydocstyle]
convention = "numpy"
@@ -197,3 +196,11 @@ allow-dunder-method-names = [
"_repr_svg_",
]
max-positional-args = 8 # TODO: revise with deprecate_positional changes
+
+[tool.versioneer]
+VCS = "git"
+style = "pep440"
+versionfile_source = "src/shapely/_version.py"
+versionfile_build = "shapely/_version.py"
+tag_prefix = ""
+parentdir_prefix = "shapely-"
=====================================
setup.cfg deleted
=====================================
@@ -1,7 +0,0 @@
-[versioneer]
-VCS = git
-style = pep440
-versionfile_source = src/shapely/_version.py
-versionfile_build = shapely/_version.py
-tag_prefix =
-parentdir_prefix = shapely-
=====================================
src/shapely/coordinates.py
=====================================
@@ -53,9 +53,8 @@ def transform(
another (N, 2) or (N, 3) ndarray of float64.
The function may not change N.
include_z : bool, optional, default False
- If False, always return 2D geometries.
- If True, the data being passed to the
- transformation function will include the third dimension
+ If False, always return 2D geometries. If True, the data being passed
+ to the transformation function will include the third dimension
(if a geometry has no third dimension, the z-coordinates
will be NaN). If None, will infer the dimensionality per
input geometry using ``has_z``, which may result in 2 calls to
@@ -67,7 +66,6 @@ def transform(
one-dimensional coordinate arrays as arguments (x, y and optional z) instead
of a single one. The return value must be a tuple of (x, y and optional z).
-
.. versionadded:: 2.1.0
Notes
@@ -80,9 +78,9 @@ def transform(
See Also
--------
- has_z : Returns a copy of a geometry array with a function applied to its
- coordinates.
- transform_coordseq : Transform single Geometry objects, optionally resizing them.
+ has_z : Return True if a geometry has Z coordinates.
+ transform_coordseq : Transform Geometry objects per coordinate sequence,
+ optionally resizing them.
Examples
--------
@@ -146,22 +144,20 @@ interleaved=False, include_z=True)
include_m = False
coordinates = lib.get_coordinates(geometry_arr, include_z, include_m, False)
if interleaved:
- new_coordinates = transformation(coordinates)
+ new_coordinates = np.asarray(transformation(coordinates), dtype=np.float64)
else:
new_coordinates = np.asarray(
transformation(*coordinates.T), dtype=np.float64
).T
# check the array to yield understandable error messages
- if not isinstance(new_coordinates, np.ndarray) or new_coordinates.ndim != 2:
- raise ValueError(
- "The provided transformation did not return a two-dimensional numpy "
- "array"
- )
- if new_coordinates.dtype != np.float64:
- raise ValueError(
- "The provided transformation returned an array with an unexpected "
- f"dtype ({new_coordinates.dtype})"
- )
+ if new_coordinates.ndim != 2:
+ if new_coordinates.size == 0:
+ # for empty input or emtpy geometry, ensure proper shape
+ new_coordinates = np.empty((0, coordinates.shape[1]), dtype=np.float64)
+ else:
+ raise ValueError(
+ "The provided transformation did not return a two-dimensional array"
+ )
if new_coordinates.shape != coordinates.shape:
# if the shape is too small we will get a segfault
raise ValueError(
@@ -175,42 +171,40 @@ interleaved=False, include_z=True)
def transform_coordseq(
- geom: shapely.Geometry | None,
+ geometry,
transformation,
*,
include_z: bool | None = False,
interleaved: bool = True,
):
- """Apply a transformation to the coordinate sequences of a single geometry.
-
- The transformation function is applied per coordinate sequence. For polygons this
- means: per ring. For collections this means: per element.
+ """Apply a transformation to the coordinate sequences of a geometry (array).
- This function differs with `transform` in the following ways:
+ This function differs from `transform` in the following ways:
- - It only accepts scalar Geometry objects, not arrays.
+ - The transformation function is applied once per coordinate sequence. For
+ polygons this means: per ring. For collections this means: per element.
- The number of coordinate pairs per coordinate sequence is allowed to change.
- The `transform` function is the more performant option, so we recommend using this
- function when changing the number of coordinate pairs.
+ The `transform` function is the more performant option, so we recommend using
+ that function when not changing the number of coordinate pairs.
.. versionadded:: 2.2.0
Parameters
----------
- geom : Geometry or None
+ geometry : Geometry or array_like
+ Geometry or geometries to transform.
transformation : function
A function that transforms a (N, 2) or (N, 3) ndarray of float64 to
another (N, 2) or (N, 3) ndarray of float64.
The function may change the value of N.
include_z : bool, optional, default False
- If False, always return 2D geometries.
- If True, the data being passed to the
- transformation function will include the third dimension
+ If False, always return 2D geometries. If True, the data being passed
+ to the transformation function will include the third dimension
(if a geometry has no third dimension, the z-coordinates
- will be NaN). If None, will infer the dimensionality using
- ``has_z``. Note that this inference
- can be unreliable with empty geometries or NaN coordinates: for a
+ will be NaN).
+ If None, will infer the dimensionality using ``has_z``. Note that this
+ inference can be unreliable with empty geometries or NaN coordinates: for a
guaranteed result, it is recommended to specify ``include_z`` explicitly.
interleaved : bool, default True
If set to False, the transformation function should accept 2 or 3 separate
@@ -219,8 +213,7 @@ def transform_coordseq(
See Also
--------
- has_z : Returns a copy of a geometry array with a function applied to its
- coordinates.
+ has_z : Return True if a geometry has Z coordinates.
transform : Transform arrays of Geometry objects.
Examples
@@ -240,6 +233,16 @@ def transform_coordseq(
lambda x, y: (x[:2], y[:2]), interleaved=False)
<LINESTRING (2 2, 4 4)>
"""
+ if lib.is_valid_input_scalar(geometry):
+ return _transform_coordseq_scalar(
+ geometry, transformation, include_z, interleaved
+ )
+ return _transform_coordseq_vectorized(
+ geometry, transformation, include_z, interleaved
+ )
+
+
+def _transform_coordseq_scalar(geom, transformation, include_z=False, interleaved=True):
if geom is None:
return geom
if geom.is_empty:
@@ -288,6 +291,11 @@ def transform_coordseq(
raise TypeError(f"Type {geom_type} not recognized")
+_transform_coordseq_vectorized = np.vectorize(
+ _transform_coordseq_scalar, otypes="O", excluded=[1, 2, 3]
+)
+
+
def count_coordinates(geometry):
"""Count the number of coordinate pairs in a geometry array.
=====================================
src/shapely/lib/geos.c
=====================================
@@ -536,7 +536,7 @@ char wkt_empty_3d_geometry(GEOSContextHandle_t ctx, GEOSGeometry* geom, char** w
* - PGERR_EXCEPTIONS on GEOS exceptions
* - PGERR_SUCCESS on a non-empty and linear geometry
*/
-char geos_interpolate_checker(GEOSContextHandle_t ctx, GEOSGeometry* geom) {
+char geos_interpolate_checker(GEOSContextHandle_t ctx, const GEOSGeometry* geom) {
char type;
char is_empty;
const GEOSGeometry* sub_geom;
=====================================
src/shapely/lib/geos.h
=====================================
@@ -210,7 +210,7 @@ extern char check_to_wkt_trim_compatible(GEOSContextHandle_t ctx, const GEOSGeom
extern char wkt_empty_3d_geometry(GEOSContextHandle_t ctx, GEOSGeometry* geom,
char** wkt);
#endif // !GEOS_SINCE_3_12_0
-extern char geos_interpolate_checker(GEOSContextHandle_t ctx, GEOSGeometry* geom);
+extern char geos_interpolate_checker(GEOSContextHandle_t ctx, const GEOSGeometry* geom);
extern int init_shapely(PyObject* m);
=====================================
src/shapely/lib/geos_funcs_O_b.c
=====================================
@@ -90,7 +90,7 @@ static char PrepareGeometry(GEOSContextHandle_t context, PyObject* obj, char* re
if (prep == NULL) {
return PGERR_GEOS_EXCEPTION;
}
- ((GeometryObject*)obj)->ptr_prepared = prep;
+ ((GeometryObject*)obj)->ptr_prepared = (GEOSPreparedGeometry*)prep;
*result = 1;
}
return PGERR_SUCCESS;
=====================================
src/shapely/linear.py
=====================================
@@ -58,9 +58,9 @@ def line_interpolate_point(line, distance, normalized=False, **kwargs):
"""
if normalized:
- return lib.line_interpolate_point_normalized(line, distance)
+ return lib.line_interpolate_point_normalized(line, distance, **kwargs)
else:
- return lib.line_interpolate_point(line, distance)
+ return lib.line_interpolate_point(line, distance, **kwargs)
# Note: future plan is to change this signature over a few releases:
@@ -108,9 +108,9 @@ def line_locate_point(line, other, normalized=False, **kwargs):
"""
if normalized:
- return lib.line_locate_point_normalized(line, other)
+ return lib.line_locate_point_normalized(line, other, **kwargs)
else:
- return lib.line_locate_point(line, other)
+ return lib.line_locate_point(line, other, **kwargs)
def line_merge(line, directed=False, **kwargs):
=====================================
src/shapely/ops.py
=====================================
@@ -8,7 +8,7 @@ import numpy as np
import shapely
from shapely.algorithms.polylabel import polylabel # noqa
from shapely.coordinates import transform_coordseq
-from shapely.errors import GeometryTypeError, ShapelyDeprecationWarning
+from shapely.errors import GeometryTypeError
from shapely.geometry import (
GeometryCollection,
LineString,
@@ -221,11 +221,16 @@ def transform(func, geom):
.. deprecated:: 2.2.0
This function was superseded by :meth:`shapely.transform` and
:meth:`shapely.transform_coordseq`.
+
+ See https://shapely.readthedocs.io/en/latest/manual.html#shapely.ops.transform
+ for more details.
"""
warn(
- "The 'ops.transform()' function is deprecated. "
- "Use 'transform()' or 'transform_coordseq()' instead.",
- ShapelyDeprecationWarning,
+ "The 'shapely.ops.transform()' function is deprecated. "
+ "Use 'shapely.transform()' or 'shapely.transform_coordseq()' instead. "
+ "See https://shapely.readthedocs.io/en/latest/manual.html#shapely.ops.transform"
+ " for more details.",
+ DeprecationWarning,
stacklevel=2,
)
=====================================
src/shapely/tests/legacy/test_transform.py
=====================================
@@ -3,7 +3,6 @@ import unittest
import pytest
from shapely import geometry
-from shapely.errors import ShapelyDeprecationWarning
from shapely.ops import transform
@@ -15,41 +14,41 @@ class IdentityTestCase(unittest.TestCase):
def test_empty(self):
g = geometry.Point()
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(self.func, g)
assert h.is_empty
def test_point(self):
g = geometry.Point(0, 1)
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(self.func, g)
assert h.geom_type == "Point"
assert list(h.coords) == [(0, 1)]
def test_line(self):
g = geometry.LineString([(0, 1), (2, 3)])
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(self.func, g)
assert h.geom_type == "LineString"
assert list(h.coords) == [(0, 1), (2, 3)]
def test_linearring(self):
g = geometry.LinearRing([(0, 1), (2, 3), (2, 2), (0, 1)])
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(self.func, g)
assert h.geom_type == "LinearRing"
assert list(h.coords) == [(0, 1), (2, 3), (2, 2), (0, 1)]
def test_polygon(self):
g = geometry.Point(0, 1).buffer(1.0)
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(self.func, g)
assert h.geom_type == "Polygon"
assert g.area == pytest.approx(h.area)
def test_multipolygon(self):
g = geometry.MultiPoint([(0, 1), (0, 4)]).buffer(1.0)
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(self.func, g)
assert h.geom_type == "MultiPolygon"
assert g.area == pytest.approx(h.area)
@@ -60,21 +59,21 @@ class LambdaTestCase(unittest.TestCase):
def test_point(self):
g = geometry.Point(0, 1)
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
assert h.geom_type == "Point"
assert list(h.coords) == [(1.0, 2.0)]
def test_line(self):
g = geometry.LineString([(0, 1), (2, 3)])
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
assert h.geom_type == "LineString"
assert list(h.coords) == [(1.0, 2.0), (3.0, 4.0)]
def test_polygon(self):
g = geometry.Point(0, 1).buffer(1.0)
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
assert h.geom_type == "Polygon"
assert g.area == pytest.approx(h.area)
@@ -83,7 +82,7 @@ class LambdaTestCase(unittest.TestCase):
def test_multipolygon(self):
g = geometry.MultiPoint([(0, 1), (0, 4)]).buffer(1.0)
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
assert h.geom_type == "MultiPolygon"
assert g.area == pytest.approx(h.area)
@@ -101,7 +100,7 @@ class TypeTestCase(unittest.TestCase):
assert all(type(elem) is float for elem in coord)
return (x, y)
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
transform(func, geometry.LineString([(0, 1), (2, 3)]))
def test_func_fallback_gets_floats(self):
@@ -112,5 +111,5 @@ class TypeTestCase(unittest.TestCase):
assert type(coord) is float
return (x, y)
- with pytest.warns(ShapelyDeprecationWarning):
+ with pytest.warns(DeprecationWarning):
transform(func, geometry.LineString([(0, 1), (2, 3)]))
=====================================
src/shapely/tests/test_coordinates.py
=====================================
@@ -303,12 +303,32 @@ def test_set_coords_mixed_dimension(include_z):
(True, True, lambda coords: coords + 1),
(False, False, lambda x, y: (x + 1, y + 1)),
(False, True, lambda x, y, z: (x + 1, y + 1, z + 1)),
+ # transform functions that mimic wrapping of scalar function
+ (True, False, lambda coords: np.array([c + 1 for c in coords])),
+ (True, False, lambda coords: [c + 1 for c in coords]),
+ (True, True, lambda coords: [c + 1 for c in coords]),
+ (
+ False,
+ False,
+ lambda x, y: (np.array([c + 1 for c in x]), np.array([c + 1 for c in y])),
+ ),
+ (False, False, lambda x, y: ([c + 1 for c in x], [c + 1 for c in y])),
+ (
+ False,
+ True,
+ lambda x, y, z: (
+ [c + 1 for c in x],
+ [c + 1 for c in y],
+ [c + 1 for c in z],
+ ),
+ ),
],
)
-def test_transform(geoms, include_z, interleaved, transformation):
+ at pytest.mark.parametrize("transform_func", [transform, transform_coordseq])
+def test_transform(geoms, include_z, interleaved, transformation, transform_func):
geoms = np.array(geoms, np.object_)
coordinates_before = get_coordinates(geoms, include_z=include_z)
- new_geoms = transform(
+ new_geoms = transform_func(
geoms, transformation, include_z=include_z, interleaved=interleaved
)
assert new_geoms is not geoms
@@ -320,13 +340,14 @@ def test_transform(geoms, include_z, interleaved, transformation):
@pytest.mark.parametrize(
"interleaved,transformation",
[
- (True, lambda coords: [c + 1 for c in coords]),
+ (True, lambda coords: coords + 1),
(False, lambda x, y: (x + 1, y + 1)),
],
)
-def test_transform_coordseq(geom, interleaved, transformation):
+ at pytest.mark.parametrize("transform_func", [transform, transform_coordseq])
+def test_transform_all_types(geom, interleaved, transformation, transform_func):
coordinates_before = get_coordinates(geom)
- new_geom = transform_coordseq(geom, transformation, interleaved=interleaved)
+ new_geom = transform_func(geom, transformation, interleaved=interleaved)
assert type(geom) is type(new_geom)
coordinates_after = get_coordinates(new_geom)
assert_allclose(coordinates_before + 1, coordinates_after, equal_nan=True)
@@ -342,9 +363,12 @@ def test_transform_coordseq(geom, interleaved, transformation):
(False, True, lambda x, y, z: (x + 1, y + 1, z + 1)),
],
)
-def test_transform_coordseq_3d(geom, include_z, interleaved, transformation):
+ at pytest.mark.parametrize("transform_func", [transform, transform_coordseq])
+def test_transform_all_types_3d(
+ geom, include_z, interleaved, transformation, transform_func
+):
coordinates_before = get_coordinates(geom, include_z=include_z)
- new_geom = transform_coordseq(
+ new_geom = transform_func(
geom, transformation, include_z=include_z, interleaved=interleaved
)
assert type(geom) is type(new_geom)
@@ -359,22 +383,32 @@ def test_transform_missing(transform_func):
assert actual is None
-def test_transform_0dim():
+ at pytest.mark.parametrize("transform_func", [transform, transform_coordseq])
+def test_transform_0dim(transform_func):
# a geometry input returns a geometry
- actual = transform(point, lambda x: x + 1)
+ actual = transform_func(point, lambda x: x + 1)
assert isinstance(actual, shapely.Geometry)
# a 0-dim array input returns a 0-dim array
- actual = transform(np.asarray(point), lambda x: x + 1)
+ actual = transform_func(np.asarray(point), lambda x: x + 1)
assert isinstance(actual, np.ndarray)
assert actual.ndim == 0
-def test_transform_no_geoms():
+ at pytest.mark.parametrize("transform_func", [transform, transform_coordseq])
+def test_transform_no_geoms(transform_func):
# a geometry input returns a geometry
- actual = transform([], lambda x: x + 1)
+ actual = transform_func([], lambda x: x + 1)
assert actual.shape == (0,)
+ at pytest.mark.parametrize("transform_func", [transform, transform_coordseq])
+def test_transform_multidim(transform_func):
+ arr = np.array([[point, point, point], [point, point, point]], dtype=object)
+ actual = transform_func(arr, lambda x: x + 1)
+ assert isinstance(actual, np.ndarray)
+ assert actual.shape == arr.shape
+
+
def test_transform_adapt_shape_not_allowed():
def remove_coord(arr):
return arr[:-1]
@@ -443,8 +477,11 @@ def test_transform_auto_coordinate_dimension(geom, expected, transform_func):
assert (shapely.get_coordinate_dimension(new_geom) == expected).all()
-def test_transform_auto_coordinate_dimension_mixed():
- new_geom = transform([line_string, line_string_z], lambda x: x + 1, include_z=None)
+ at pytest.mark.parametrize("transform_func", [transform, transform_coordseq])
+def test_transform_auto_coordinate_dimension_mixed(transform_func):
+ new_geom = transform_func(
+ [line_string, line_string_z], lambda x: x + 1, include_z=None
+ )
assert_equal(shapely.get_coordinate_dimension(new_geom), [2, 3])
assert_equal(
shapely.get_coordinates(line_string, include_z=False) + 1,
@@ -463,8 +500,9 @@ def transform_non_interleaved(x, y, z=None):
return [x + 1, y + 2, z + 3]
-def test_transform_auto_coordinate_dimension_mixed_interleaved():
- new_geom = transform(
+ at pytest.mark.parametrize("transform_func", [transform, transform_coordseq])
+def test_transform_auto_coordinate_dimension_mixed_interleaved(transform_func):
+ new_geom = transform_func(
[line_string, line_string_z],
transform_non_interleaved,
include_z=None,
=====================================
src/shapely/tests/test_linear.py
=====================================
@@ -101,6 +101,15 @@ def test_line_interpolate_point_deprecate_positional():
shapely.line_interpolate_point(line_string, 0, False)
+ at pytest.mark.parametrize("normalized", [False, True])
+def test_line_interpolate_point_out(normalized):
+ out = np.empty((), dtype=object)
+ actual = shapely.line_interpolate_point(
+ line_string, 0.2, normalized=normalized, out=out
+ )
+ assert actual is out
+
+
def test_line_locate_point_geom_array():
point = shapely.points(0, 1)
actual = shapely.line_locate_point([line_string, linear_ring], point)
@@ -145,6 +154,15 @@ def test_line_locate_point_deprecate_positional():
shapely.line_locate_point(line_string, point, False)
+ at pytest.mark.parametrize("normalized", [False, True])
+def test_line_locate_point_out(normalized):
+ out = np.empty((), dtype=np.float64)
+ actual = shapely.line_locate_point(
+ line_string, point, normalized=normalized, out=out
+ )
+ assert actual is out
+
+
def test_line_merge_geom_array():
actual = shapely.line_merge([line_string, multi_line_string])
assert_geometries_equal(actual[0], line_string)
=====================================
src/shapely/tests/test_misc.py
=====================================
@@ -31,6 +31,10 @@ def test_version():
assert isinstance(shapely.__version__, str)
+def test_git_version():
+ assert isinstance(shapely.__git_version__, str)
+
+
def test_geos_version():
expected = "{}.{}.{}".format(*shapely.geos_version)
actual = shapely.geos_version_string
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-shapely/-/commit/2816314621cb427581a39545ed15468dadb4fb8b
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-shapely/-/commit/2816314621cb427581a39545ed15468dadb4fb8b
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20261007/612838a0/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list