set_operations.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. """Set-theoretic operations on geometry objects."""
  2. import warnings
  3. import numpy as np
  4. from shapely import Geometry, GeometryType, lib
  5. from shapely.decorators import (
  6. deprecate_positional,
  7. multithreading_enabled,
  8. requires_geos,
  9. )
  10. __all__ = [
  11. "coverage_union",
  12. "coverage_union_all",
  13. "difference",
  14. "disjoint_subset_union",
  15. "disjoint_subset_union_all",
  16. "intersection",
  17. "intersection_all",
  18. "symmetric_difference",
  19. "symmetric_difference_all",
  20. "unary_union",
  21. "union",
  22. "union_all",
  23. ]
  24. # Note: future plan is to change this signature over a few releases:
  25. # shapely 2.0:
  26. # difference(a, b, grid_size=None, **kwargs)
  27. # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
  28. # same signature as 2.0
  29. # shapely 2.2(?): enforce keyword-only arguments after 'b'
  30. # difference(a, b, *, grid_size=None, **kwargs)
  31. @deprecate_positional(["grid_size"], category=DeprecationWarning)
  32. @multithreading_enabled
  33. def difference(a, b, grid_size=None, **kwargs):
  34. """Return the part of geometry A that does not intersect with geometry B.
  35. If grid_size is nonzero, input coordinates will be snapped to a precision
  36. grid of that size and resulting coordinates will be snapped to that same
  37. grid. If 0, this operation will use double precision coordinates. If None,
  38. the highest precision of the inputs will be used, which may be previously
  39. set using set_precision. Note: returned geometry does not have precision
  40. set unless specified previously by set_precision.
  41. Parameters
  42. ----------
  43. a : Geometry or array_like
  44. Geometry or geometries to subtract b from.
  45. b : Geometry or array_like
  46. Geometry or geometries to subtract from a.
  47. grid_size : float, optional
  48. Precision grid size; will use the highest precision of the inputs by default.
  49. **kwargs
  50. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  51. Notes
  52. -----
  53. .. deprecated:: 2.1.0
  54. A deprecation warning is shown if ``grid_size`` is specified as a
  55. positional argument. This will need to be specified as a keyword
  56. argument in a future release.
  57. See Also
  58. --------
  59. set_precision
  60. Examples
  61. --------
  62. >>> import shapely
  63. >>> from shapely import LineString
  64. >>> line = LineString([(0, 0), (2, 2)])
  65. >>> shapely.difference(line, LineString([(1, 1), (3, 3)]))
  66. <LINESTRING (0 0, 1 1)>
  67. >>> shapely.difference(line, LineString())
  68. <LINESTRING (0 0, 2 2)>
  69. >>> shapely.difference(line, None) is None
  70. True
  71. >>> box1 = shapely.box(0, 0, 2, 2)
  72. >>> box2 = shapely.box(1, 1, 3, 3)
  73. >>> shapely.difference(box1, box2).normalize()
  74. <POLYGON ((0 0, 0 2, 1 2, 1 1, 2 1, 2 0, 0 0))>
  75. >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
  76. >>> shapely.difference(box1, box2, grid_size=1)
  77. <POLYGON ((2 0, 0 0, 0 2, 1 2, 1 1, 2 1, 2 0))>
  78. """
  79. if grid_size is not None:
  80. if not np.isscalar(grid_size):
  81. raise ValueError("grid_size parameter only accepts scalar values")
  82. return lib.difference_prec(a, b, grid_size, **kwargs)
  83. return lib.difference(a, b, **kwargs)
  84. # Note: future plan is to change this signature over a few releases:
  85. # shapely 2.0:
  86. # intersection(a, b, grid_size=None, **kwargs)
  87. # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
  88. # same signature as 2.0
  89. # shapely 2.2(?): enforce keyword-only arguments after 'b'
  90. # intersection(a, b, *, grid_size=None, **kwargs)
  91. @deprecate_positional(["grid_size"], category=DeprecationWarning)
  92. @multithreading_enabled
  93. def intersection(a, b, grid_size=None, **kwargs):
  94. """Return the geometry that is shared between input geometries.
  95. If grid_size is nonzero, input coordinates will be snapped to a precision
  96. grid of that size and resulting coordinates will be snapped to that same
  97. grid. If 0, this operation will use double precision coordinates. If None,
  98. the highest precision of the inputs will be used, which may be previously
  99. set using set_precision. Note: returned geometry does not have precision
  100. set unless specified previously by set_precision.
  101. Parameters
  102. ----------
  103. a, b : Geometry or array_like
  104. Geometry or geometries to intersect with.
  105. grid_size : float, optional
  106. Precision grid size; will use the highest precision of the inputs by default.
  107. **kwargs
  108. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  109. Notes
  110. -----
  111. .. deprecated:: 2.1.0
  112. A deprecation warning is shown if ``grid_size`` is specified as a
  113. positional argument. This will need to be specified as a keyword
  114. argument in a future release.
  115. See Also
  116. --------
  117. intersection_all
  118. set_precision
  119. Examples
  120. --------
  121. >>> import shapely
  122. >>> from shapely import LineString
  123. >>> line = LineString([(0, 0), (2, 2)])
  124. >>> shapely.intersection(line, LineString([(1, 1), (3, 3)]))
  125. <LINESTRING (1 1, 2 2)>
  126. >>> box1 = shapely.box(0, 0, 2, 2)
  127. >>> box2 = shapely.box(1, 1, 3, 3)
  128. >>> shapely.intersection(box1, box2).normalize()
  129. <POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))>
  130. >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
  131. >>> shapely.intersection(box1, box2, grid_size=1)
  132. <POLYGON ((2 2, 2 1, 1 1, 1 2, 2 2))>
  133. """
  134. if grid_size is not None:
  135. if not np.isscalar(grid_size):
  136. raise ValueError("grid_size parameter only accepts scalar values")
  137. return lib.intersection_prec(a, b, grid_size, **kwargs)
  138. return lib.intersection(a, b, **kwargs)
  139. # Note: future plan is to change this signature over a few releases:
  140. # shapely 2.0:
  141. # intersection_all(geometries, axis=None, **kwargs)
  142. # shapely 2.1: shows deprecation warning about positional 'axis' arg
  143. # same signature as 2.0
  144. # shapely 2.2(?): enforce keyword-only arguments after 'geometries'
  145. # intersection_all(geometries, *, axis=None, **kwargs)
  146. @deprecate_positional(["axis"], category=DeprecationWarning)
  147. @multithreading_enabled
  148. def intersection_all(geometries, axis=None, **kwargs):
  149. """Return the intersection of multiple geometries.
  150. This function ignores None values when other Geometry elements are present.
  151. If all elements of the given axis are None, an empty GeometryCollection is
  152. returned.
  153. Parameters
  154. ----------
  155. geometries : array_like
  156. Geometries to calculate the intersection of.
  157. axis : int, optional
  158. Axis along which the operation is performed. The default (None)
  159. performs the operation over all axes, returning a scalar value.
  160. Axis may be negative, in which case it counts from the last to the
  161. first axis.
  162. **kwargs
  163. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  164. Notes
  165. -----
  166. .. deprecated:: 2.1.0
  167. A deprecation warning is shown if ``axis`` is specified as a
  168. positional argument. This will need to be specified as a keyword
  169. argument in a future release.
  170. See Also
  171. --------
  172. intersection
  173. Examples
  174. --------
  175. >>> import shapely
  176. >>> from shapely import LineString
  177. >>> line1 = LineString([(0, 0), (2, 2)])
  178. >>> line2 = LineString([(1, 1), (3, 3)])
  179. >>> shapely.intersection_all([line1, line2])
  180. <LINESTRING (1 1, 2 2)>
  181. >>> shapely.intersection_all([[line1, line2, None]], axis=1).tolist()
  182. [<LINESTRING (1 1, 2 2)>]
  183. >>> shapely.intersection_all([line1, None])
  184. <LINESTRING (0 0, 2 2)>
  185. """
  186. geometries = np.asarray(geometries)
  187. if axis is None:
  188. geometries = geometries.ravel()
  189. else:
  190. geometries = np.rollaxis(geometries, axis=axis, start=geometries.ndim)
  191. return lib.intersection_all(geometries, **kwargs)
  192. # Note: future plan is to change this signature over a few releases:
  193. # shapely 2.0:
  194. # symmetric_difference(a, b, grid_size=None, **kwargs)
  195. # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
  196. # same signature as 2.0
  197. # shapely 2.2(?): enforce keyword-only arguments after 'b'
  198. # symmetric_difference(a, b, *, grid_size=None, **kwargs)
  199. @deprecate_positional(["grid_size"], category=DeprecationWarning)
  200. @multithreading_enabled
  201. def symmetric_difference(a, b, grid_size=None, **kwargs):
  202. """Return the geometry with the portions of input geometries that do not intersect.
  203. If grid_size is nonzero, input coordinates will be snapped to a precision
  204. grid of that size and resulting coordinates will be snapped to that same
  205. grid. If 0, this operation will use double precision coordinates. If None,
  206. the highest precision of the inputs will be used, which may be previously
  207. set using set_precision. Note: returned geometry does not have precision
  208. set unless specified previously by set_precision.
  209. Parameters
  210. ----------
  211. a, b : Geometry or array_like
  212. Geometry or geometries to evaluate symmetric difference with.
  213. grid_size : float, optional
  214. Precision grid size; will use the highest precision of the inputs by default.
  215. **kwargs
  216. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  217. Notes
  218. -----
  219. .. deprecated:: 2.1.0
  220. A deprecation warning is shown if ``grid_size`` is specified as a
  221. positional argument. This will need to be specified as a keyword
  222. argument in a future release.
  223. See Also
  224. --------
  225. symmetric_difference_all
  226. set_precision
  227. Examples
  228. --------
  229. >>> import shapely
  230. >>> from shapely import LineString
  231. >>> line = LineString([(0, 0), (2, 2)])
  232. >>> shapely.symmetric_difference(line, LineString([(1, 1), (3, 3)]))
  233. <MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))>
  234. >>> box1 = shapely.box(0, 0, 2, 2)
  235. >>> box2 = shapely.box(1, 1, 3, 3)
  236. >>> shapely.symmetric_difference(box1, box2).normalize()
  237. <MULTIPOLYGON (((1 2, 1 3, 3 3, 3 1, 2 1, 2 2, 1 2)), ((0 0, 0 2, 1 2, 1 1, ...>
  238. >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
  239. >>> shapely.symmetric_difference(box1, box2, grid_size=1)
  240. <MULTIPOLYGON (((2 0, 0 0, 0 2, 1 2, 1 1, 2 1, 2 0)), ((2 2, 1 2, 1 3, 3 3, ...>
  241. """
  242. if grid_size is not None:
  243. if not np.isscalar(grid_size):
  244. raise ValueError("grid_size parameter only accepts scalar values")
  245. return lib.symmetric_difference_prec(a, b, grid_size, **kwargs)
  246. return lib.symmetric_difference(a, b, **kwargs)
  247. # Note: future plan is to change this signature over a few releases:
  248. # shapely 2.0:
  249. # symmetric_difference_all(geometries, axis=None, **kwargs)
  250. # shapely 2.1: shows deprecation warning about positional 'axis' arg
  251. # same signature as 2.0
  252. # shapely 2.2(?): enforce keyword-only arguments after 'geometries'
  253. # symmetric_difference_all(geometries, *, axis=None, **kwargs)
  254. @deprecate_positional(["axis"], category=DeprecationWarning)
  255. @multithreading_enabled
  256. def symmetric_difference_all(geometries, axis=None, **kwargs):
  257. """Return the symmetric difference of multiple geometries.
  258. This function ignores None values when other Geometry elements are present.
  259. If all elements of the given axis are None an empty GeometryCollection is
  260. returned.
  261. .. deprecated:: 2.1.0
  262. This function behaves incorrectly and will be removed in a future
  263. version. See https://github.com/shapely/shapely/issues/2027 for more
  264. details.
  265. Parameters
  266. ----------
  267. geometries : array_like
  268. Geometries to calculate the combined symmetric difference of.
  269. axis : int, optional
  270. Axis along which the operation is performed. The default (None)
  271. performs the operation over all axes, returning a scalar value.
  272. Axis may be negative, in which case it counts from the last to the
  273. first axis.
  274. **kwargs
  275. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  276. Notes
  277. -----
  278. .. deprecated:: 2.1.0
  279. A deprecation warning is shown if ``axis`` is specified as a
  280. positional argument. This will need to be specified as a keyword
  281. argument in a future release.
  282. See Also
  283. --------
  284. symmetric_difference
  285. Examples
  286. --------
  287. >>> import shapely
  288. >>> from shapely import LineString
  289. >>> line1 = LineString([(0, 0), (2, 2)])
  290. >>> line2 = LineString([(1, 1), (3, 3)])
  291. >>> shapely.symmetric_difference_all([line1, line2])
  292. <MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))>
  293. >>> shapely.symmetric_difference_all([[line1, line2, None]], axis=1).tolist()
  294. [<MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))>]
  295. >>> shapely.symmetric_difference_all([line1, None])
  296. <LINESTRING (0 0, 2 2)>
  297. >>> shapely.symmetric_difference_all([None, None])
  298. <GEOMETRYCOLLECTION EMPTY>
  299. """
  300. warnings.warn(
  301. "The symmetric_difference_all function behaves incorrectly and will be "
  302. "removed in a future version. "
  303. "See https://github.com/shapely/shapely/issues/2027 for more details.",
  304. DeprecationWarning,
  305. stacklevel=2,
  306. )
  307. geometries = np.asarray(geometries)
  308. if axis is None:
  309. geometries = geometries.ravel()
  310. else:
  311. geometries = np.rollaxis(geometries, axis=axis, start=geometries.ndim)
  312. return lib.symmetric_difference_all(geometries, **kwargs)
  313. # Note: future plan is to change this signature over a few releases:
  314. # shapely 2.0:
  315. # union(a, b, grid_size=None, **kwargs)
  316. # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
  317. # same signature as 2.0
  318. # shapely 2.2(?): enforce keyword-only arguments after 'b'
  319. # union(a, b, *, grid_size=None, **kwargs)
  320. @deprecate_positional(["grid_size"], category=DeprecationWarning)
  321. @multithreading_enabled
  322. def union(a, b, grid_size=None, **kwargs):
  323. """Merge geometries into one.
  324. If grid_size is nonzero, input coordinates will be snapped to a precision
  325. grid of that size and resulting coordinates will be snapped to that same
  326. grid. If 0, this operation will use double precision coordinates. If None,
  327. the highest precision of the inputs will be used, which may be previously
  328. set using set_precision. Note: returned geometry does not have precision
  329. set unless specified previously by set_precision.
  330. Parameters
  331. ----------
  332. a, b : Geometry or array_like
  333. Geometry or geometries to merge (union).
  334. grid_size : float, optional
  335. Precision grid size; will use the highest precision of the inputs by default.
  336. **kwargs
  337. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  338. Notes
  339. -----
  340. .. deprecated:: 2.1.0
  341. A deprecation warning is shown if ``grid_size`` is specified as a
  342. positional argument. This will need to be specified as a keyword
  343. argument in a future release.
  344. See Also
  345. --------
  346. union_all
  347. set_precision
  348. Examples
  349. --------
  350. >>> import shapely
  351. >>> from shapely import LineString
  352. >>> line = LineString([(0, 0), (2, 2)])
  353. >>> shapely.union(line, LineString([(2, 2), (3, 3)]))
  354. <MULTILINESTRING ((0 0, 2 2), (2 2, 3 3))>
  355. >>> shapely.union(line, None) is None
  356. True
  357. >>> box1 = shapely.box(0, 0, 2, 2)
  358. >>> box2 = shapely.box(1, 1, 3, 3)
  359. >>> shapely.union(box1, box2).normalize()
  360. <POLYGON ((0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1, 2 0, 0 0))>
  361. >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
  362. >>> shapely.union(box1, box2, grid_size=1)
  363. <POLYGON ((2 0, 0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1, 2 0))>
  364. """
  365. if grid_size is not None:
  366. if not np.isscalar(grid_size):
  367. raise ValueError("grid_size parameter only accepts scalar values")
  368. return lib.union_prec(a, b, grid_size, **kwargs)
  369. return lib.union(a, b, **kwargs)
  370. # Note: future plan is to change this signature over a few releases:
  371. # shapely 2.0:
  372. # union_all(geometries, grid_size=None, axis=None, **kwargs)
  373. # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
  374. # same signature as 2.0
  375. # shapely 2.2(?): enforce keyword-only arguments after 'geometries'
  376. # union_all(geometries, *, grid_size=None, axis=None, **kwargs)
  377. @deprecate_positional(["grid_size", "axis"], category=DeprecationWarning)
  378. @multithreading_enabled
  379. def union_all(geometries, grid_size=None, axis=None, **kwargs):
  380. """Return the union of multiple geometries.
  381. This function ignores None values when other Geometry elements are present.
  382. If all elements of the given axis are None an empty GeometryCollection is
  383. returned.
  384. If grid_size is nonzero, input coordinates will be snapped to a precision
  385. grid of that size and resulting coordinates will be snapped to that same
  386. grid. If 0, this operation will use double precision coordinates. If None,
  387. the highest precision of the inputs will be used, which may be previously
  388. set using set_precision. Note: returned geometry does not have precision
  389. set unless specified previously by set_precision.
  390. `unary_union` is an alias of `union_all`.
  391. Parameters
  392. ----------
  393. geometries : array_like
  394. Geometries to merge/union.
  395. grid_size : float, optional
  396. Precision grid size; will use the highest precision of the inputs by default.
  397. axis : int, optional
  398. Axis along which the operation is performed. The default (None)
  399. performs the operation over all axes, returning a scalar value.
  400. Axis may be negative, in which case it counts from the last to the
  401. first axis.
  402. **kwargs
  403. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  404. Notes
  405. -----
  406. .. deprecated:: 2.1.0
  407. A deprecation warning is shown if ``grid_size`` or ``axis`` are
  408. specified as positional arguments. In a future release, these will
  409. need to be specified as keyword arguments.
  410. See Also
  411. --------
  412. union
  413. set_precision
  414. Examples
  415. --------
  416. >>> import shapely
  417. >>> from shapely import LineString, Point
  418. >>> line1 = LineString([(0, 0), (2, 2)])
  419. >>> line2 = LineString([(2, 2), (3, 3)])
  420. >>> shapely.union_all([line1, line2])
  421. <MULTILINESTRING ((0 0, 2 2), (2 2, 3 3))>
  422. >>> shapely.union_all([[line1, line2, None]], axis=1).tolist()
  423. [<MULTILINESTRING ((0 0, 2 2), (2 2, 3 3))>]
  424. >>> box1 = shapely.box(0, 0, 2, 2)
  425. >>> box2 = shapely.box(1, 1, 3, 3)
  426. >>> shapely.union_all([box1, box2]).normalize()
  427. <POLYGON ((0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1, 2 0, 0 0))>
  428. >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
  429. >>> shapely.union_all([box1, box2], grid_size=1)
  430. <POLYGON ((2 0, 0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1, 2 0))>
  431. >>> shapely.union_all([None, Point(0, 1)])
  432. <POINT (0 1)>
  433. >>> shapely.union_all([None, None])
  434. <GEOMETRYCOLLECTION EMPTY>
  435. >>> shapely.union_all([])
  436. <GEOMETRYCOLLECTION EMPTY>
  437. """
  438. # for union_all, GEOS provides an efficient route through first creating
  439. # GeometryCollections
  440. # first roll the aggregation axis backwards
  441. geometries = np.asarray(geometries)
  442. if axis is None:
  443. geometries = geometries.ravel()
  444. else:
  445. geometries = np.rollaxis(geometries, axis=axis, start=geometries.ndim)
  446. # create_collection acts on the inner axis
  447. collections = lib.create_collection(
  448. geometries, np.intc(GeometryType.GEOMETRYCOLLECTION)
  449. )
  450. if grid_size is not None:
  451. if not np.isscalar(grid_size):
  452. raise ValueError("grid_size parameter only accepts scalar values")
  453. return lib.unary_union_prec(collections, grid_size, **kwargs)
  454. return lib.unary_union(collections, **kwargs)
  455. unary_union = union_all
  456. @multithreading_enabled
  457. def coverage_union(a, b, **kwargs):
  458. """Merge multiple polygons into one.
  459. This is an optimized version of union which assumes the polygons to be
  460. non-overlapping.
  461. If this assumption is not met, the exact result is not guaranteed
  462. (depending on the GEOS version, it may return the input unchanged or raise
  463. an error).
  464. Parameters
  465. ----------
  466. a, b : Geometry or array_like
  467. Geometry or geometries to merge (union).
  468. **kwargs
  469. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  470. See Also
  471. --------
  472. coverage_union_all
  473. Examples
  474. --------
  475. >>> import shapely
  476. >>> from shapely import Polygon
  477. >>> polygon_1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
  478. >>> polygon_2 = Polygon([(1, 0), (1, 1), (2, 1), (2, 0), (1, 0)])
  479. >>> shapely.coverage_union(polygon_1, polygon_2).normalize()
  480. <POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))>
  481. Union with None returns same polygon
  482. >>> shapely.coverage_union(polygon_1, None).normalize()
  483. <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
  484. """
  485. return coverage_union_all([a, b], **kwargs)
  486. # Note: future plan is to change this signature over a few releases:
  487. # shapely 2.0:
  488. # coverage_union_all(geometries, axis=None, **kwargs)
  489. # shapely 2.1: shows deprecation warning about positional 'axis' arg
  490. # same signature as 2.0
  491. # shapely 2.2(?): enforce keyword-only arguments after 'geometries'
  492. # coverage_union_all(geometries, *, axis=None, **kwargs)
  493. @deprecate_positional(["axis"], category=DeprecationWarning)
  494. @multithreading_enabled
  495. def coverage_union_all(geometries, axis=None, **kwargs):
  496. """Return the union of multiple polygons of a geometry collection.
  497. This is an optimized version of union which assumes the polygons
  498. to be non-overlapping.
  499. This function ignores None values when other Geometry elements are present.
  500. If all elements of the given axis are None, an empty GeometryCollection is
  501. returned (before GEOS 3.12 this was an empty MultiPolygon).
  502. Parameters
  503. ----------
  504. geometries : array_like
  505. Geometries to merge/union.
  506. axis : int, optional
  507. Axis along which the operation is performed. The default (None)
  508. performs the operation over all axes, returning a scalar value.
  509. Axis may be negative, in which case it counts from the last to the
  510. first axis.
  511. **kwargs
  512. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  513. Notes
  514. -----
  515. .. deprecated:: 2.1.0
  516. A deprecation warning is shown if ``axis`` is specified as a
  517. positional argument. This will need to be specified as a keyword
  518. argument in a future release.
  519. See Also
  520. --------
  521. coverage_union
  522. Examples
  523. --------
  524. >>> import shapely
  525. >>> from shapely import Polygon
  526. >>> polygon_1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
  527. >>> polygon_2 = Polygon([(1, 0), (1, 1), (2, 1), (2, 0), (1, 0)])
  528. >>> shapely.coverage_union_all([polygon_1, polygon_2]).normalize()
  529. <POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))>
  530. >>> shapely.coverage_union_all([polygon_1, None]).normalize()
  531. <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
  532. >>> shapely.coverage_union_all([None, None]).normalize()
  533. <GEOMETRYCOLLECTION EMPTY>
  534. """
  535. # coverage union in GEOS works over GeometryCollections
  536. # first roll the aggregation axis backwards
  537. geometries = np.asarray(geometries)
  538. if axis is None:
  539. geometries = geometries.ravel()
  540. else:
  541. geometries = np.rollaxis(
  542. np.asarray(geometries), axis=axis, start=geometries.ndim
  543. )
  544. # create_collection acts on the inner axis
  545. collections = lib.create_collection(
  546. geometries, np.intc(GeometryType.GEOMETRYCOLLECTION)
  547. )
  548. return lib.coverage_union(collections, **kwargs)
  549. @requires_geos("3.12.0")
  550. @multithreading_enabled
  551. def disjoint_subset_union(a, b, **kwargs):
  552. """Merge multiple polygons into one using algorithm optimised for subsets.
  553. This is an optimized version of union which assumes inputs can be
  554. divided into subsets that do not intersect.
  555. If there is only one such subset, performance can be expected to be worse than
  556. :func:`union`. As such, it is recommeded to use ``disjoint_subset_union`` with
  557. GeometryCollections rather than individual geometries.
  558. .. versionadded:: 2.1.0
  559. Parameters
  560. ----------
  561. a, b : Geometry or array_like
  562. Geometry or geometries to merge (union).
  563. **kwargs
  564. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  565. See Also
  566. --------
  567. union
  568. coverage_union
  569. disjoint_subset_union_all
  570. Examples
  571. --------
  572. >>> import shapely
  573. >>> from shapely import Polygon
  574. >>> polygon_1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
  575. >>> polygon_2 = Polygon([(1, 0), (1, 1), (2, 1), (2, 0), (1, 0)])
  576. >>> shapely.disjoint_subset_union(polygon_1, polygon_2).normalize()
  577. <POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))>
  578. Union with None returns same polygon:
  579. >>> shapely.disjoint_subset_union(polygon_1, None).normalize()
  580. <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
  581. """
  582. if (isinstance(a, Geometry) or a is None) and (
  583. isinstance(b, Geometry) or b is None
  584. ):
  585. pass
  586. elif isinstance(a, Geometry) or a is None:
  587. a = np.full_like(b, a)
  588. elif isinstance(b, Geometry) or b is None:
  589. b = np.full_like(a, b)
  590. elif len(a) != len(b):
  591. raise ValueError("Arrays a and b must have the same length")
  592. return disjoint_subset_union_all([a, b], axis=0, **kwargs)
  593. @requires_geos("3.12.0")
  594. @multithreading_enabled
  595. def disjoint_subset_union_all(geometries, *, axis=None, **kwargs):
  596. """Return the union of multiple polygons.
  597. This is an optimized version of union which assumes inputs can be divided into
  598. subsets that do not intersect.
  599. If there is only one such subset, performance can be expected to be worse than
  600. :func:`union_all`.
  601. This function ignores None values when other Geometry elements are present.
  602. If all elements of the given axis are None, an empty GeometryCollection is
  603. returned.
  604. .. versionadded:: 2.1.0
  605. Parameters
  606. ----------
  607. geometries : array_like
  608. Geometries to union.
  609. axis : int, optional
  610. Axis along which the operation is performed. The default (None)
  611. performs the operation over all axes, returning a scalar value.
  612. Axis may be negative, in which case it counts from the last to the
  613. first axis.
  614. **kwargs
  615. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  616. See Also
  617. --------
  618. coverage_union_all
  619. union_all
  620. disjoint_subset_union
  621. Examples
  622. --------
  623. >>> import shapely
  624. >>> from shapely import Polygon
  625. >>> polygon_1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
  626. >>> polygon_2 = Polygon([(1, 0), (1, 1), (2, 1), (2, 0), (1, 0)])
  627. >>> shapely.disjoint_subset_union_all([polygon_1, polygon_2]).normalize()
  628. <POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))>
  629. >>> shapely.disjoint_subset_union_all([polygon_1, None]).normalize()
  630. <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
  631. >>> shapely.disjoint_subset_union_all([None, None]).normalize()
  632. <GEOMETRYCOLLECTION EMPTY>
  633. """
  634. geometries = np.asarray(geometries)
  635. if axis is None:
  636. geometries = geometries.ravel()
  637. else:
  638. geometries = np.rollaxis(
  639. np.asarray(geometries), axis=axis, start=geometries.ndim
  640. )
  641. # create_collection acts on the inner axis
  642. collections = lib.create_collection(
  643. geometries, np.intc(GeometryType.GEOMETRYCOLLECTION)
  644. )
  645. return lib.disjoint_subset_union(collections, **kwargs)