迁移到Shapely 1.8/2.0

Shapely 1.8.0是一个过渡版本,引入了几个警告,为2.0.0中即将进行的更改做准备。

Shapely 2.0.0将是一个主要版本,对内部结构进行了重构,并对性能进行了相当大的改进(基于 PyGEOS 包),以及几个突破性的变化。

本指南概述了最重要的更改,并详细介绍了2.0.0中的更改、1.8.0中如何对此进行警告,以及如何更新代码以适应未来的需要。

有关更多背景信息,请参阅 RFC 1: Roadmap for Shapely 2.0

几何对象将变得不可变

几何图形对象在2.0.0版中将变得不可变。

在Shapely 1.x中,一些几何图形类是可变的,这意味着您可以就地更改它们的坐标。说明性代码::

>>> from shapely.geometry import LineString
>>> line = LineString([(0,0), (2, 2)])
>>> print(line)
LINESTRING (0 0, 2 2)

>>> line.coords = [(0, 0), (10, 0), (10, 10)]
>>> print(line)
LINESTRING (0 0, 10 0, 10 10)

在Shapely 1.8中,这将开始引发警告::

>>> line.coords = [(0, 0), (10, 0), (10, 10)]
ShapelyDeprecationWarning: Setting the 'coords' to mutate a Geometry
in place is deprecated, and will not be possible any more in Shapely 2.0

从2.0.0版开始,所有几何图形对象都将变得不可变。因此,它们也将成为可散列的,因此可用作例如词典关键字。

How do I update my code? 除了使用新坐标创建新的几何体对象外,没有更改现有几何体坐标的直接替代方法。

设置自定义属性

几何体对象变得不可变的另一个后果是,将不再可能指定当前有效的自定义属性。

目前您可以执行以下操作:

>>> line.name = "my_geometry"
>>> line.name
'my_geometry'

在Shapely 1.8中,这将开始引发警告,并在Shapely 2.0中引发AttributeError。

How do I update my code? 没有将自定义属性添加到几何体对象的直接替代方法。您可以使用其他的Python数据结构,例如(类似GeoJSON的)词典或GeoPandas的GeoDataFrames来存储几何特征和属性。

多部分几何图形将不再是“序列”(长度、可迭代、可索引)

在Shapely 1.x中,多部分几何图形(多点、多线串、多多面和GeometryCollection)实现了“序列”Python接口的一部分(使它们类似于列表)。这意味着您可以遍历对象以获取部件,对对象进行索引以获取特定部件,并使用 len() 方法。

Shapely 1.x就是这样的一个例子:

>>> from shapely.geometry import Point, MultiPoint
>>> mp = MultiPoint([(1, 1), (2, 2), (3, 3)])
>>> print(mp)
MULTIPOINT (1 1, 2 2, 3 3)
>>> for part in mp:
...     print(part)
POINT (1 1)
POINT (2 2)
POINT (3 3)
>>> print(mp[1])
POINT (2 2)
>>> len(mp)
3
>>> list(mp)
[<shapely.geometry.point.Point at 0x7f2e0912bf10>,
 <shapely.geometry.point.Point at 0x7f2e09fed820>,
 <shapely.geometry.point.Point at 0x7f2e09fed4c0>]

从Shapely 1.8开始,上面的所有示例都将开始引发弃用警告。例如:

>>> for part in mp:
...     print(part)
ShapelyDeprecationWarning: Iteration over multi-part geometries is deprecated
and will be removed in Shapely 2.0. Use the `geoms` property to access the
constituent parts of a multi-part geometry.
POINT (1 1)
POINT (2 2)
POINT (3 3)

在Shapely 2.0中,所有这些示例都会引发错误。

How do I update my code? 若要访问包含多个部分的几何体的几何体部分,可以使用 .geoms 属性,如警告所示。

以上示例可以更新为:

>>> for part in mp.geoms:
...     print(part)
POINT (1 1)
POINT (2 2)
POINT (3 3)
>>> print(mp.geoms[1])
POINT (2 2)
>>> len(mp.geoms)
3
>>> list(mp.geoms)
[<shapely.geometry.point.Point at 0x7f2e0912bf10>,
 <shapely.geometry.point.Point at 0x7f2e09fed820>,
 <shapely.geometry.point.Point at 0x7f2e09fed4c0>]

单部分几何图形(点、线串、多边形)已经不支持这些功能,并且对于这些类来说,这方面的行为没有变化。

与NumPy和阵列接口的互操作性

将坐标转换为(NumPy)数组

Shapely提供了一个数组接口,可以轻松访问坐标,例如NumPy数组 (manual section )。

一个小例子::

>>> line = LineString([(0, 0), (1, 1), (2, 2)])
>>> import numpy as np
>>> np.asarray(line)
array([[0., 0.],
       [1., 1.],
       [2., 2.]])

此外,还有明确的 array_interface() 方法和方法 ctypes 属性以访问作为数组数据的坐标:

>>> line.ctypes
<shapely.geometry.linestring.c_double_Array_6 at 0x7f75261eb740>
>>> line.array_interface()
{'version': 3,
 'typestr': '<f8',
 'data': <shapely.geometry.linestring.c_double_Array_6 at 0x7f752664ae40>,
 'shape': (3, 2)}

此功能适用于点、线串、线环和多点。

为了与NumPy实现更强大的互操作性,该数组接口将从这些几何图形类中移除,并仅限于 coords

从Shapely 1.8开始,将几何对象直接转换为NumPy数组将开始引发警告::

>>> np.asarray(line)
ShapelyDeprecationWarning: The array interface is deprecated and will no longer
work in Shapely 2.0. Convert the '.coords' to a NumPy array instead.
array([[0., 0.],
       [1., 1.],
       [2., 2.]])

How do I update my code? 若要将几何体转换为NumPy数组,可以将 .coords 而是属性::

>>> line.coords
<shapely.coords.CoordinateSequence at 0x7f2e09e88d60>
>>> np.array(line.coords)
array([[0., 0.],
       [1., 1.],
       [2., 2.]])

这个 array_interface() 方法和方法 ctypes 属性将在Shapely 2.0中删除,但由于Shapely将开始要求将NumPy作为依赖项,因此您可以直接使用NumPy或其数组接口。查看上的NumPy文档 ctypes 属性或 array interface 了解更多详细信息。

创建几何体对象的NumPy数组

形状几何图形对象可以使用 object 数据类型。通常,可以从几何图形列表创建这样的数组,如下所示:

>>> from shapely.geometry import Point
>>> arr = np.array([Point(0, 0), Point(1, 1), Point(2, 2)])
>>> arr
array([<shapely.geometry.point.Point object at 0x7fb798407cd0>,
       <shapely.geometry.point.Point object at 0x7fb7982831c0>,
       <shapely.geometry.point.Point object at 0x7fb798283b80>],
      dtype=object)

上面的方法适用于点几何图形,但是因为在Shapely 1.x中,一些几何图形类型是类似序列的(见上),所以NumPy可以在创建数组时尝试对它们进行“解包”。因此,为了更健壮地从几何图形列表创建NumPy数组,通常建议使用两步法(首先创建一个空数组,然后填充它):

geoms = [Point(0, 0), Point(1, 1), Point(2, 2)]
arr = np.empty(len(geoms), dtype="object")
arr[:] = geoms

此代码片段产生与上面示例相同的数组,并且适用于所有几何体类型和Shapely/NumPy版本。

然而,从Shapely 1.8开始,上面的代码将显示无法避免的弃用警告(根据几何体类型,NumPy尝试访问对象的数组接口或检查对象是否可迭代或具有长度,这些操作现在都已弃用。最终结果仍然是正确的,但警告出现了)。具体地说,在这种情况下,忽略这些警告是可以的(也是让它们消失的唯一方法):

import warnings
from shapely.errors import ShapelyDeprecationWarning

geoms = [Point(0, 0), Point(1, 1), Point(2, 2)]
arr = np.empty(len(geoms), dtype="object")

with warnings.catch_warnings():
    warnings.filterwarnings("ignore", category=ShapelyDeprecationWarning)
    arr[:] = geoms

在Shapely 2.0中,几何对象将不再是类似的顺序,这些不推荐使用的警告将被移除(因此 filterwarnings 将不再需要),并且NumPy数组的创建通常将更加健壮。

如果您维护依赖Shaply的代码,并且希望它与Shaply的多个版本一起工作,则上面的代码片段提供了一个可以复制到您的项目中的上下文管理器:

import contextlib
import shapely
import warnings
from distutils.version import LooseVersion

SHAPELY_GE_20 = str(shapely.__version__) >= LooseVersion("2.0")

try:
    from shapely.errors import ShapelyDeprecationWarning as shapely_warning
except ImportError:
    shapely_warning = None

if shapely_warning is not None and not SHAPELY_GE_20:
    @contextlib.contextmanager
    def ignore_shapely2_warnings():
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore", category=shapely_warning)
            yield
else:
    @contextlib.contextmanager
    def ignore_shapely2_warnings():
        yield

然后可以在创建NumPy数组时使用它(请注意 only 将其用于此特定目的,而不是通常取消这些警告):

geoms = [...]
arr = np.empty(len(geoms), dtype="object")
with ignore_shapely2_warnings():
    arr[:] = geoms

一致地创建空几何图形

Shapely 1.x在创建各种创建方法之间的空几何图形时不一致。空多边形几何体的一个小示例:

# Using an empty constructor results in a GeometryCollection
>>> from shapely.geometry import Polygon
>>> g1 = Polygon()
>>> type(g1)
<class 'shapely.geometry.polygon.Polygon'>
>>> g1.wkt
GEOMETRYCOLLECTION EMPTY

# Converting from WKT gives a correct empty polygon
>>> from shapely import wkt
>>> g2 = wkt.loads("POLYGON EMPTY")
>>> type(g2)
<class 'shapely.geometry.polygon.Polygon'>
>>> g2.wkt
POLYGON EMPTY

Shapely 1.8尚未改变这种不一致的行为,但从Shapely 2.0开始,不同的方法将始终一致地提供正确类型的空几何图形对象,而不是将空GeometryCollection用作“通用”空几何图形对象。

How do I update my code? 那些将发生更改的情况不会引发警告,但如果您依赖空几何图形对象属于GeometryCollection类型这一事实,则需要更新代码。使用 .is_empty 属性用于可靠地检查几何图形对象是否为空几何图形。

此外,WKB序列化方法将开始支持空点(使用 "POINT (NaN NaN)" 表示空点)。

其他不推荐使用的功能

Shapely 1.8中还弃用了其他一些不同的函数和方法:

  • Shapely 2.0中将不再支持使用存储在Shapely几何外部的坐标创建类似几何的代理对象的适配器(例如,使用 asShape() )。与普通几何图形类相比,它们几乎没有任何好处,因此您可以将数据转换为普通几何图形对象。使用 shape() 函数将类似GeoJSON的词典转换为形状良好的几何体。

  • 这个 empty() 几何对象上的方法已弃用。

  • 这个 shapely.ops.cascaded_union 函数已弃用。使用 shapely.ops.unary_union 相反,它已经在内部使用了级联联合操作以获得更好的性能。