File: migration.rst

package info (click to toggle)
python-shapely 2.1.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,528 kB
  • sloc: python: 18,648; ansic: 6,615; makefile: 88; sh: 62
file content (347 lines) | stat: -rw-r--r-- 13,061 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
.. _migration:

==============================
Migrating to Shapely 1.8 / 2.0
==============================

Shapely 1.8.0 is a transitional version introducing several warnings in
transition to 2.0.0.

Shapely 2.0.0 was a major release with a refactor of the internals with
considerable performance improvements (based on the developments in the
`PyGEOS <https://github.com/pygeos/pygeos>`__ package), along with several
breaking changes.

This guide gives an overview of the most important changes with details
on what will change in 2.0.0, how we warn for this in 1.8.0, and how
you can update your code to be future-proof.

For more background, see
`RFC 1: Roadmap for Shapely 2.0 <https://github.com/shapely/shapely-rfc/pull/1>`__.


Geometry objects will become immutable
======================================

Geometry objects will become immutable in version 2.0.0.

In Shapely 1.x, some of the geometry classes are mutable, meaning that you
can change their coordinates in-place. Illustrative code::

    >>> 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)]  # doctest: +SKIP
    >>> print(line)  # doctest: +SKIP
    LINESTRING (0 0, 10 0, 10 10)

In Shapely 1.8, this will start raising a warning::

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

and starting with version 2.0.0, all geometry objects will become immutable.
As a consequence, they will also become hashable and therefore usable as, for
example, dictionary keys.

**How do I update my code?** There is no direct alternative for mutating the
coordinates of an existing geometry, except for creating a new geometry
object with the new coordinates.


Setting custom attributes
-------------------------

Another consequence of the geometry objects becoming immutable is that
assigning custom attributes, which previously worked, will no longer be
possible.

Previously, custom attributes could have ben added::

    >>> line.name = "my_geometry"  # doctest: +SKIP
    >>> line.name  # doctest: +SKIP
    'my_geometry'

In Shapely 1.8, this will raise a warning, and will raise an
AttributeError in Shapely 2.0.

**How do I update my code?** There is no direct alternative for adding custom
attributes to geometry objects. You can use other Python data structures such
as (GeoJSON-like) dictionaries or GeoPandas' GeoDataFrames to store attributes
alongside geometry features.

Multi-part geometries will no longer be "sequences" (length, iterable, indexable)
=================================================================================

In Shapely 1.x, multi-part geometries (MultiPoint, MultiLineString,
MultiPolygon and GeometryCollection) implement a part of the "sequence"
python interface (making them list-like). This means you can iterate through
the object to get the parts, index into the object to get a specific part,
and ask for the number of parts with the ``len()`` method.

Some examples of this with Shapely 1.x:

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

Starting with Shapely 1.8, all the examples above will start raising a
deprecation warning. For example:

    >>> for part in mp:  # doctest: +SKIP
    ...     print(part)  # doctest: +SKIP
    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)

In Shapely 2.0, all those examples will raise an error.

**How do I update my code?** To access the geometry parts of a multi-part
geometry, you can use the ``.geoms`` attribute, as the warning indicates.

The examples above can be updated to::

    >>> 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)  # doctest: +SKIP
    [<shapely.geometry.point.Point at 0x7f2e0912bf10>,
     <shapely.geometry.point.Point at 0x7f2e09fed820>,
     <shapely.geometry.point.Point at 0x7f2e09fed4c0>]

The single-part geometries (Point, LineString, Polygon) already didn't
support those features, and for those classes there is no change in behaviour
for this aspect.


Interoperability with NumPy and the array interface
===================================================

Conversion of the coordinates to (NumPy) arrays
-----------------------------------------------

Shapely provides an array interface to have easy access to the coordinates as,
for example, NumPy arrays (:ref:`manual section <array-interface>`).

A small example::

    >>> line = LineString([(0, 0), (1, 1), (2, 2)])
    >>> import numpy as np
    >>> np.asarray(line)  # doctest: +SKIP
    array([[0., 0.],
           [1., 1.],
           [2., 2.]])

In addition, there are also the explicit ``array_interface()`` method and
``ctypes`` attribute to get access to the coordinates as array data:

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

This functionality is available for Point, LineString, LinearRing and
MultiPoint.

For more robust interoperability with NumPy, this array interface will be
removed from those geometry classes, and limited to the ``coords``.

Starting with Shapely 1.8, converting a geometry object to a NumPy array
directly will start raising a warning::

    >>> np.asarray(line)  # doctest: +SKIP
    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?** To convert a geometry to a NumPy array, you can
convert the ``.coords`` attribute instead::

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

The ``array_interface()`` method and ``ctypes`` attribute will be removed in
Shapely 2.0, but since Shapely will start requiring NumPy as a dependency,
you can use NumPy or its array interface directly. Check the NumPy docs on
the :py:attr:`ctypes <numpy:numpy.ndarray.ctypes>` attribute
or the :ref:`array interface <numpy:arrays.interface>` for more details.

Creating NumPy arrays of geometry objects
-----------------------------------------

Shapely geometry objects can be stored in NumPy arrays using the ``object``
dtype. In general, one could create such an array from a list of geometries
as follows::

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

The above works for point geometries, but because in Shapely 1.x, some
geometry types are sequence-like (see above), NumPy can try to "unpack" them
when creating an array. Therefore, for more robust creation of a NumPy array
from a list of geometries, it's generally recommended to this in a two-step
way (first creating an empty array and then filling it)::

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

This code snippet results in the same array as the example above, and works
for all geometry types and Shapely/NumPy versions.

However, starting with Shapely 1.8, the above code will show deprecation
warnings that cannot be avoided (depending on the geometry type, NumPy tries
to access the array interface of the objects or check if an object is
iterable or has a length, and those operations are all deprecated now. The
end result is still correct, but the warnings appear nonetheless).
Specifically in this case, it is fine to ignore those warnings (and the only
way to make them go away)::

    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

In Shapely 2.0, the geometry objects will no longer be sequence like and
those deprecation warnings will be removed (and thus the ``filterwarnings``
will no longer be necessary), and creation of NumPy arrays will generally be
more robust.

If you maintain code that depends on Shapely, and you want to have it work
with multiple versions of Shapely, the above code snippet provides a context
manager that can be copied into your project::

    import contextlib
    import shapely
    import warnings
    from packaging import version  # https://packaging.pypa.io/

    SHAPELY_GE_20 = version.parse(shapely.__version__) >= version.parse("2.0a1")

    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

This can then be used when creating NumPy arrays (be careful to *only* use it
for this specific purpose, and not generally suppress those warnings)::

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


Consistent creation of empty geometries
=======================================

Shapely 1.x is inconsistent in creating empty geometries between various
creation methods. A small example for an empty Polygon geometry:

.. code-block:: pycon

    # Using an empty constructor results in a GeometryCollection
    >>> from shapely.geometry import Polygon
    >>> g1 = Polygon()
    >>> type(g1)
    <class 'shapely.geometry.polygon.Polygon'>
    >>> g1.wkt  # doctest: +SKIP
    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 does not yet change this inconsistent behaviour, but starting
with Shapely 2.0, the different methods will always consistently give an
empty geometry object of the correct type, instead of using an empty
GeometryCollection as "generic" empty geometry object.

**How do I update my code?** Those cases that will change don't raise a
warning, but you will need to update your code if you rely on the fact that
empty geometry objects are of the GeometryCollection type. Use the
``.is_empty`` attribute for robustly checking if a geometry object is an
empty geometry.

In addition, the WKB serialization methods will start supporting empty
Points (using ``"POINT (NaN NaN)"`` to represent an empty point).


Other deprecated functionality
==============================

There are some other various functions and methods deprecated in Shapely 1.8
as well:

- The adapters to create geometry-like proxy objects with coordinates stored
  outside Shapely geometries are deprecated and will be removed in Shapely
  2.0 (e.g. created using ``asShape()``). They have little to no benefit
  compared to the normal geometry classes, as thus you can convert to your
  data to a normal geometry object instead. Use the ``shape()`` function
  instead to convert a GeoJSON-like dict to a Shapely geometry.

- The ``empty()`` method on a geometry object is deprecated.

- The ``shapely.ops.cascaded_union`` function is deprecated. Use
  ``shapely.ops.unary_union`` instead, which internally already uses a cascaded
  union operation for better performance.