[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