test_constructive.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390
  1. import numpy as np
  2. import pytest
  3. import shapely
  4. from shapely import (
  5. Geometry,
  6. GeometryCollection,
  7. GEOSException,
  8. LinearRing,
  9. LineString,
  10. MultiLineString,
  11. MultiPoint,
  12. MultiPolygon,
  13. Point,
  14. Polygon,
  15. geos_version,
  16. )
  17. from shapely.errors import UnsupportedGEOSVersionError
  18. from shapely.testing import assert_geometries_equal
  19. from shapely.tests.common import (
  20. ArrayLike,
  21. all_types,
  22. empty,
  23. empty_line_string,
  24. empty_point,
  25. empty_polygon,
  26. ignore_invalid,
  27. line_string,
  28. multi_point,
  29. point,
  30. point_z,
  31. )
  32. geos39 = geos_version[0:2] == (3, 9)
  33. geos310 = geos_version[0:2] == (3, 10)
  34. geos311 = geos_version[0:2] == (3, 11)
  35. CONSTRUCTIVE_NO_ARGS = (
  36. shapely.boundary,
  37. shapely.centroid,
  38. shapely.convex_hull,
  39. pytest.param(
  40. shapely.concave_hull,
  41. marks=pytest.mark.skipif(
  42. shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11"
  43. ),
  44. ),
  45. shapely.envelope,
  46. shapely.extract_unique_points,
  47. shapely.minimum_clearance_line,
  48. shapely.node,
  49. shapely.normalize,
  50. shapely.point_on_surface,
  51. pytest.param(
  52. shapely.constrained_delaunay_triangles,
  53. marks=pytest.mark.skipif(
  54. shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10"
  55. ),
  56. ),
  57. )
  58. CONSTRUCTIVE_FLOAT_ARG = (
  59. shapely.buffer,
  60. shapely.offset_curve,
  61. shapely.delaunay_triangles,
  62. shapely.simplify,
  63. shapely.voronoi_polygons,
  64. )
  65. @pytest.mark.parametrize("geometry", all_types)
  66. @pytest.mark.parametrize("func", CONSTRUCTIVE_NO_ARGS)
  67. def test_no_args_array(geometry, func):
  68. if (
  69. geometry.is_empty
  70. and shapely.get_num_geometries(geometry) > 0
  71. and func is shapely.node
  72. and (
  73. (geos39 and geos_version < (3, 9, 3))
  74. or (geos310 and geos_version < (3, 10, 3))
  75. )
  76. ): # GEOS GH-601
  77. pytest.xfail("GEOS < 3.9.3 or GEOS < 3.10.3 crashes with empty geometries")
  78. actual = func([geometry, geometry])
  79. assert actual.shape == (2,)
  80. assert actual[0] is None or isinstance(actual[0], Geometry)
  81. @pytest.mark.parametrize("geometry", all_types)
  82. @pytest.mark.parametrize("func", CONSTRUCTIVE_FLOAT_ARG)
  83. def test_float_arg_array(geometry, func):
  84. if (
  85. func is shapely.offset_curve
  86. and shapely.get_type_id(geometry) not in [1, 2]
  87. and shapely.geos_version < (3, 11, 0)
  88. ):
  89. with pytest.raises(GEOSException, match="only accept linestrings"):
  90. func([geometry, geometry], 0.0)
  91. return
  92. # voronoi_polygons emits an "invalid" warning when supplied with an empty
  93. # point (see https://github.com/libgeos/geos/issues/515)
  94. with ignore_invalid(
  95. func is shapely.voronoi_polygons
  96. and shapely.get_type_id(geometry) == 0
  97. and shapely.geos_version < (3, 12, 0)
  98. ):
  99. actual = func([geometry, geometry], 0.0)
  100. assert actual.shape == (2,)
  101. assert isinstance(actual[0], Geometry)
  102. @pytest.mark.parametrize("geometry", all_types)
  103. @pytest.mark.parametrize("reference", all_types)
  104. def test_snap_array(geometry, reference):
  105. actual = shapely.snap([geometry, geometry], [reference, reference], tolerance=1.0)
  106. assert actual.shape == (2,)
  107. assert isinstance(actual[0], Geometry)
  108. @pytest.mark.parametrize("func", CONSTRUCTIVE_NO_ARGS)
  109. def test_no_args_missing(func):
  110. actual = func(None)
  111. assert actual is None
  112. @pytest.mark.parametrize("func", CONSTRUCTIVE_FLOAT_ARG)
  113. def test_float_arg_missing(func):
  114. actual = func(None, 1.0)
  115. assert actual is None
  116. @pytest.mark.parametrize("geometry", all_types)
  117. @pytest.mark.parametrize("func", CONSTRUCTIVE_FLOAT_ARG)
  118. def test_float_arg_nan(geometry, func):
  119. actual = func(geometry, float("nan"))
  120. assert actual is None
  121. def test_buffer_cap_style_invalid():
  122. with pytest.raises(ValueError, match="'invalid' is not a valid option"):
  123. shapely.buffer(point, 1, cap_style="invalid")
  124. def test_buffer_join_style_invalid():
  125. with pytest.raises(ValueError, match="'invalid' is not a valid option"):
  126. shapely.buffer(point, 1, join_style="invalid")
  127. def test_snap_none():
  128. actual = shapely.snap(None, point, tolerance=1.0)
  129. assert actual is None
  130. @pytest.mark.parametrize("geometry", all_types)
  131. def test_snap_nan_float(geometry):
  132. actual = shapely.snap(geometry, point, tolerance=np.nan)
  133. assert actual is None
  134. def test_build_area_none():
  135. actual = shapely.build_area(None)
  136. assert actual is None
  137. @pytest.mark.parametrize(
  138. "geom,expected",
  139. [
  140. (point, empty), # a point has no area
  141. (line_string, empty), # a line string has no area
  142. # geometry collection of two polygons are combined into one
  143. (
  144. GeometryCollection(
  145. [
  146. Polygon([(0, 0), (0, 3), (3, 3), (3, 0), (0, 0)]),
  147. Polygon([(1, 1), (2, 2), (1, 2), (1, 1)]),
  148. ]
  149. ),
  150. Polygon(
  151. [(0, 0), (0, 3), (3, 3), (3, 0), (0, 0)],
  152. holes=[[(1, 1), (2, 2), (1, 2), (1, 1)]],
  153. ),
  154. ),
  155. (empty, empty),
  156. ([empty], [empty]),
  157. ],
  158. )
  159. def test_build_area(geom, expected):
  160. actual = shapely.build_area(geom)
  161. assert actual is not expected
  162. assert actual == expected
  163. def test_make_valid_none():
  164. actual = shapely.make_valid(None)
  165. assert actual is None
  166. @pytest.mark.parametrize(
  167. "geom,expected",
  168. [
  169. (point, point), # a valid geometry stays the same (but is copied)
  170. # an L shaped polygon without area is converted to a multilinestring
  171. (
  172. Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]),
  173. MultiLineString([((1, 1), (1, 2)), ((0, 0), (1, 1))]),
  174. ),
  175. # a polygon with self-intersection (bowtie) is converted into polygons
  176. (
  177. Polygon([(0, 0), (2, 2), (2, 0), (0, 2), (0, 0)]),
  178. MultiPolygon(
  179. [
  180. Polygon([(1, 1), (2, 2), (2, 0), (1, 1)]),
  181. Polygon([(0, 0), (0, 2), (1, 1), (0, 0)]),
  182. ]
  183. ),
  184. ),
  185. (empty, empty),
  186. ([empty], [empty]),
  187. ],
  188. )
  189. def test_make_valid(geom, expected):
  190. actual = shapely.make_valid(geom)
  191. assert actual is not expected
  192. # normalize needed to handle variation in output across GEOS versions
  193. assert shapely.normalize(actual) == expected
  194. @pytest.mark.parametrize(
  195. "geom,expected",
  196. [
  197. (all_types, all_types),
  198. # first polygon is valid, second polygon has self-intersection
  199. (
  200. [
  201. Polygon([(0, 0), (2, 2), (0, 2), (0, 0)]),
  202. Polygon([(0, 0), (2, 2), (2, 0), (0, 2), (0, 0)]),
  203. ],
  204. [
  205. Polygon([(0, 0), (2, 2), (0, 2), (0, 0)]),
  206. MultiPolygon(
  207. [
  208. Polygon([(1, 1), (0, 0), (0, 2), (1, 1)]),
  209. Polygon([(1, 1), (2, 2), (2, 0), (1, 1)]),
  210. ]
  211. ),
  212. ],
  213. ),
  214. ([point, None, empty], [point, None, empty]),
  215. ],
  216. )
  217. def test_make_valid_1d(geom, expected):
  218. actual = shapely.make_valid(geom)
  219. # normalize needed to handle variation in output across GEOS versions
  220. assert np.all(shapely.normalize(actual) == shapely.normalize(expected))
  221. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  222. @pytest.mark.parametrize(
  223. "geom,expected",
  224. [
  225. (point, point), # a valid geometry stays the same (but is copied)
  226. # an L shaped polygon without area is converted to a linestring
  227. (
  228. Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]),
  229. LineString([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]),
  230. ),
  231. # a polygon with self-intersection (bowtie) is converted into polygons
  232. (
  233. Polygon([(0, 0), (2, 2), (2, 0), (0, 2), (0, 0)]),
  234. MultiPolygon(
  235. [
  236. Polygon([(1, 1), (2, 2), (2, 0), (1, 1)]),
  237. Polygon([(0, 0), (0, 2), (1, 1), (0, 0)]),
  238. ]
  239. ),
  240. ),
  241. (empty, empty),
  242. ([empty], [empty]),
  243. ],
  244. )
  245. def test_make_valid_structure(geom, expected):
  246. actual = shapely.make_valid(geom, method="structure")
  247. assert actual is not expected
  248. # normalize needed to handle variation in output across GEOS versions
  249. assert shapely.normalize(actual) == expected
  250. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  251. @pytest.mark.parametrize(
  252. "geom,expected",
  253. [
  254. (point, point), # a valid geometry stays the same (but is copied)
  255. # an L shaped polygon without area is converted to Empty Polygon
  256. (
  257. Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]),
  258. Polygon(),
  259. ),
  260. # a polygon with self-intersection (bowtie) is converted into polygons
  261. (
  262. Polygon([(0, 0), (2, 2), (2, 0), (0, 2), (0, 0)]),
  263. MultiPolygon(
  264. [
  265. Polygon([(1, 1), (2, 2), (2, 0), (1, 1)]),
  266. Polygon([(0, 0), (0, 2), (1, 1), (0, 0)]),
  267. ]
  268. ),
  269. ),
  270. (empty, empty),
  271. ([empty], [empty]),
  272. ],
  273. )
  274. def test_make_valid_structure_keep_collapsed_false(geom, expected):
  275. actual = shapely.make_valid(geom, method="structure", keep_collapsed=False)
  276. assert actual is not expected
  277. # normalize needed to handle variation in output across GEOS versions
  278. assert shapely.normalize(actual) == expected
  279. @pytest.mark.skipif(shapely.geos_version >= (3, 10, 0), reason="GEOS >= 3.10")
  280. def test_make_valid_structure_unsupported_geos():
  281. with pytest.raises(
  282. ValueError, match="The 'structure' method is only available in GEOS >= 3.10.0"
  283. ):
  284. _ = shapely.make_valid(Point(), method="structure")
  285. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  286. @pytest.mark.parametrize(
  287. "method, keep_collapsed, error_type, error",
  288. [
  289. (
  290. np.array(["linework", "structure"]),
  291. True,
  292. TypeError,
  293. "method only accepts scalar values",
  294. ),
  295. (
  296. "linework",
  297. [True, False],
  298. TypeError,
  299. "keep_collapsed only accepts scalar values",
  300. ),
  301. ("unknown", True, ValueError, "Unknown method: unknown"),
  302. (
  303. "linework",
  304. False,
  305. ValueError,
  306. "The 'linework' method does not support 'keep_collapsed=False'",
  307. ),
  308. ],
  309. )
  310. def test_make_valid_invalid_params(method, keep_collapsed, error_type, error):
  311. with pytest.raises(error_type, match=error):
  312. _ = shapely.make_valid(Point(), method=method, keep_collapsed=keep_collapsed)
  313. @pytest.mark.parametrize(
  314. "geom,expected",
  315. [
  316. (point, point), # a point is always in normalized form
  317. # order coordinates of linestrings and parts of multi-linestring
  318. (
  319. MultiLineString([((1, 1), (0, 0)), ((1, 1), (1, 2))]),
  320. MultiLineString([((1, 1), (1, 2)), ((0, 0), (1, 1))]),
  321. ),
  322. ],
  323. )
  324. def test_normalize(geom, expected):
  325. actual = shapely.normalize(geom)
  326. assert actual == expected
  327. def test_offset_curve_empty():
  328. with ignore_invalid(shapely.geos_version < (3, 12, 0)):
  329. # Empty geometries emit an "invalid" warning
  330. # (see https://github.com/libgeos/geos/issues/515)
  331. actual = shapely.offset_curve(empty_line_string, 2.0)
  332. assert shapely.is_empty(actual)
  333. def test_offset_curve_distance_array():
  334. # check that kwargs are passed through
  335. result = shapely.offset_curve([line_string, line_string], [-2.0, -3.0])
  336. assert result[0] == shapely.offset_curve(line_string, -2.0)
  337. assert result[1] == shapely.offset_curve(line_string, -3.0)
  338. def test_offset_curve_kwargs():
  339. # check that kwargs are passed through
  340. result1 = shapely.offset_curve(
  341. line_string, -2.0, quad_segs=2, join_style="mitre", mitre_limit=2.0
  342. )
  343. result2 = shapely.offset_curve(line_string, -2.0)
  344. assert result1 != result2
  345. def test_offset_curve_non_scalar_kwargs():
  346. msg = "only accepts scalar values"
  347. with pytest.raises(TypeError, match=msg):
  348. shapely.offset_curve([line_string, line_string], 1, quad_segs=np.array([8, 9]))
  349. with pytest.raises(TypeError, match=msg):
  350. shapely.offset_curve(
  351. [line_string, line_string], 1, join_style=["round", "bevel"]
  352. )
  353. with pytest.raises(TypeError, match=msg):
  354. shapely.offset_curve([line_string, line_string], 1, mitre_limit=[5.0, 6.0])
  355. def test_offset_curve_join_style_invalid():
  356. with pytest.raises(ValueError, match="'invalid' is not a valid option"):
  357. shapely.offset_curve(line_string, 1.0, join_style="invalid")
  358. @pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11")
  359. @pytest.mark.parametrize(
  360. "geom,expected",
  361. [
  362. (LineString([(0, 0), (0, 0), (1, 0)]), LineString([(0, 0), (1, 0)])),
  363. (
  364. LinearRing([(0, 0), (1, 2), (1, 2), (1, 3), (0, 0)]),
  365. LinearRing([(0, 0), (1, 2), (1, 3), (0, 0)]),
  366. ),
  367. (
  368. Polygon([(0, 0), (0, 0), (1, 0), (1, 1), (1, 0), (0, 0)]),
  369. Polygon([(0, 0), (1, 0), (1, 1), (1, 0), (0, 0)]),
  370. ),
  371. (
  372. Polygon(
  373. [(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)],
  374. holes=[[(2, 2), (2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]],
  375. ),
  376. Polygon(
  377. [(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)],
  378. holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]],
  379. ),
  380. ),
  381. (
  382. MultiPolygon(
  383. [
  384. Polygon([(0, 0), (0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]),
  385. Polygon([(2, 2), (2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]),
  386. ]
  387. ),
  388. MultiPolygon(
  389. [
  390. Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]),
  391. Polygon([(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]),
  392. ]
  393. ),
  394. ),
  395. # points are unchanged
  396. (point, point),
  397. (point_z, point_z),
  398. (multi_point, multi_point),
  399. # empty geometries are unchanged
  400. (empty_point, empty_point),
  401. (empty_line_string, empty_line_string),
  402. (empty, empty),
  403. (empty_polygon, empty_polygon),
  404. ],
  405. )
  406. def test_remove_repeated_points(geom, expected):
  407. assert_geometries_equal(shapely.remove_repeated_points(geom, 0), expected)
  408. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12")
  409. @pytest.mark.parametrize(
  410. "geom, tolerance", [[Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]), 2]]
  411. )
  412. def test_remove_repeated_points_invalid_result(geom, tolerance):
  413. # Requiring GEOS 3.12 instead of 3.11
  414. # (GEOS 3.11 had a bug causing this to intermittently not fail)
  415. with pytest.raises(shapely.GEOSException, match="Invalid number of points"):
  416. shapely.remove_repeated_points(geom, tolerance)
  417. @pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11")
  418. def test_remove_repeated_points_none():
  419. assert shapely.remove_repeated_points(None, 1) is None
  420. assert shapely.remove_repeated_points([None], 1).tolist() == [None]
  421. geometry = LineString([(0, 0), (0, 0), (1, 1)])
  422. expected = LineString([(0, 0), (1, 1)])
  423. result = shapely.remove_repeated_points([None, geometry], 1)
  424. assert result[0] is None
  425. assert_geometries_equal(result[1], expected)
  426. @pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11")
  427. @pytest.mark.parametrize("geom, tolerance", [("Not a geometry", 1), (1, 1)])
  428. def test_remove_repeated_points_invalid_type(geom, tolerance):
  429. with pytest.raises(TypeError, match="One of the arguments is of incorrect type"):
  430. shapely.remove_repeated_points(geom, tolerance)
  431. @pytest.mark.parametrize(
  432. "geom,expected",
  433. [
  434. (LineString([(0, 0), (1, 2)]), LineString([(1, 2), (0, 0)])),
  435. (
  436. LinearRing([(0, 0), (1, 2), (1, 3), (0, 0)]),
  437. LinearRing([(0, 0), (1, 3), (1, 2), (0, 0)]),
  438. ),
  439. (
  440. Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]),
  441. Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]),
  442. ),
  443. (
  444. Polygon(
  445. [(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)],
  446. holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]],
  447. ),
  448. Polygon(
  449. [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
  450. holes=[[(2, 2), (4, 2), (4, 4), (2, 4), (2, 2)]],
  451. ),
  452. ),
  453. (
  454. MultiLineString([[(0, 0), (1, 2)], [(3, 3), (4, 4)]]),
  455. MultiLineString([[(1, 2), (0, 0)], [(4, 4), (3, 3)]]),
  456. ),
  457. (
  458. MultiPolygon(
  459. [
  460. Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]),
  461. Polygon([(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]),
  462. ]
  463. ),
  464. MultiPolygon(
  465. [
  466. Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]),
  467. Polygon([(2, 2), (3, 2), (3, 3), (2, 3), (2, 2)]),
  468. ]
  469. ),
  470. ),
  471. # points are unchanged
  472. (point, point),
  473. (point_z, point_z),
  474. (multi_point, multi_point),
  475. # empty geometries are unchanged
  476. (empty_point, empty_point),
  477. (empty_line_string, empty_line_string),
  478. (empty, empty),
  479. (empty_polygon, empty_polygon),
  480. ],
  481. )
  482. def test_reverse(geom, expected):
  483. assert_geometries_equal(shapely.reverse(geom), expected)
  484. def test_reverse_none():
  485. assert shapely.reverse(None) is None
  486. assert shapely.reverse([None]).tolist() == [None]
  487. geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
  488. expected = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
  489. result = shapely.reverse([None, geometry])
  490. assert result[0] is None
  491. assert_geometries_equal(result[1], expected)
  492. @pytest.mark.parametrize("geom", ["Not a geometry", 1])
  493. def test_reverse_invalid_type(geom):
  494. with pytest.raises(TypeError, match="One of the arguments is of incorrect type"):
  495. shapely.reverse(geom)
  496. @pytest.mark.parametrize(
  497. "geom,expected",
  498. [
  499. # Point outside
  500. (Point(0, 0), GeometryCollection()),
  501. # Point inside
  502. (Point(15, 15), Point(15, 15)),
  503. # Point on boundary
  504. (Point(15, 10), GeometryCollection()),
  505. # Line outside
  506. (LineString([(0, 0), (-5, 5)]), GeometryCollection()),
  507. # Line inside
  508. (LineString([(15, 15), (16, 15)]), LineString([(15, 15), (16, 15)])),
  509. # Line on boundary
  510. (LineString([(10, 15), (10, 10), (15, 10)]), GeometryCollection()),
  511. # Line splitting rectangle
  512. (LineString([(10, 5), (25, 20)]), LineString([(15, 10), (20, 15)])),
  513. ],
  514. )
  515. def test_clip_by_rect(geom, expected):
  516. actual = shapely.clip_by_rect(geom, 10, 10, 20, 20)
  517. assert_geometries_equal(actual, expected)
  518. @pytest.mark.parametrize(
  519. "geom, rect, expected",
  520. [
  521. # Polygon hole (CCW) fully on rectangle boundary"""
  522. (
  523. Polygon(
  524. ((0, 0), (0, 30), (30, 30), (30, 0), (0, 0)),
  525. holes=[((10, 10), (20, 10), (20, 20), (10, 20), (10, 10))],
  526. ),
  527. (10, 10, 20, 20),
  528. GeometryCollection(),
  529. ),
  530. # Polygon hole (CW) fully on rectangle boundary"""
  531. (
  532. Polygon(
  533. ((0, 0), (0, 30), (30, 30), (30, 0), (0, 0)),
  534. holes=[((10, 10), (10, 20), (20, 20), (20, 10), (10, 10))],
  535. ),
  536. (10, 10, 20, 20),
  537. GeometryCollection(),
  538. ),
  539. # Polygon fully within rectangle"""
  540. (
  541. Polygon(
  542. ((1, 1), (1, 30), (30, 30), (30, 1), (1, 1)),
  543. holes=[((10, 10), (20, 10), (20, 20), (10, 20), (10, 10))],
  544. ),
  545. (0, 0, 40, 40),
  546. Polygon(
  547. ((1, 1), (1, 30), (30, 30), (30, 1), (1, 1)),
  548. holes=[((10, 10), (20, 10), (20, 20), (10, 20), (10, 10))],
  549. ),
  550. ),
  551. # Polygon overlapping rectanglez
  552. (
  553. Polygon(
  554. [(0, 0), (0, 30), (30, 30), (30, 0), (0, 0)],
  555. holes=[[(10, 10), (20, 10), (20, 20), (10, 20), (10, 10)]],
  556. ),
  557. (5, 5, 15, 15),
  558. Polygon([(5, 5), (5, 15), (10, 15), (10, 10), (15, 10), (15, 5), (5, 5)]),
  559. ),
  560. ],
  561. )
  562. def test_clip_by_rect_polygon(geom, rect, expected):
  563. actual = shapely.clip_by_rect(geom, *rect)
  564. assert_geometries_equal(actual, expected)
  565. @pytest.mark.parametrize("geometry", all_types)
  566. def test_clip_by_rect_array(geometry):
  567. if (
  568. geometry.is_empty
  569. and shapely.get_type_id(geometry) == shapely.GeometryType.POINT
  570. and (
  571. (geos39 and geos_version < (3, 9, 5))
  572. or (geos310 and geos_version < (3, 10, 6))
  573. or (geos311 and geos_version < (3, 11, 3))
  574. )
  575. ):
  576. # GEOS GH-913
  577. with pytest.raises(GEOSException):
  578. shapely.clip_by_rect([geometry, geometry], 0.0, 0.0, 1.0, 1.0)
  579. return
  580. actual = shapely.clip_by_rect([geometry, geometry], 0.0, 0.0, 1.0, 1.0)
  581. assert actual.shape == (2,)
  582. assert actual[0] is None or isinstance(actual[0], Geometry)
  583. def test_clip_by_rect_missing():
  584. actual = shapely.clip_by_rect(None, 0, 0, 1, 1)
  585. assert actual is None
  586. @pytest.mark.parametrize("geom", [empty, empty_line_string, empty_polygon])
  587. def test_clip_by_rect_empty(geom):
  588. # TODO empty point
  589. actual = shapely.clip_by_rect(geom, 0, 0, 1, 1)
  590. assert actual == GeometryCollection()
  591. def test_clip_by_rect_non_scalar_kwargs():
  592. msg = "only accepts scalar values"
  593. with pytest.raises(TypeError, match=msg):
  594. shapely.clip_by_rect([line_string, line_string], 0, 0, 1, np.array([0, 1]))
  595. def test_polygonize():
  596. lines = [
  597. LineString([(0, 0), (1, 1)]),
  598. LineString([(0, 0), (0, 1)]),
  599. LineString([(0, 1), (1, 1)]),
  600. LineString([(1, 1), (1, 0)]),
  601. LineString([(1, 0), (0, 0)]),
  602. LineString([(5, 5), (6, 6)]),
  603. Point(0, 0),
  604. None,
  605. ]
  606. result = shapely.polygonize(lines)
  607. assert shapely.get_type_id(result) == 7 # GeometryCollection
  608. expected = GeometryCollection(
  609. [
  610. Polygon([(0, 0), (1, 1), (1, 0), (0, 0)]),
  611. Polygon([(1, 1), (0, 0), (0, 1), (1, 1)]),
  612. ]
  613. )
  614. assert result == expected
  615. def test_polygonize_array():
  616. lines = [
  617. LineString([(0, 0), (1, 1)]),
  618. LineString([(0, 0), (0, 1)]),
  619. LineString([(0, 1), (1, 1)]),
  620. ]
  621. expected = GeometryCollection([Polygon([(1, 1), (0, 0), (0, 1), (1, 1)])])
  622. result = shapely.polygonize(np.array(lines))
  623. assert isinstance(result, shapely.Geometry)
  624. assert result == expected
  625. result = shapely.polygonize(np.array([lines]))
  626. assert isinstance(result, np.ndarray)
  627. assert result.shape == (1,)
  628. assert result[0] == expected
  629. arr = np.array([lines, lines])
  630. assert arr.shape == (2, 3)
  631. result = shapely.polygonize(arr)
  632. assert isinstance(result, np.ndarray)
  633. assert result.shape == (2,)
  634. assert result[0] == expected
  635. assert result[1] == expected
  636. arr = np.array([[lines, lines], [lines, lines], [lines, lines]])
  637. assert arr.shape == (3, 2, 3)
  638. result = shapely.polygonize(arr)
  639. assert isinstance(result, np.ndarray)
  640. assert result.shape == (3, 2)
  641. for res in result.flatten():
  642. assert res == expected
  643. def test_polygonize_array_axis():
  644. lines = [
  645. LineString([(0, 0), (1, 1)]),
  646. LineString([(0, 0), (0, 1)]),
  647. LineString([(0, 1), (1, 1)]),
  648. ]
  649. arr = np.array([lines, lines]) # shape (2, 3)
  650. result = shapely.polygonize(arr, axis=1)
  651. assert result.shape == (2,)
  652. result = shapely.polygonize(arr, axis=0)
  653. assert result.shape == (3,)
  654. def test_polygonize_missing():
  655. # set of geometries that is all missing
  656. result = shapely.polygonize([None, None])
  657. assert result == GeometryCollection()
  658. def test_polygonize_full():
  659. lines = [
  660. None,
  661. LineString([(0, 0), (1, 1)]),
  662. LineString([(0, 0), (0, 1)]),
  663. LineString([(0, 1), (1, 1)]),
  664. LineString([(1, 1), (1, 0)]),
  665. None,
  666. LineString([(1, 0), (0, 0)]),
  667. LineString([(5, 5), (6, 6)]),
  668. LineString([(1, 1), (100, 100)]),
  669. Point(0, 0),
  670. None,
  671. ]
  672. result = shapely.polygonize_full(lines)
  673. assert len(result) == 4
  674. assert all(shapely.get_type_id(geom) == 7 for geom in result) # GeometryCollection
  675. polygons, cuts, dangles, invalid = result
  676. expected_polygons = GeometryCollection(
  677. [
  678. Polygon([(0, 0), (1, 1), (1, 0), (0, 0)]),
  679. Polygon([(1, 1), (0, 0), (0, 1), (1, 1)]),
  680. ]
  681. )
  682. assert polygons == expected_polygons
  683. assert cuts == GeometryCollection()
  684. expected_dangles = GeometryCollection(
  685. [LineString([(1, 1), (100, 100)]), LineString([(5, 5), (6, 6)])]
  686. )
  687. assert dangles == expected_dangles
  688. assert invalid == GeometryCollection()
  689. def test_polygonize_full_array():
  690. lines = [
  691. LineString([(0, 0), (1, 1)]),
  692. LineString([(0, 0), (0, 1)]),
  693. LineString([(0, 1), (1, 1)]),
  694. ]
  695. expected = GeometryCollection([Polygon([(1, 1), (0, 0), (0, 1), (1, 1)])])
  696. result = shapely.polygonize_full(np.array(lines))
  697. assert len(result) == 4
  698. assert all(isinstance(geom, shapely.Geometry) for geom in result)
  699. assert result[0] == expected
  700. assert all(geom == GeometryCollection() for geom in result[1:])
  701. result = shapely.polygonize_full(np.array([lines]))
  702. assert len(result) == 4
  703. assert all(isinstance(geom, np.ndarray) for geom in result)
  704. assert all(geom.shape == (1,) for geom in result)
  705. assert result[0][0] == expected
  706. assert all(geom[0] == GeometryCollection() for geom in result[1:])
  707. arr = np.array([lines, lines])
  708. assert arr.shape == (2, 3)
  709. result = shapely.polygonize_full(arr)
  710. assert len(result) == 4
  711. assert all(isinstance(arr, np.ndarray) for arr in result)
  712. assert all(arr.shape == (2,) for arr in result)
  713. assert result[0][0] == expected
  714. assert result[0][1] == expected
  715. assert all(g == GeometryCollection() for geom in result[1:] for g in geom)
  716. arr = np.array([[lines, lines], [lines, lines], [lines, lines]])
  717. assert arr.shape == (3, 2, 3)
  718. result = shapely.polygonize_full(arr)
  719. assert len(result) == 4
  720. assert all(isinstance(arr, np.ndarray) for arr in result)
  721. assert all(arr.shape == (3, 2) for arr in result)
  722. for res in result[0].flatten():
  723. assert res == expected
  724. for arr in result[1:]:
  725. for res in arr.flatten():
  726. assert res == GeometryCollection()
  727. def test_polygonize_full_array_axis():
  728. lines = [
  729. LineString([(0, 0), (1, 1)]),
  730. LineString([(0, 0), (0, 1)]),
  731. LineString([(0, 1), (1, 1)]),
  732. ]
  733. arr = np.array([lines, lines]) # shape (2, 3)
  734. result = shapely.polygonize_full(arr, axis=1)
  735. assert len(result) == 4
  736. assert all(arr.shape == (2,) for arr in result)
  737. result = shapely.polygonize_full(arr, axis=0)
  738. assert len(result) == 4
  739. assert all(arr.shape == (3,) for arr in result)
  740. def test_polygonize_full_missing():
  741. # set of geometries that is all missing
  742. result = shapely.polygonize_full([None, None])
  743. assert len(result) == 4
  744. assert all(geom == GeometryCollection() for geom in result)
  745. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  746. @pytest.mark.parametrize("geometry", all_types)
  747. @pytest.mark.parametrize("max_segment_length", [-1, 0])
  748. def test_segmentize_invalid_max_segment_length(geometry, max_segment_length):
  749. with pytest.raises(GEOSException, match="IllegalArgumentException"):
  750. shapely.segmentize(geometry, max_segment_length=max_segment_length)
  751. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  752. @pytest.mark.parametrize("geometry", all_types)
  753. def test_segmentize_max_segment_length_nan(geometry):
  754. actual = shapely.segmentize(geometry, max_segment_length=np.nan)
  755. assert actual is None
  756. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  757. @pytest.mark.parametrize(
  758. "geometry", [empty, empty_point, empty_line_string, empty_polygon]
  759. )
  760. def test_segmentize_empty(geometry):
  761. actual = shapely.segmentize(geometry, max_segment_length=5)
  762. assert_geometries_equal(actual, geometry)
  763. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  764. @pytest.mark.parametrize("geometry", [point, point_z, multi_point])
  765. def test_segmentize_no_change(geometry):
  766. actual = shapely.segmentize(geometry, max_segment_length=5)
  767. assert_geometries_equal(actual, geometry)
  768. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  769. def test_segmentize_none():
  770. assert shapely.segmentize(None, max_segment_length=5) is None
  771. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  772. @pytest.mark.parametrize(
  773. "geometry,tolerance, expected",
  774. [
  775. # tolerance greater than max edge length, no change
  776. (
  777. LineString([(0, 0), (0, 10)]),
  778. 20,
  779. LineString([(0, 0), (0, 10)]),
  780. ),
  781. (
  782. Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]),
  783. 20,
  784. Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]),
  785. ),
  786. # tolerance causes one vertex per segment
  787. (
  788. LineString([(0, 0), (0, 10)]),
  789. 5,
  790. LineString([(0, 0), (0, 5), (0, 10)]),
  791. ),
  792. (
  793. Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]),
  794. 5,
  795. Polygon(
  796. [
  797. (0, 0),
  798. (5, 0),
  799. (10, 0),
  800. (10, 5),
  801. (10, 10),
  802. (5, 10),
  803. (0, 10),
  804. (0, 5),
  805. (0, 0),
  806. ]
  807. ),
  808. ),
  809. # ensure input arrays are broadcast correctly
  810. (
  811. [
  812. LineString([(0, 0), (0, 10)]),
  813. LineString([(0, 0), (0, 2)]),
  814. ],
  815. 5,
  816. [
  817. LineString([(0, 0), (0, 5), (0, 10)]),
  818. LineString([(0, 0), (0, 2)]),
  819. ],
  820. ),
  821. (
  822. [
  823. LineString([(0, 0), (0, 10)]),
  824. LineString([(0, 0), (0, 2)]),
  825. ],
  826. [5],
  827. [
  828. LineString([(0, 0), (0, 5), (0, 10)]),
  829. LineString([(0, 0), (0, 2)]),
  830. ],
  831. ),
  832. (
  833. [
  834. LineString([(0, 0), (0, 10)]),
  835. LineString([(0, 0), (0, 2)]),
  836. ],
  837. [5, 1.5],
  838. [
  839. LineString([(0, 0), (0, 5), (0, 10)]),
  840. LineString([(0, 0), (0, 1), (0, 2)]),
  841. ],
  842. ),
  843. ],
  844. )
  845. def test_segmentize(geometry, tolerance, expected):
  846. actual = shapely.segmentize(geometry, tolerance)
  847. assert_geometries_equal(actual, expected)
  848. @pytest.mark.parametrize("geometry", all_types)
  849. def test_minimum_bounding_circle_all_types(geometry):
  850. actual = shapely.minimum_bounding_circle([geometry, geometry])
  851. assert actual.shape == (2,)
  852. assert actual[0] is None or isinstance(actual[0], Geometry)
  853. actual = shapely.minimum_bounding_circle(None)
  854. assert actual is None
  855. @pytest.mark.parametrize(
  856. "geometry, expected",
  857. [
  858. (
  859. Polygon([(0, 5), (5, 10), (10, 5), (5, 0), (0, 5)]),
  860. shapely.buffer(Point(5, 5), 5),
  861. ),
  862. (
  863. LineString([(1, 0), (1, 10)]),
  864. shapely.buffer(Point(1, 5), 5),
  865. ),
  866. (
  867. MultiPoint([(2, 2), (4, 2)]),
  868. shapely.buffer(Point(3, 2), 1),
  869. ),
  870. (
  871. Point(2, 2),
  872. Point(2, 2),
  873. ),
  874. (
  875. GeometryCollection(),
  876. Polygon(),
  877. ),
  878. ],
  879. )
  880. def test_minimum_bounding_circle(geometry, expected):
  881. actual = shapely.minimum_bounding_circle(geometry)
  882. assert_geometries_equal(actual, expected)
  883. @pytest.mark.parametrize("geometry", all_types)
  884. def test_oriented_envelope_all_types(geometry):
  885. actual = shapely.oriented_envelope([geometry, geometry])
  886. assert actual.shape == (2,)
  887. assert actual[0] is None or isinstance(actual[0], Geometry)
  888. actual = shapely.oriented_envelope(None)
  889. assert actual is None
  890. @pytest.mark.parametrize(
  891. "func", [shapely.oriented_envelope, shapely.minimum_rotated_rectangle]
  892. )
  893. @pytest.mark.parametrize(
  894. "geometry, expected",
  895. [
  896. (
  897. MultiPoint([(1.0, 1.0), (1.0, 5.0), (3.0, 6.0), (4.0, 2.0), (5.0, 5.0)]),
  898. Polygon([(1.0, 1.0), (1.0, 6.0), (5.0, 6.0), (5.0, 1.0), (1.0, 1.0)]),
  899. ),
  900. (
  901. LineString([(1, 1), (5, 1), (10, 10)]),
  902. Polygon([(1, 1), (3, -1), (12, 8), (10, 10), (1, 1)]),
  903. ),
  904. (
  905. Polygon([(1, 1), (15, 1), (5, 9), (1, 1)]),
  906. Polygon([(1.0, 1.0), (5.0, 9.0), (16.2, 3.4), (12.2, -4.6), (1.0, 1.0)]),
  907. ),
  908. (
  909. LineString([(1, 1), (10, 1)]),
  910. LineString([(1, 1), (10, 1)]),
  911. ),
  912. (
  913. Point(2, 2),
  914. Point(2, 2),
  915. ),
  916. (
  917. GeometryCollection(),
  918. Polygon(),
  919. ),
  920. ],
  921. )
  922. def test_oriented_envelope(geometry, expected, func):
  923. actual = func(geometry)
  924. assert_geometries_equal(actual, expected, normalize=True, tolerance=1e-3)
  925. @pytest.mark.skipif(shapely.geos_version >= (3, 12, 0), reason="GEOS >= 3.12")
  926. @pytest.mark.parametrize(
  927. "geometry, expected",
  928. [
  929. (
  930. MultiPoint([(1.0, 1.0), (1.0, 5.0), (3.0, 6.0), (4.0, 2.0), (5.0, 5.0)]),
  931. Polygon([(-0.2, 1.4), (1.5, 6.5), (5.1, 5.3), (3.4, 0.2), (-0.2, 1.4)]),
  932. ),
  933. (
  934. LineString([(1, 1), (5, 1), (10, 10)]),
  935. Polygon([(1, 1), (3, -1), (12, 8), (10, 10), (1, 1)]),
  936. ),
  937. (
  938. Polygon([(1, 1), (15, 1), (5, 9), (1, 1)]),
  939. Polygon([(1.0, 1.0), (1.0, 9.0), (15.0, 9.0), (15.0, 1.0), (1.0, 1.0)]),
  940. ),
  941. (
  942. LineString([(1, 1), (10, 1)]),
  943. LineString([(1, 1), (10, 1)]),
  944. ),
  945. (
  946. Point(2, 2),
  947. Point(2, 2),
  948. ),
  949. (
  950. GeometryCollection(),
  951. Polygon(),
  952. ),
  953. ],
  954. )
  955. def test_oriented_envelope_pre_geos_312(geometry, expected):
  956. # use private method (similar as direct shapely.lib.oriented_envelope)
  957. # to cover the C code for older GEOS versions
  958. actual = shapely.constructive._oriented_envelope_geos(geometry)
  959. assert_geometries_equal(actual, expected, normalize=True, tolerance=1e-3)
  960. def test_oriented_evelope_array_like():
  961. # https://github.com/shapely/shapely/issues/1929
  962. # because we have a custom python implementation, need to ensure this has
  963. # the same capabilities as numpy ufuncs to work with array-likes
  964. geometries = [Point(1, 1).buffer(1), Point(2, 2).buffer(1)]
  965. actual = shapely.oriented_envelope(ArrayLike(geometries))
  966. assert isinstance(actual, ArrayLike)
  967. expected = shapely.oriented_envelope(geometries)
  968. assert_geometries_equal(np.asarray(actual), expected)
  969. @pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11")
  970. def test_concave_hull_kwargs():
  971. p = Point(10, 10)
  972. mp = MultiPoint(p.buffer(5).exterior.coords[:] + p.buffer(4).exterior.coords[:])
  973. result1 = shapely.concave_hull(mp, ratio=0.5)
  974. assert len(result1.interiors) == 0
  975. result2 = shapely.concave_hull(mp, ratio=0.5, allow_holes=True)
  976. assert len(result2.interiors) == 1
  977. result3 = shapely.concave_hull(mp, ratio=0)
  978. result4 = shapely.concave_hull(mp, ratio=1)
  979. assert shapely.get_num_coordinates(result4) < shapely.get_num_coordinates(result3)
  980. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  981. class TestConstrainedDelaunayTriangulation:
  982. """
  983. Only testing the number of triangles and their type here.
  984. This doesn't actually test the points in the resulting geometries.
  985. """
  986. def test_poly(self):
  987. polys = shapely.constrained_delaunay_triangles(
  988. Polygon([(10, 10), (20, 40), (90, 90), (90, 10), (10, 10)])
  989. )
  990. assert len(polys.geoms) == 2
  991. for p in polys.geoms:
  992. assert isinstance(p, Polygon)
  993. def test_multi_polygon(self):
  994. multipoly = MultiPolygon(
  995. [
  996. Polygon(((50, 30), (60, 30), (100, 100), (50, 30))),
  997. Polygon(((10, 10), (20, 40), (90, 90), (90, 10), (10, 10))),
  998. ]
  999. )
  1000. polys = shapely.constrained_delaunay_triangles(multipoly)
  1001. assert len(polys.geoms) == 3
  1002. for p in polys.geoms:
  1003. assert isinstance(p, Polygon)
  1004. def test_point(self):
  1005. p = Point(1, 1)
  1006. polys = shapely.constrained_delaunay_triangles(p)
  1007. assert len(polys.geoms) == 0
  1008. def test_empty_poly(self):
  1009. polys = shapely.constrained_delaunay_triangles(Polygon())
  1010. assert len(polys.geoms) == 0
  1011. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12")
  1012. def test_voronoi_polygons_ordered():
  1013. mp = MultiPoint([(3.0, 1.0), (3.0, 2.0), (1.0, 2.0), (1.0, 1.0)])
  1014. result = shapely.voronoi_polygons(mp, ordered=False)
  1015. assert result.geoms[0].equals(
  1016. Polygon([(-1, -1), (-1, 1.5), (2, 1.5), (2, -1), (-1, -1)])
  1017. )
  1018. result_ordered = shapely.voronoi_polygons(mp, ordered=True)
  1019. assert result_ordered.geoms[0].equals(
  1020. Polygon([(5, -1), (2, -1), (2, 1.5), (5, 1.5), (5, -1)])
  1021. )
  1022. @pytest.mark.skipif(shapely.geos_version >= (3, 12, 0), reason="GEOS >= 3.12")
  1023. def test_voronoi_polygons_ordered_raise():
  1024. mp = MultiPoint([(3.0, 1.0), (3.0, 2.0), (1.0, 2.0), (1.0, 1.0)])
  1025. with pytest.raises(
  1026. UnsupportedGEOSVersionError, match="Ordered Voronoi polygons require GEOS"
  1027. ):
  1028. shapely.voronoi_polygons(mp, ordered=True)
  1029. @pytest.mark.parametrize("geometry", all_types)
  1030. def test_maximum_inscribed_circle_all_types(geometry):
  1031. if shapely.get_type_id(geometry) not in [3, 6]:
  1032. # Maximum Inscribed Circle is only supported for (Multi)Polygon input
  1033. with pytest.raises(
  1034. GEOSException,
  1035. match=(
  1036. "Argument must be Polygonal or LinearRing|" # GEOS < 3.10.4
  1037. "must be a Polygon or MultiPolygon|"
  1038. "Operation not supported by GeometryCollection"
  1039. ),
  1040. ):
  1041. shapely.maximum_inscribed_circle(geometry)
  1042. return
  1043. if geometry.is_empty:
  1044. with pytest.raises(
  1045. GEOSException, match="Empty input(?: geometry)? is not supported"
  1046. ):
  1047. shapely.maximum_inscribed_circle(geometry)
  1048. return
  1049. actual = shapely.maximum_inscribed_circle([geometry, geometry])
  1050. assert actual.shape == (2,)
  1051. assert actual[0] is None or isinstance(actual[0], Geometry)
  1052. actual = shapely.maximum_inscribed_circle(None)
  1053. assert actual is None
  1054. @pytest.mark.parametrize(
  1055. "geometry, expected",
  1056. [
  1057. (
  1058. "POLYGON ((0 5, 5 10, 10 5, 5 0, 0 5))",
  1059. "LINESTRING (5 5, 2.5 7.5)",
  1060. ),
  1061. ],
  1062. )
  1063. def test_maximum_inscribed_circle(geometry, expected):
  1064. geometry, expected = shapely.from_wkt(geometry), shapely.from_wkt(expected)
  1065. actual = shapely.maximum_inscribed_circle(geometry)
  1066. assert_geometries_equal(actual, expected)
  1067. def test_maximum_inscribed_circle_empty():
  1068. geometry = shapely.from_wkt("POINT EMPTY")
  1069. with pytest.raises(
  1070. GEOSException,
  1071. match=(
  1072. "Argument must be Polygonal or LinearRing|" # GEOS < 3.10.4
  1073. "must be a Polygon or MultiPolygon"
  1074. ),
  1075. ):
  1076. shapely.maximum_inscribed_circle(geometry)
  1077. geometry = shapely.from_wkt("POLYGON EMPTY")
  1078. with pytest.raises(
  1079. GEOSException, match="Empty input(?: geometry)? is not supported"
  1080. ):
  1081. shapely.maximum_inscribed_circle(geometry)
  1082. def test_maximum_inscribed_circle_invalid_tolerance():
  1083. geometry = shapely.from_wkt("POLYGON ((0 5, 5 10, 10 5, 5 0, 0 5))")
  1084. with pytest.raises(ValueError, match="'tolerance' should be positive"):
  1085. shapely.maximum_inscribed_circle(geometry, tolerance=-1)
  1086. @pytest.mark.parametrize("geometry", all_types)
  1087. def test_orient_polygons_all_types(geometry):
  1088. actual = shapely.orient_polygons([geometry, geometry])
  1089. assert actual.shape == (2,)
  1090. assert isinstance(actual[0], Geometry)
  1091. actual = shapely.orient_polygons(None)
  1092. assert actual is None
  1093. def test_orient_polygons():
  1094. # polygon with both shell and hole having clockwise orientation
  1095. polygon = Polygon(
  1096. [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
  1097. holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]],
  1098. )
  1099. result = shapely.orient_polygons(polygon)
  1100. assert result.exterior.is_ccw
  1101. assert not result.interiors[0].is_ccw
  1102. result = shapely.orient_polygons(polygon, exterior_cw=True)
  1103. assert not result.exterior.is_ccw
  1104. assert result.interiors[0].is_ccw
  1105. # in a MultiPolygon
  1106. mp = MultiPolygon([polygon, polygon])
  1107. result = shapely.orient_polygons(mp)
  1108. assert len(result.geoms) == 2
  1109. for geom in result.geoms:
  1110. assert geom.exterior.is_ccw
  1111. assert not geom.interiors[0].is_ccw
  1112. result = shapely.orient_polygons([mp], exterior_cw=True)[0]
  1113. assert len(result.geoms) == 2
  1114. for geom in result.geoms:
  1115. assert not geom.exterior.is_ccw
  1116. assert geom.interiors[0].is_ccw
  1117. # in a GeometryCollection
  1118. gc = GeometryCollection([Point(1, 1), polygon, mp])
  1119. result = shapely.orient_polygons(gc)
  1120. assert len(result.geoms) == 3
  1121. assert result.geoms[0] == Point(1, 1)
  1122. assert result.geoms[1] == shapely.orient_polygons(polygon)
  1123. assert result.geoms[2] == shapely.orient_polygons(mp)
  1124. def test_orient_polygons_non_polygonal_input():
  1125. arr = np.array([Point(0, 0), LineString([(0, 0), (1, 1)]), None])
  1126. result = shapely.orient_polygons(arr)
  1127. assert_geometries_equal(result, arr)
  1128. def test_orient_polygons_array():
  1129. # because we have a custom python implementation for older GEOS, need to
  1130. # ensure this has the same capabilities as numpy ufuncs to work with array-likes
  1131. polygon = Polygon(
  1132. [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
  1133. holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]],
  1134. )
  1135. geometries = np.array([[polygon] * 3] * 2)
  1136. actual = shapely.orient_polygons(geometries)
  1137. assert isinstance(actual, np.ndarray)
  1138. assert actual.shape == (2, 3)
  1139. expected = shapely.orient_polygons(polygon)
  1140. assert (actual == expected).all()
  1141. def test_orient_polygons_array_like():
  1142. # because we have a custom python implementation for older GEOS, need to
  1143. # ensure this has the same capabilities as numpy ufuncs to work with array-likes
  1144. polygon = Polygon(
  1145. [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
  1146. holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]],
  1147. )
  1148. geometries = [polygon, Point(2, 2).buffer(1)]
  1149. actual = shapely.orient_polygons(ArrayLike(geometries))
  1150. assert isinstance(actual, ArrayLike)
  1151. expected = shapely.orient_polygons(geometries)
  1152. assert_geometries_equal(np.asarray(actual), expected)
  1153. def test_buffer_deprecate_positional():
  1154. with pytest.deprecated_call(
  1155. match="positional argument `quad_segs` for `buffer` is deprecated"
  1156. ):
  1157. shapely.buffer(point, 1.0, 8)
  1158. with pytest.deprecated_call(
  1159. match="positional arguments `quad_segs` and `cap_style` "
  1160. "for `buffer` are deprecated"
  1161. ):
  1162. shapely.buffer(point, 1.0, 8, "round")
  1163. with pytest.deprecated_call(
  1164. match="positional arguments `quad_segs`, `cap_style`, and `join_style` "
  1165. "for `buffer` are deprecated"
  1166. ):
  1167. shapely.buffer(point, 1.0, 8, "round", "round")
  1168. with pytest.deprecated_call():
  1169. shapely.buffer(point, 1.0, 8, "round", "round", 5.0)
  1170. with pytest.deprecated_call():
  1171. shapely.buffer(point, 1.0, 8, "round", "round", 5.0, False)
  1172. def test_offset_curve_deprecate_positional():
  1173. with pytest.deprecated_call(
  1174. match="positional argument `quad_segs` for `offset_curve` is deprecated"
  1175. ):
  1176. shapely.offset_curve(line_string, 1.0, 8)
  1177. with pytest.deprecated_call(
  1178. match="positional arguments `quad_segs` and `join_style` "
  1179. "for `offset_curve` are deprecated"
  1180. ):
  1181. shapely.offset_curve(line_string, 1.0, 8, "round")
  1182. with pytest.deprecated_call(
  1183. match="positional arguments `quad_segs`, `join_style`, and `mitre_limit` "
  1184. "for `offset_curve` are deprecated"
  1185. ):
  1186. shapely.offset_curve(line_string, 1.0, 8, "round", 5.0)
  1187. def test_simplify_deprecate_positional():
  1188. with pytest.deprecated_call(
  1189. match="positional argument `preserve_topology` for `simplify` is deprecated"
  1190. ):
  1191. shapely.simplify(line_string, 1.0, True)
  1192. def test_voronoi_polygons_deprecate_positional():
  1193. with pytest.deprecated_call(
  1194. match="positional argument `extend_to` for `voronoi_polygons` is deprecated"
  1195. ):
  1196. shapely.voronoi_polygons(multi_point, 0.0, None)
  1197. with pytest.deprecated_call(
  1198. match="positional arguments `extend_to` and `only_edges` "
  1199. "for `voronoi_polygons` are deprecated"
  1200. ):
  1201. shapely.voronoi_polygons(multi_point, 0.0, None, False)
  1202. with pytest.deprecated_call(
  1203. match="positional arguments `extend_to`, `only_edges`, and `ordered` "
  1204. "for `voronoi_polygons` are deprecated"
  1205. ):
  1206. shapely.voronoi_polygons(multi_point, 0.0, None, False, False)