test_io.py 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351
  1. import json
  2. import pickle
  3. import struct
  4. import warnings
  5. from contextlib import nullcontext
  6. import numpy as np
  7. import pytest
  8. import shapely
  9. from shapely import (
  10. GeometryCollection,
  11. GEOSException,
  12. LinearRing,
  13. LineString,
  14. MultiLineString,
  15. MultiPoint,
  16. MultiPolygon,
  17. Point,
  18. Polygon,
  19. )
  20. from shapely.errors import UnsupportedGEOSVersionError
  21. from shapely.testing import assert_geometries_equal
  22. from shapely.tests.common import (
  23. all_types,
  24. all_types_m,
  25. all_types_z,
  26. all_types_zm,
  27. empty_point,
  28. empty_point_m,
  29. empty_point_z,
  30. empty_point_zm,
  31. equal_geometries_abnormally_yield_unequal,
  32. multi_point_empty,
  33. multi_point_empty_m,
  34. multi_point_empty_z,
  35. multi_point_empty_zm,
  36. point,
  37. point_m,
  38. point_z,
  39. point_zm,
  40. polygon_z,
  41. )
  42. EWKBZ = 0x80000000
  43. EWKBM = 0x40000000
  44. EWKBZM = EWKBZ | EWKBM
  45. ISOWKBZ = 1000
  46. ISOWKBM = 2000
  47. ISOWKBZM = ISOWKBZ + ISOWKBM
  48. POINT11_WKB = struct.pack("<BI2d", 1, 1, 1.0, 1.0)
  49. NAN = struct.pack("<d", float("nan"))
  50. POINT_NAN_WKB = struct.pack("<BI", 1, 1) + (NAN * 2)
  51. POINTZ_NAN_WKB = struct.pack("<BI", 1, 1 | EWKBZ) + (NAN * 3)
  52. POINTM_NAN_WKB = struct.pack("<BI", 1, 1 | EWKBM) + (NAN * 3)
  53. POINTZM_NAN_WKB = struct.pack("<BI", 1, 1 | EWKBZM) + (NAN * 4)
  54. MULTIPOINT_NAN_WKB = struct.pack("<BII", 1, 4, 1) + POINT_NAN_WKB
  55. MULTIPOINTZ_NAN_WKB = struct.pack("<BII", 1, 4 | EWKBZ, 1) + POINTZ_NAN_WKB
  56. MULTIPOINTM_NAN_WKB = struct.pack("<BII", 1, 4 | EWKBM, 1) + POINTM_NAN_WKB
  57. MULTIPOINTZM_NAN_WKB = struct.pack("<BII", 1, 4 | EWKBZM, 1) + POINTZM_NAN_WKB
  58. GEOMETRYCOLLECTION_NAN_WKB = struct.pack("<BII", 1, 7, 1) + POINT_NAN_WKB
  59. GEOMETRYCOLLECTIONZ_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBZ, 1) + POINTZ_NAN_WKB
  60. GEOMETRYCOLLECTIONM_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBM, 1) + POINTM_NAN_WKB
  61. GEOMETRYCOLLECTIONZM_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBZM, 1) + POINTZM_NAN_WKB
  62. NESTED_COLLECTION_NAN_WKB = struct.pack("<BII", 1, 7, 1) + MULTIPOINT_NAN_WKB
  63. NESTED_COLLECTIONZ_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBZ, 1) + MULTIPOINTZ_NAN_WKB
  64. NESTED_COLLECTIONM_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBM, 1) + MULTIPOINTM_NAN_WKB
  65. NESTED_COLLECTIONZM_NAN_WKB = (
  66. struct.pack("<BII", 1, 7 | EWKBZM, 1) + MULTIPOINTZM_NAN_WKB
  67. )
  68. INVALID_WKB = "01030000000100000002000000507daec600b1354100de02498e5e3d41306ea321fcb03541a011a53d905e3d41" # noqa: E501
  69. GEOJSON_GEOMETRY = json.dumps({"type": "Point", "coordinates": [125.6, 10.1]}, indent=4)
  70. GEOJSON_FEATURE = json.dumps(
  71. {
  72. "type": "Feature",
  73. "geometry": {"type": "Point", "coordinates": [125.6, 10.1]},
  74. "properties": {"name": "Dinagat Islands"},
  75. },
  76. indent=4,
  77. )
  78. GEOJSON_FEATURECOLECTION = json.dumps(
  79. {
  80. "type": "FeatureCollection",
  81. "features": [
  82. {
  83. "type": "Feature",
  84. "geometry": {"type": "Point", "coordinates": [102.0, 0.6]},
  85. "properties": {"prop0": "value0"},
  86. },
  87. {
  88. "type": "Feature",
  89. "geometry": {
  90. "type": "LineString",
  91. "coordinates": [
  92. [102.0, 0.0],
  93. [103.0, 1.0],
  94. [104.0, 0.0],
  95. [105.0, 1.0],
  96. ],
  97. },
  98. "properties": {"prop1": 0.0, "prop0": "value0"},
  99. },
  100. {
  101. "type": "Feature",
  102. "geometry": {
  103. "type": "Polygon",
  104. "coordinates": [
  105. [
  106. [100.0, 0.0],
  107. [101.0, 0.0],
  108. [101.0, 1.0],
  109. [100.0, 1.0],
  110. [100.0, 0.0],
  111. ]
  112. ],
  113. },
  114. "properties": {"prop1": {"this": "that"}, "prop0": "value0"},
  115. },
  116. ],
  117. },
  118. indent=4,
  119. )
  120. GEOJSON_GEOMETRY_EXPECTED = shapely.points(125.6, 10.1)
  121. GEOJSON_COLLECTION_EXPECTED = [
  122. shapely.points([102.0, 0.6]),
  123. shapely.linestrings([[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]),
  124. shapely.polygons(
  125. [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]
  126. ),
  127. ]
  128. def test_from_wkt():
  129. expected = shapely.points(1, 1)
  130. actual = shapely.from_wkt("POINT (1 1)")
  131. assert_geometries_equal(actual, expected)
  132. # also accept bytes
  133. actual = shapely.from_wkt(b"POINT (1 1)")
  134. assert_geometries_equal(actual, expected)
  135. def test_from_wkt_none():
  136. # None propagates
  137. assert shapely.from_wkt(None) is None
  138. @pytest.mark.parametrize(
  139. "wkt, on_invalid, error, message",
  140. [
  141. (1, "raise", TypeError, "Expected bytes or string, got int"),
  142. ("", "ignore", None, None),
  143. ("", "warn", Warning, "Expected word but encountered end of stream"),
  144. ("", "raise", GEOSException, "Expected word but encountered end of stream"),
  145. ("", "unsupported_option", ValueError, "not a valid option"),
  146. ("LINESTRING (0 0)", "ignore", None, None),
  147. ("LINESTRING (0 0)", "raise", GEOSException, "must contain 0 or >1 elements"),
  148. ("LINESTRING (0 0)", "warn", Warning, "must contain 0 or >1 elements"),
  149. ("NOT A WKT STRING", "ignore", None, None),
  150. ("NOT A WKT STRING", "warn", Warning, "Unknown type: 'NOT'"),
  151. ("POLYGON ((0 0, 0 0))", "ignore", None, None),
  152. ("POLYGON ((0 0, 0 0))", "raise", GEOSException, "Invalid number of points"),
  153. ("POLYGON ((0 0, 0 0))", "warn", Warning, "Invalid number of points"),
  154. ],
  155. )
  156. def test_from_wkt_on_invalid(wkt, on_invalid, error, message):
  157. if on_invalid == "warn":
  158. handler = pytest.warns(error, match=message)
  159. elif on_invalid == "raise":
  160. handler = pytest.raises(error, match=message)
  161. elif on_invalid == "ignore":
  162. handler = nullcontext()
  163. else:
  164. handler = pytest.raises(error, match=message)
  165. with handler:
  166. result = shapely.from_wkt(wkt, on_invalid=on_invalid)
  167. assert result is None
  168. @pytest.mark.skipif(
  169. shapely.geos_version < (3, 11, 0),
  170. reason="on_invalid='fix' not supported with GEOS < 3.11",
  171. )
  172. @pytest.mark.parametrize(
  173. "wkt, expected_wkt",
  174. [
  175. ("", None),
  176. ("LINESTRING (0 0)", None),
  177. ("NOT A WKT STRING", None),
  178. ("POLYGON ((0 0, 0 0))", None),
  179. ("POLYGON ((0 0, 1 1, 0 1))", "POLYGON ((0 0, 1 1, 0 1, 0 0))"),
  180. ("POLYGON ((0 0, 1 1))", "POLYGON ((0 0, 1 1, 0 0))"),
  181. ("MULTIPOLYGON (((5 5, 6 6, 6 5, 5 5)), ((0 0, 0 0)))", None),
  182. (
  183. "MULTIPOLYGON (((5 5, 6 6, 6 5, 5 5)), ((0 0, 1 1)))",
  184. "MULTIPOLYGON (((5 5, 6 6, 6 5, 5 5)), ((0 0, 1 1, 0 0)))",
  185. ),
  186. (
  187. "GEOMETRYCOLLECTION (POLYGON ((5 5, 6 6, 6 5, 5 5)), POLYGON ((0 0, 0 0)))",
  188. None,
  189. ),
  190. ],
  191. )
  192. def test_from_wkt_on_invalid_fix(wkt, expected_wkt):
  193. """Tests for on_invalid="fix".
  194. Geometries that cannot be fixed are returned as None.
  195. """
  196. geom = shapely.from_wkt(wkt, on_invalid="fix")
  197. assert shapely.to_wkt(geom) == expected_wkt
  198. @pytest.mark.skipif(
  199. shapely.geos_version >= (3, 11, 0),
  200. reason="on_invalid='fix' is supported with GEOS >= 3.11",
  201. )
  202. def test_from_wkt_on_invalid_fix_unsupported_geos():
  203. """on_invalid="fix" not supported with GEOS < 3.11"""
  204. with pytest.raises(
  205. ValueError, match="on_invalid='fix' only supported for GEOS >= 3.11"
  206. ):
  207. _ = shapely.from_wkt("", on_invalid="fix")
  208. @pytest.mark.parametrize("geom", all_types)
  209. def test_from_wkt_all_types(geom):
  210. wkt = shapely.to_wkt(geom)
  211. actual = shapely.from_wkt(wkt)
  212. if equal_geometries_abnormally_yield_unequal(geom):
  213. # check abnormal test
  214. with pytest.raises(AssertionError):
  215. assert_geometries_equal(actual, geom)
  216. else:
  217. # normal test
  218. assert_geometries_equal(actual, geom)
  219. @pytest.mark.parametrize(
  220. "wkt",
  221. ("POINT EMPTY", "LINESTRING EMPTY", "POLYGON EMPTY", "GEOMETRYCOLLECTION EMPTY"),
  222. )
  223. def test_from_wkt_empty(wkt):
  224. geom = shapely.from_wkt(wkt)
  225. assert shapely.is_geometry(geom).all()
  226. assert shapely.is_empty(geom).all()
  227. assert shapely.to_wkt(geom) == wkt
  228. # WKT from https://github.com/libgeos/geos/blob/main/tests/unit/io/WKBReaderTest.cpp
  229. @pytest.mark.parametrize(
  230. "wkt",
  231. (
  232. "CIRCULARSTRING(1 3,2 4,3 1)",
  233. "COMPOUNDCURVE(CIRCULARSTRING(1 3,2 4,3 1),(3 1,0 0))",
  234. "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0,2 1,2 3,4 3),(4 3,4 5,1 4,0 0)),CIRCULARSTRING(1.7 1,1.4 0.4,1.6 0.4,1.6 0.5,1.7 1))", # noqa: E501
  235. "MULTICURVE((0 0,5 5),COMPOUNDCURVE((-1 -1,0 0),CIRCULARSTRING(0 0,1 1,2 0)),CIRCULARSTRING(4 0,4 4,8 4))", # noqa: E501
  236. "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0),(1 1,3 3,3 1,1 1)),((10 10,14 12,11 10,10 10),(11 11,11.5 11,11 11.5,11 11)))", # noqa: E501
  237. ),
  238. )
  239. def test_from_wkt_nonlinear_unsupported(wkt):
  240. if shapely.geos_version >= (3, 13, 0):
  241. with pytest.raises(
  242. NotImplementedError,
  243. match="Nonlinear geometry types are not currently supported",
  244. ):
  245. shapely.from_wkt(wkt)
  246. else:
  247. # prior to GEOS 3.13 nonlinear types were rejected by GEOS on read from WKT
  248. with pytest.raises(shapely.errors.GEOSException, match="Unknown type"):
  249. shapely.from_wkt(wkt)
  250. def test_from_wkb():
  251. expected = shapely.points(1, 1)
  252. actual = shapely.from_wkb(POINT11_WKB)
  253. assert_geometries_equal(actual, expected)
  254. def test_from_wkb_hex():
  255. # HEX form
  256. expected = shapely.points(1, 1)
  257. actual = shapely.from_wkb("0101000000000000000000F03F000000000000F03F")
  258. assert_geometries_equal(actual, expected)
  259. actual = shapely.from_wkb(b"0101000000000000000000F03F000000000000F03F")
  260. assert_geometries_equal(actual, expected)
  261. def test_from_wkb_none():
  262. # None propagates
  263. assert shapely.from_wkb(None) is None
  264. @pytest.mark.parametrize(
  265. "wkb, on_invalid, error, message",
  266. [
  267. (1, "raise", TypeError, "Expected bytes or string, got int"),
  268. ("", "ignore", None, None),
  269. ("", "raise", GEOSException, "Unexpected EOF parsing WKB"),
  270. ("", "warn", Warning, "Unexpected EOF parsing WKB"),
  271. ("", "unsupported_option", ValueError, "not a valid option"),
  272. (b"\x01\x01\x00\x00\x00\x00", "ignore", None, None),
  273. (b"\x01\x01\x00\x00\x00\x00", "raise", GEOSException, "ParseException"),
  274. (b"\x01\x01\x00\x00\x00\x00", "warn", Warning, "ParseException"),
  275. (INVALID_WKB, "ignore", None, None),
  276. (
  277. INVALID_WKB,
  278. "raise",
  279. GEOSException,
  280. "Points of LinearRing do not form a closed linestring",
  281. ),
  282. (
  283. INVALID_WKB,
  284. "warn",
  285. Warning,
  286. "Points of LinearRing do not form a closed linestring",
  287. ),
  288. ],
  289. )
  290. def test_from_wkb_on_invalid(wkb, on_invalid, error, message):
  291. if on_invalid == "warn":
  292. handler = pytest.warns(error, match=message)
  293. elif on_invalid == "raise":
  294. handler = pytest.raises(error, match=message)
  295. elif on_invalid == "ignore":
  296. handler = nullcontext()
  297. else:
  298. handler = pytest.raises(error, match=message)
  299. with handler:
  300. result = shapely.from_wkb(wkb, on_invalid=on_invalid)
  301. assert result is None
  302. @pytest.mark.skipif(
  303. shapely.geos_version < (3, 11, 0),
  304. reason="on_invalid='fix' not supported with GEOS < 3.11",
  305. )
  306. @pytest.mark.parametrize(
  307. "wkb, expected_wkt",
  308. [
  309. (b"", None),
  310. (b"\x01\x01\x00\x00\x00\x00", None),
  311. (
  312. INVALID_WKB,
  313. "POLYGON ((1421568.7761 1924750.2852, 1421564.1314 1924752.2408, 1421568.7761 1924750.2852))", # noqa: E501
  314. ),
  315. ],
  316. )
  317. def test_from_wkb_on_invalid_fix(wkb, expected_wkt):
  318. """Tests for on_invalid="fix".
  319. Geometries that cannot be fixed are returned as None.
  320. """
  321. geom = shapely.from_wkb(wkb, on_invalid="fix")
  322. assert shapely.to_wkt(geom) == expected_wkt
  323. @pytest.mark.skipif(
  324. shapely.geos_version >= (3, 11, 0),
  325. reason="on_invalid='fix' is supported with GEOS >= 3.11",
  326. )
  327. def test_from_wkb_on_invalid_fix_unsupported_geos():
  328. """on_invalid="fix" not supported with GEOS < 3.11"""
  329. with pytest.raises(
  330. ValueError, match="on_invalid='fix' only supported for GEOS >= 3.11"
  331. ):
  332. _ = shapely.from_wkb(b"", on_invalid="fix")
  333. @pytest.mark.parametrize("geom", all_types)
  334. @pytest.mark.parametrize("use_hex", [False, True])
  335. @pytest.mark.parametrize("byte_order", [0, 1])
  336. def test_from_wkb_all_types(geom, use_hex, byte_order):
  337. if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING:
  338. pytest.skip("Linearrings are not preserved in WKB")
  339. wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order)
  340. actual = shapely.from_wkb(wkb)
  341. assert_geometries_equal(actual, geom)
  342. @pytest.mark.parametrize("geom", all_types_z)
  343. @pytest.mark.parametrize("use_hex", [False, True])
  344. @pytest.mark.parametrize("byte_order", [0, 1])
  345. def test_from_wkb_all_types_z(geom, use_hex, byte_order):
  346. if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING:
  347. pytest.skip("Linearrings are not preserved in WKB")
  348. wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order)
  349. actual = shapely.from_wkb(wkb)
  350. assert_geometries_equal(actual, geom)
  351. @pytest.mark.skipif(
  352. shapely.geos_version < (3, 12, 0),
  353. reason="M coordinates not supported with GEOS < 3.12",
  354. )
  355. @pytest.mark.parametrize("geom", all_types_m)
  356. @pytest.mark.parametrize("use_hex", [False, True])
  357. @pytest.mark.parametrize("byte_order", [0, 1])
  358. def test_from_wkb_all_types_m(geom, use_hex, byte_order):
  359. if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING:
  360. pytest.skip("Linearrings are not preserved in WKB")
  361. wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order)
  362. actual = shapely.from_wkb(wkb)
  363. assert_geometries_equal(actual, geom)
  364. @pytest.mark.skipif(
  365. shapely.geos_version < (3, 12, 0),
  366. reason="M coordinates not supported with GEOS < 3.12",
  367. )
  368. @pytest.mark.parametrize("geom", all_types_zm)
  369. @pytest.mark.parametrize("use_hex", [False, True])
  370. @pytest.mark.parametrize("byte_order", [0, 1])
  371. def test_from_wkb_all_types_zm(geom, use_hex, byte_order):
  372. if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING:
  373. pytest.skip("Linearrings are not preserved in WKB")
  374. wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order)
  375. actual = shapely.from_wkb(wkb)
  376. assert_geometries_equal(actual, geom)
  377. @pytest.mark.parametrize(
  378. "geom",
  379. (Point(), LineString(), Polygon(), GeometryCollection()),
  380. )
  381. def test_from_wkb_empty(geom):
  382. wkb = shapely.to_wkb(geom)
  383. geom = shapely.from_wkb(wkb)
  384. assert shapely.is_geometry(geom).all()
  385. assert shapely.is_empty(geom).all()
  386. assert shapely.to_wkb(geom) == wkb
  387. # WKB from https://github.com/libgeos/geos/blob/main/tests/unit/io/WKBReaderTest.cpp
  388. @pytest.mark.parametrize(
  389. "wkb",
  390. (
  391. # "CIRCULARSTRING(1 3,2 4,3 1)",
  392. "010800000003000000000000000000F03F0000000000000840000000000000004000000000000010400000000000000840000000000000F03F",
  393. # "COMPOUNDCURVE(CIRCULARSTRING(1 3,2 4,3 1),(3 1,0 0))",
  394. "01090000200E16000002000000010800000003000000000000000000F03F0000000000000840000000000000004000000000000010400000000000000840000000000000F03F0102000000020000000000000000000840000000000000F03F00000000000000000000000000000000",
  395. # "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0,2 1,2 3,4 3),(4 3,4 5,1 4,0 0)),CIRCULARSTRING(1.7 1,1.4 0.4,1.6 0.4,1.6 0.5,1.7 1))", # noqa: E501
  396. "010A0000200E1600000200000001090000000200000001080000000500000000000000000000000000000000000000000000000000004000000000000000000000000000000040000000000000F03F00000000000000400000000000000840000000000000104000000000000008400102000000040000000000000000001040000000000000084000000000000010400000000000001440000000000000F03F000000000000104000000000000000000000000000000000010800000005000000333333333333FB3F000000000000F03F666666666666F63F9A9999999999D93F9A9999999999F93F9A9999999999D93F9A9999999999F93F000000000000E03F333333333333FB3F000000000000F03F",
  397. # "MULTICURVE((0 0,5 5),COMPOUNDCURVE((-1 -1,0 0),CIRCULARSTRING(0 0,1 1,2 0)),CIRCULARSTRING(4 0,4 4,8 4))", # noqa: E501
  398. "010B000000030000000102000000020000000000000000000000000000000000000000000000000014400000000000001440010900000002000000010200000002000000000000000000F0BF000000000000F0BF0000000000000000000000000000000001080000000300000000000000000000000000000000000000000000000000F03F000000000000F03F00000000000000400000000000000000010800000003000000000000000000104000000000000000000000000000001040000000000000104000000000000020400000000000001040",
  399. # "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0),(1 1,3 3,3 1,1 1)),((10 10,14 12,11 10,10 10),(11 11,11.5 11,11 11.5,11 11)))", # noqa: E501
  400. "010C00000002000000010A000000020000000108000000050000000000000000000000000000000000000000000000000010400000000000000000000000000000104000000000000010400000000000000000000000000000104000000000000000000000000000000000010200000004000000000000000000F03F000000000000F03F000000000000084000000000000008400000000000000840000000000000F03F000000000000F03F000000000000F03F01030000000200000004000000000000000000244000000000000024400000000000002C40000000000000284000000000000026400000000000002440000000000000244000000000000024400400000000000000000026400000000000002640000000000000274000000000000026400000000000002640000000000000274000000000000026400000000000002640",
  401. ),
  402. )
  403. def test_from_wkb_nonlinear_unsupported(wkb):
  404. if shapely.geos_version >= (3, 13, 0):
  405. with pytest.raises(
  406. NotImplementedError,
  407. match="Nonlinear geometry types are not currently supported",
  408. ):
  409. shapely.from_wkb(wkb)
  410. else:
  411. # prior to GEOS 3.13 nonlinear types were rejected by GEOS on read from WKB
  412. with pytest.raises(shapely.errors.GEOSException, match="Unknown WKB type"):
  413. shapely.from_wkb(wkb)
  414. def test_to_wkt():
  415. point = shapely.points(1, 1)
  416. actual = shapely.to_wkt(point)
  417. assert actual == "POINT (1 1)"
  418. actual = shapely.to_wkt(point, trim=False)
  419. assert actual == "POINT (1.000000 1.000000)"
  420. actual = shapely.to_wkt(point, rounding_precision=3, trim=False)
  421. assert actual == "POINT (1.000 1.000)"
  422. def test_to_wkt_z():
  423. point = shapely.points(1, 2, 3)
  424. assert shapely.to_wkt(point) == "POINT Z (1 2 3)"
  425. assert shapely.to_wkt(point, output_dimension=2) == "POINT (1 2)"
  426. assert shapely.to_wkt(point, output_dimension=3) == "POINT Z (1 2 3)"
  427. assert shapely.to_wkt(point, old_3d=True) == "POINT (1 2 3)"
  428. if shapely.geos_version >= (3, 12, 0):
  429. assert shapely.to_wkt(point, output_dimension=4) == "POINT Z (1 2 3)"
  430. def test_to_wkt_m():
  431. point = shapely.from_wkt("POINT M (1 2 4)")
  432. assert shapely.to_wkt(point, output_dimension=2) == "POINT (1 2)"
  433. if shapely.geos_version < (3, 12, 0):
  434. # previous behavior was to incorrectly parse M as Z
  435. assert shapely.to_wkt(point) == "POINT Z (1 2 4)"
  436. assert shapely.to_wkt(point, output_dimension=3) == "POINT Z (1 2 4)"
  437. assert shapely.to_wkt(point, old_3d=True) == "POINT (1 2 4)"
  438. else:
  439. assert shapely.to_wkt(point) == "POINT M (1 2 4)"
  440. assert shapely.to_wkt(point, output_dimension=3) == "POINT M (1 2 4)"
  441. assert shapely.to_wkt(point, output_dimension=4) == "POINT M (1 2 4)"
  442. assert shapely.to_wkt(point, old_3d=True) == "POINT M (1 2 4)"
  443. def test_to_wkt_zm():
  444. point = shapely.from_wkt("POINT ZM (1 2 3 4)")
  445. assert shapely.to_wkt(point, output_dimension=2) == "POINT (1 2)"
  446. assert shapely.to_wkt(point, output_dimension=3) == "POINT Z (1 2 3)"
  447. if shapely.geos_version < (3, 12, 0):
  448. # previous behavior was to parse and ignore M
  449. assert shapely.to_wkt(point) == "POINT Z (1 2 3)"
  450. assert shapely.to_wkt(point, old_3d=True) == "POINT (1 2 3)"
  451. else:
  452. assert shapely.to_wkt(point) == "POINT ZM (1 2 3 4)"
  453. assert shapely.to_wkt(point, output_dimension=4) == "POINT ZM (1 2 3 4)"
  454. assert shapely.to_wkt(point, old_3d=True) == "POINT (1 2 3 4)"
  455. def test_to_wkt_none():
  456. # None propagates
  457. assert shapely.to_wkt(None) is None
  458. def test_to_wkt_array_with_empty_z():
  459. # See GH-2004
  460. empty_wkt = ["POINT Z EMPTY", None, "POLYGON Z EMPTY"]
  461. empty_geoms = shapely.from_wkt(empty_wkt)
  462. assert list(shapely.to_wkt(empty_geoms)) == empty_wkt
  463. def test_to_wkt_exceptions():
  464. with pytest.raises(TypeError):
  465. shapely.to_wkt(1)
  466. with pytest.raises(shapely.GEOSException):
  467. shapely.to_wkt(point, output_dimension=5)
  468. def test_to_wkt_point_empty():
  469. assert shapely.to_wkt(empty_point) == "POINT EMPTY"
  470. @pytest.mark.parametrize(
  471. "wkt",
  472. [
  473. "POINT Z EMPTY",
  474. "LINESTRING Z EMPTY",
  475. "LINEARRING Z EMPTY",
  476. "POLYGON Z EMPTY",
  477. ],
  478. )
  479. def test_to_wkt_empty_z(wkt):
  480. assert shapely.to_wkt(shapely.from_wkt(wkt)) == wkt
  481. def test_to_wkt_geometrycollection_with_point_empty():
  482. collection = shapely.geometrycollections([empty_point, point])
  483. # do not check the full value as some GEOS versions give
  484. # GEOMETRYCOLLECTION Z (...) and others give GEOMETRYCOLLECTION (...)
  485. assert shapely.to_wkt(collection).endswith("(POINT EMPTY, POINT (2 3))")
  486. def test_to_wkt_multipoint_with_point_empty():
  487. geom = shapely.multipoints([empty_point, point])
  488. if shapely.geos_version >= (3, 12, 0):
  489. expected = "MULTIPOINT (EMPTY, (2 3))"
  490. else:
  491. # invalid WKT form
  492. expected = "MULTIPOINT (EMPTY, 2 3)"
  493. assert shapely.to_wkt(geom) == expected
  494. @pytest.mark.parametrize("geom", [Point(1e100, 0), Point(0, 1e100)])
  495. def test_to_wkt_large_float_ok(geom):
  496. # https://github.com/shapely/shapely/issues/1903
  497. shapely.to_wkt(geom)
  498. assert "Exception in WKT writer" not in repr(geom)
  499. @pytest.mark.parametrize("geom", [Point(1e101, 0), Point(0, 1e101)])
  500. def test_to_wkt_large_float(geom):
  501. if shapely.geos_version >= (3, 13, 0):
  502. # round-trip WKT
  503. assert geom.equals(shapely.from_wkt(shapely.to_wkt(geom)))
  504. else:
  505. # https://github.com/shapely/shapely/issues/1903
  506. with pytest.raises(
  507. ValueError, match="WKT output of coordinates greater than.*"
  508. ):
  509. shapely.to_wkt(geom)
  510. assert "Exception in WKT writer" in repr(geom)
  511. @pytest.mark.parametrize(
  512. "geom",
  513. [
  514. # We implemented our own "GetZMax", so go through all geometry types:
  515. Point(0, 0, 1e101),
  516. LineString([(0, 0, 0), (0, 0, 1e101)]),
  517. LinearRing([(0, 0, 0), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)]),
  518. Polygon([(0, 0, 0), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)]),
  519. Polygon(
  520. [(0, 0, 0), (0, 10, 0), (10, 0, 0), (0, 0, 0)],
  521. [[(0, 0, 0), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)]],
  522. ),
  523. MultiPoint([(0, 0, 0), (0, 0, 1e101)]),
  524. MultiLineString(
  525. [LineString([(0, 0, 0), (0, 1, 0)]), LineString([(0, 0, 0), (0, 1, 1e101)])]
  526. ),
  527. MultiPolygon(
  528. [polygon_z, Polygon([(0, 0, 0), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)])]
  529. ),
  530. GeometryCollection([point_z, Point(0, 0, 1e101)]),
  531. GeometryCollection([GeometryCollection([Point(0, 0, 1e101)])]),
  532. LineString([(0, 0, np.nan), (0, 0, 1e101)]),
  533. Polygon([(0, 0, np.nan), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)]),
  534. GeometryCollection([Point(0, 0), Point(0, 0, 1e101)]),
  535. ],
  536. )
  537. def test_to_wkt_large_float_3d_no_crash(geom):
  538. # https://github.com/shapely/shapely/issues/1903
  539. # just test if there is a crash (detailed behaviour differs per GEOS version)
  540. try:
  541. shapely.to_wkt(geom)
  542. except ValueError as e:
  543. assert str(e).startswith("WKT output of coordinates greater than")
  544. repr(geom)
  545. def test_to_wkt_large_float_skip_z():
  546. # https://github.com/shapely/shapely/issues/1903
  547. assert shapely.to_wkt(Point(0, 0, 1e101), output_dimension=2) == "POINT (0 0)"
  548. def test_to_wkt_large_float_no_trim():
  549. # https://github.com/shapely/shapely/issues/1903
  550. # don't test the exact number, it is ridiculously large and probably platform
  551. # dependent
  552. assert shapely.to_wkt(Point(1e101, 0), trim=False).startswith("POINT (")
  553. def test_repr():
  554. assert repr(point) == "<POINT (2 3)>"
  555. assert repr(point_z) == "<POINT Z (2 3 4)>"
  556. @pytest.mark.skipif(
  557. shapely.geos_version < (3, 12, 0),
  558. reason="M coordinates not supported with GEOS < 3.12",
  559. )
  560. def test_repr_m():
  561. assert repr(point_m) == "<POINT M (2 3 5)>"
  562. assert repr(point_zm) == "<POINT ZM (2 3 4 5)>"
  563. def test_repr_max_length():
  564. # the repr is limited to 80 characters
  565. geom = shapely.linestrings(np.arange(1000), np.arange(1000))
  566. representation = repr(geom)
  567. assert len(representation) == 80
  568. assert representation.endswith("...>")
  569. def test_repr_point_z_empty():
  570. assert repr(empty_point_z) == "<POINT Z EMPTY>"
  571. @pytest.mark.skipif(
  572. shapely.geos_version < (3, 12, 0),
  573. reason="M coordinates not supported with GEOS < 3.12",
  574. )
  575. def test_repr_point_m_empty():
  576. assert repr(empty_point_m) == "<POINT M EMPTY>"
  577. assert repr(empty_point_zm) == "<POINT ZM EMPTY>"
  578. def test_to_wkb():
  579. point = shapely.points(1, 1)
  580. actual = shapely.to_wkb(point, byte_order=1)
  581. assert actual == POINT11_WKB
  582. def test_to_wkb_hex():
  583. point = shapely.points(1, 1)
  584. actual = shapely.to_wkb(point, hex=True, byte_order=1)
  585. le = "01"
  586. point_type = "01000000"
  587. coord = "000000000000F03F" # 1.0 as double (LE)
  588. assert actual == le + point_type + 2 * coord
  589. def test_to_wkb_z():
  590. point = shapely.points(1, 2, 3)
  591. expected_wkb = struct.pack("<BI2d", 1, 1, 1.0, 2.0)
  592. expected_wkb_z = struct.pack("<BI3d", 1, 1 | EWKBZ, 1.0, 2.0, 3.0)
  593. assert shapely.to_wkb(point, byte_order=1) == expected_wkb_z
  594. assert shapely.to_wkb(point, output_dimension=2, byte_order=1) == expected_wkb
  595. assert shapely.to_wkb(point, output_dimension=3, byte_order=1) == expected_wkb_z
  596. if shapely.geos_version >= (3, 12, 0):
  597. assert shapely.to_wkb(point, output_dimension=4, byte_order=1) == expected_wkb_z
  598. def test_to_wkb_m():
  599. # POINT M (1 2 4)
  600. point = shapely.from_wkb(struct.pack("<BI3d", 1, 1 | EWKBM, 1.0, 2.0, 4.0))
  601. expected_wkb = struct.pack("<BI2d", 1, 1, 1.0, 2.0)
  602. expected_wkb_m = struct.pack("<BI3d", 1, 1 | EWKBM, 1.0, 2.0, 4.0)
  603. if shapely.geos_version < (3, 12, 0):
  604. # previous behavior was to ignore M, treat as 2D
  605. expected_wkb_m = expected_wkb
  606. assert shapely.to_wkb(point, byte_order=1) == expected_wkb_m
  607. assert shapely.to_wkb(point, output_dimension=2, byte_order=1) == expected_wkb
  608. assert shapely.to_wkb(point, output_dimension=3, byte_order=1) == expected_wkb_m
  609. if shapely.geos_version >= (3, 12, 0):
  610. assert shapely.to_wkb(point, output_dimension=4, byte_order=1) == expected_wkb_m
  611. def test_to_wkb_zm():
  612. # POINT ZM (1 2 3 4)
  613. point = shapely.from_wkb(struct.pack("<BI4d", 1, 1 | EWKBZM, 1.0, 2.0, 3.0, 4.0))
  614. expected_wkb = struct.pack("<BI2d", 1, 1, 1.0, 2.0)
  615. expected_wkb_z = struct.pack("<BI3d", 1, 1 | EWKBZ, 1.0, 2.0, 3.0)
  616. expected_wkb_zm = struct.pack("<BI4d", 1, 1 | EWKBZM, 1.0, 2.0, 3.0, 4.0)
  617. if shapely.geos_version < (3, 12, 0):
  618. # previous behavior was to ignore M, treat as XYZ
  619. expected_wkb_zm = expected_wkb_z
  620. assert shapely.to_wkb(point, byte_order=1) == expected_wkb_zm
  621. assert shapely.to_wkb(point, output_dimension=2, byte_order=1) == expected_wkb
  622. assert shapely.to_wkb(point, output_dimension=3, byte_order=1) == expected_wkb_z
  623. if shapely.geos_version >= (3, 12, 0):
  624. assert (
  625. shapely.to_wkb(point, output_dimension=4, byte_order=1) == expected_wkb_zm
  626. )
  627. def test_to_wkb_none():
  628. # None propagates
  629. assert shapely.to_wkb(None) is None
  630. def test_to_wkb_exceptions():
  631. with pytest.raises(TypeError):
  632. shapely.to_wkb(1)
  633. with pytest.raises(shapely.GEOSException):
  634. shapely.to_wkb(point, output_dimension=5)
  635. with pytest.raises(ValueError):
  636. shapely.to_wkb(point, flavor="other")
  637. def test_to_wkb_byte_order():
  638. point = shapely.points(1.0, 1.0)
  639. be = b"\x00"
  640. le = b"\x01"
  641. point_type = b"\x01\x00\x00\x00" # 1 as 32-bit uint (LE)
  642. coord = b"\x00\x00\x00\x00\x00\x00\xf0?" # 1.0 as double (LE)
  643. assert shapely.to_wkb(point, byte_order=1) == le + point_type + 2 * coord
  644. assert (
  645. shapely.to_wkb(point, byte_order=0) == be + point_type[::-1] + 2 * coord[::-1]
  646. )
  647. def test_to_wkb_srid():
  648. # hex representation of POINT (0 0) with SRID=4
  649. ewkb = "01010000200400000000000000000000000000000000000000"
  650. wkb = "010100000000000000000000000000000000000000"
  651. actual = shapely.from_wkb(ewkb)
  652. assert shapely.to_wkt(actual, trim=True) == "POINT (0 0)"
  653. assert shapely.to_wkb(actual, hex=True, byte_order=1) == wkb
  654. assert shapely.to_wkb(actual, hex=True, include_srid=True, byte_order=1) == ewkb
  655. point = shapely.points(1, 1)
  656. point_with_srid = shapely.set_srid(point, np.int32(4326))
  657. result = shapely.to_wkb(point_with_srid, include_srid=True, byte_order=1)
  658. assert np.frombuffer(result[5:9], "<u4").item() == 4326
  659. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10.0")
  660. def test_to_wkb_flavor():
  661. # http://libgeos.org/specifications/wkb/#extended-wkb
  662. actual = shapely.to_wkb(point_z, byte_order=1) # default "extended"
  663. assert actual.hex()[2:10] == struct.pack("<I", 1 | EWKBZ).hex()
  664. actual = shapely.to_wkb(point_z, byte_order=1, flavor="extended")
  665. assert actual.hex()[2:10] == struct.pack("<I", 1 | EWKBZ).hex()
  666. actual = shapely.to_wkb(point_z, byte_order=1, flavor="iso")
  667. assert actual.hex()[2:10] == struct.pack("<I", 1 | ISOWKBZ).hex()
  668. @pytest.mark.skipif(
  669. shapely.geos_version < (3, 12, 0),
  670. reason="M coordinates not supported with GEOS < 3.12",
  671. )
  672. def test_to_wkb_m_flavor():
  673. # XYM
  674. actual = shapely.to_wkb(point_m, byte_order=1) # default "extended"
  675. assert actual.hex()[2:10] == struct.pack("<I", 1 | EWKBM).hex()
  676. actual = shapely.to_wkb(point_m, byte_order=1, flavor="iso")
  677. assert actual.hex()[2:10] == struct.pack("<I", 1 | ISOWKBM).hex()
  678. # XYZM
  679. actual = shapely.to_wkb(point_zm, byte_order=1) # default "extended"
  680. assert actual.hex()[2:10] == struct.pack("<I", 1 | EWKBZM).hex()
  681. actual = shapely.to_wkb(point_zm, byte_order=1, flavor="iso")
  682. assert actual.hex()[2:10] == struct.pack("<I", 1 | ISOWKBZM).hex()
  683. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10.0")
  684. def test_to_wkb_flavor_srid():
  685. with pytest.raises(ValueError, match="cannot be used together"):
  686. shapely.to_wkb(point_z, include_srid=True, flavor="iso")
  687. @pytest.mark.skipif(shapely.geos_version >= (3, 10, 0), reason="GEOS < 3.10.0")
  688. def test_to_wkb_flavor_unsupported_geos():
  689. with pytest.raises(UnsupportedGEOSVersionError):
  690. shapely.to_wkb(point_z, flavor="iso")
  691. @pytest.mark.parametrize(
  692. "geom,expected",
  693. [
  694. pytest.param(empty_point, POINT_NAN_WKB, id="POINT EMPTY"),
  695. pytest.param(empty_point_z, POINT_NAN_WKB, id="POINT Z EMPTY"),
  696. pytest.param(empty_point_m, POINT_NAN_WKB, id="POINT M EMPTY"),
  697. pytest.param(empty_point_zm, POINT_NAN_WKB, id="POINT ZM EMPTY"),
  698. pytest.param(
  699. multi_point_empty,
  700. MULTIPOINT_NAN_WKB,
  701. id="MULTIPOINT EMPTY",
  702. ),
  703. pytest.param(
  704. multi_point_empty_z,
  705. MULTIPOINT_NAN_WKB,
  706. id="MULTIPOINT Z EMPTY",
  707. ),
  708. pytest.param(
  709. multi_point_empty_m,
  710. MULTIPOINT_NAN_WKB,
  711. id="MULTIPOINT M EMPTY",
  712. ),
  713. pytest.param(
  714. multi_point_empty_zm,
  715. MULTIPOINT_NAN_WKB,
  716. id="MULTIPOINT ZM EMPTY",
  717. ),
  718. pytest.param(
  719. shapely.geometrycollections([empty_point]),
  720. GEOMETRYCOLLECTION_NAN_WKB,
  721. id="GEOMETRYCOLLECTION (POINT EMPTY)",
  722. ),
  723. pytest.param(
  724. shapely.geometrycollections([empty_point_z]),
  725. GEOMETRYCOLLECTION_NAN_WKB,
  726. id="GEOMETRYCOLLECTION (POINT Z EMPTY)",
  727. ),
  728. pytest.param(
  729. shapely.geometrycollections([empty_point_m]),
  730. GEOMETRYCOLLECTION_NAN_WKB,
  731. id="GEOMETRYCOLLECTION (POINT M EMPTY)",
  732. ),
  733. pytest.param(
  734. shapely.geometrycollections([empty_point_zm]),
  735. GEOMETRYCOLLECTION_NAN_WKB,
  736. id="GEOMETRYCOLLECTION (POINT ZM EMPTY)",
  737. ),
  738. pytest.param(
  739. shapely.geometrycollections([multi_point_empty]),
  740. NESTED_COLLECTION_NAN_WKB,
  741. id="GEOMETRYCOLLECTION (MULTIPOINT EMPTY)",
  742. ),
  743. pytest.param(
  744. shapely.geometrycollections([multi_point_empty_z]),
  745. NESTED_COLLECTION_NAN_WKB,
  746. id="GEOMETRYCOLLECTION (MULTIPOINT Z EMPTY)",
  747. ),
  748. pytest.param(
  749. shapely.geometrycollections([multi_point_empty_m]),
  750. NESTED_COLLECTION_NAN_WKB,
  751. id="GEOMETRYCOLLECTION (MULTIPOINT M EMPTY)",
  752. ),
  753. pytest.param(
  754. shapely.geometrycollections([multi_point_empty_zm]),
  755. NESTED_COLLECTION_NAN_WKB,
  756. id="GEOMETRYCOLLECTION (MULTIPOINT ZM EMPTY)",
  757. ),
  758. ],
  759. )
  760. def test_to_wkb_point_empty_2d(geom, expected):
  761. actual = shapely.to_wkb(geom, output_dimension=2, byte_order=1)
  762. # Split 'actual' into header and coordinates
  763. coordinate_length = 16
  764. header_length = len(expected) - coordinate_length
  765. # Check the total length (this checks the correct dimensionality)
  766. assert len(actual) == header_length + coordinate_length
  767. # Check the header
  768. assert actual[:header_length] == expected[:header_length]
  769. # Check the coordinates (using numpy.isnan; there are many byte representations for
  770. # NaN)
  771. assert np.isnan(struct.unpack("<2d", actual[header_length:])).all()
  772. @pytest.mark.parametrize(
  773. "geom,expected",
  774. [
  775. pytest.param(empty_point_z, POINTZ_NAN_WKB, id="POINT Z EMPTY"),
  776. pytest.param(empty_point_zm, POINTZ_NAN_WKB, id="POINT ZM EMPTY"),
  777. pytest.param(
  778. multi_point_empty_z,
  779. MULTIPOINTZ_NAN_WKB,
  780. id="MULTIPOINT Z EMPTY",
  781. ),
  782. pytest.param(
  783. multi_point_empty_zm,
  784. MULTIPOINTZ_NAN_WKB,
  785. id="MULTIPOINT ZM EMPTY",
  786. ),
  787. pytest.param(
  788. shapely.geometrycollections([empty_point_z]),
  789. GEOMETRYCOLLECTIONZ_NAN_WKB,
  790. id="GEOMETRYCOLLECTION (POINT Z EMPTY)",
  791. ),
  792. pytest.param(
  793. shapely.geometrycollections([empty_point_zm]),
  794. GEOMETRYCOLLECTIONZ_NAN_WKB,
  795. id="GEOMETRYCOLLECTION (POINT ZM EMPTY)",
  796. ),
  797. pytest.param(
  798. shapely.geometrycollections([multi_point_empty_z]),
  799. NESTED_COLLECTIONZ_NAN_WKB,
  800. id="GEOMETRYCOLLECTION (MULTIPOINT Z EMPTY)",
  801. ),
  802. pytest.param(
  803. shapely.geometrycollections([multi_point_empty_zm]),
  804. NESTED_COLLECTIONZ_NAN_WKB,
  805. id="GEOMETRYCOLLECTION (MULTIPOINT ZM EMPTY)",
  806. ),
  807. ],
  808. )
  809. def test_to_wkb_point_empty_z(geom, expected):
  810. actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1)
  811. # Split 'actual' into header and coordinates
  812. coordinate_length = 8 * 3
  813. header_length = len(expected) - coordinate_length
  814. # Check the total length (this checks the correct dimensionality)
  815. assert len(actual) == header_length + coordinate_length
  816. # Check the header
  817. assert actual[:header_length] == expected[:header_length]
  818. # Check the coordinates (using numpy.isnan; there are many byte representations for
  819. # NaN)
  820. assert np.isnan(struct.unpack("<3d", actual[header_length:])).all()
  821. @pytest.mark.skipif(
  822. shapely.geos_version < (3, 12, 0),
  823. reason="M coordinates not supported with GEOS < 3.12",
  824. )
  825. @pytest.mark.parametrize(
  826. "geom,expected",
  827. [
  828. pytest.param(empty_point_m, POINTM_NAN_WKB, id="POINT M EMPTY"),
  829. pytest.param(
  830. multi_point_empty_m,
  831. MULTIPOINTM_NAN_WKB,
  832. id="MULTIPOINT M EMPTY",
  833. ),
  834. pytest.param(
  835. shapely.geometrycollections([empty_point_m]),
  836. GEOMETRYCOLLECTIONM_NAN_WKB,
  837. id="GEOMETRYCOLLECTION (POINT M EMPTY)",
  838. ),
  839. pytest.param(
  840. shapely.geometrycollections([multi_point_empty_m]),
  841. NESTED_COLLECTIONM_NAN_WKB,
  842. id="GEOMETRYCOLLECTION (MULTIPOINT M EMPTY)",
  843. ),
  844. ],
  845. )
  846. def test_to_wkb_point_empty_m(geom, expected):
  847. actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1)
  848. # Split 'actual' into header and coordinates
  849. coordinate_length = 8 * 3
  850. header_length = len(expected) - coordinate_length
  851. assert len(actual) == header_length + coordinate_length
  852. assert actual[:header_length] == expected[:header_length]
  853. assert np.isnan(struct.unpack("<3d", actual[header_length:])).all()
  854. @pytest.mark.skipif(
  855. shapely.geos_version < (3, 12, 0),
  856. reason="M coordinates not supported with GEOS < 3.12",
  857. )
  858. @pytest.mark.parametrize(
  859. "geom,expected",
  860. [
  861. pytest.param(empty_point_zm, POINTZM_NAN_WKB, id="POINT ZM EMPTY"),
  862. pytest.param(
  863. multi_point_empty_zm,
  864. MULTIPOINTZM_NAN_WKB,
  865. id="MULTIPOINT ZM EMPTY",
  866. ),
  867. pytest.param(
  868. shapely.geometrycollections([empty_point_zm]),
  869. GEOMETRYCOLLECTIONZM_NAN_WKB,
  870. id="GEOMETRYCOLLECTION (POINT ZM EMPTY)",
  871. ),
  872. pytest.param(
  873. shapely.geometrycollections([multi_point_empty_zm]),
  874. NESTED_COLLECTIONZM_NAN_WKB,
  875. id="GEOMETRYCOLLECTION (MULTIPOINT ZM EMPTY)",
  876. ),
  877. ],
  878. )
  879. def test_to_wkb_point_empty_zm(geom, expected):
  880. actual = shapely.to_wkb(geom, output_dimension=4, byte_order=1)
  881. # Split 'actual' into header and coordinates
  882. coordinate_length = 8 * 4
  883. header_length = len(expected) - coordinate_length
  884. assert len(actual) == header_length + coordinate_length
  885. assert actual[:header_length] == expected[:header_length]
  886. assert np.isnan(struct.unpack("<4d", actual[header_length:])).all()
  887. @pytest.mark.parametrize(
  888. "geom,expected",
  889. [
  890. pytest.param(empty_point, POINT_NAN_WKB, id="POINT EMPTY"),
  891. pytest.param(multi_point_empty, MULTIPOINT_NAN_WKB, id="MULTIPOINT EMPTY"),
  892. pytest.param(
  893. shapely.geometrycollections([empty_point]),
  894. GEOMETRYCOLLECTION_NAN_WKB,
  895. id="GEOMETRYCOLLECTION (POINT EMPTY)",
  896. ),
  897. pytest.param(
  898. shapely.geometrycollections([multi_point_empty]),
  899. NESTED_COLLECTION_NAN_WKB,
  900. id="GEOMETRYCOLLECTION (MULTIPOINT EMPTY)",
  901. ),
  902. ],
  903. )
  904. def test_to_wkb_point_empty_2d_output_dim_3(geom, expected):
  905. actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1)
  906. # Split 'actual' into header and coordinates
  907. coordinate_length = 16
  908. header_length = len(expected) - coordinate_length
  909. # Check the total length (this checks the correct dimensionality)
  910. assert len(actual) == header_length + coordinate_length
  911. # Check the header
  912. assert actual[:header_length] == expected[:header_length]
  913. # Check the coordinates (using numpy.isnan; there are many byte representations for
  914. # NaN)
  915. assert np.isnan(struct.unpack("<2d", actual[header_length:])).all()
  916. @pytest.mark.parametrize(
  917. "wkb,expected_type,expected_dim",
  918. [
  919. pytest.param(POINT_NAN_WKB, 0, 2, id="POINT_NAN_WKB"),
  920. pytest.param(POINTZ_NAN_WKB, 0, 3, id="POINTZ_NAN_WKB"),
  921. pytest.param(MULTIPOINT_NAN_WKB, 4, 2, id="MULTIPOINT_NAN_WKB"),
  922. pytest.param(MULTIPOINTZ_NAN_WKB, 4, 3, id="MULTIPOINTZ_NAN_WKB"),
  923. pytest.param(GEOMETRYCOLLECTION_NAN_WKB, 7, 2, id="GEOMETRYCOLLECTION_NAN_WKB"),
  924. pytest.param(
  925. GEOMETRYCOLLECTIONZ_NAN_WKB, 7, 3, id="GEOMETRYCOLLECTIONZ_NAN_WKB"
  926. ),
  927. pytest.param(NESTED_COLLECTION_NAN_WKB, 7, 2, id="NESTED_COLLECTION_NAN_WKB"),
  928. pytest.param(NESTED_COLLECTIONZ_NAN_WKB, 7, 3, id="NESTED_COLLECTIONZ_NAN_WKB"),
  929. ],
  930. )
  931. def test_from_wkb_point_empty(wkb, expected_type, expected_dim):
  932. geom = shapely.from_wkb(wkb)
  933. # POINT (nan nan) transforms to an empty point
  934. assert shapely.is_empty(geom)
  935. assert shapely.get_type_id(geom) == expected_type
  936. assert shapely.get_coordinate_dimension(geom) == expected_dim
  937. @pytest.mark.skipif(
  938. shapely.geos_version < (3, 12, 0),
  939. reason="M coordinates not supported with GEOS < 3.12",
  940. )
  941. @pytest.mark.parametrize(
  942. "wkb,expected_type",
  943. [
  944. pytest.param(POINTM_NAN_WKB, 0, id="POINTM_NAN_WKB"),
  945. pytest.param(MULTIPOINTM_NAN_WKB, 4, id="MULTIPOINTM_NAN_WKB"),
  946. pytest.param(GEOMETRYCOLLECTIONM_NAN_WKB, 7, id="GEOMETRYCOLLECTIONM_NAN_WKB"),
  947. pytest.param(NESTED_COLLECTIONM_NAN_WKB, 7, id="NESTED_COLLECTIONM_NAN_WKB"),
  948. ],
  949. )
  950. def test_from_wkb_point_empty_m(wkb, expected_type):
  951. geom = shapely.from_wkb(wkb)
  952. assert shapely.is_empty(geom)
  953. assert shapely.get_type_id(geom) == expected_type
  954. assert shapely.get_coordinate_dimension(geom) == 3
  955. assert not shapely.has_z(geom)
  956. assert shapely.has_m(geom)
  957. @pytest.mark.skipif(
  958. shapely.geos_version < (3, 12, 0),
  959. reason="M coordinates not supported with GEOS < 3.12",
  960. )
  961. @pytest.mark.parametrize(
  962. "wkb,expected_type",
  963. [
  964. pytest.param(POINTZM_NAN_WKB, 0, id="POINTZM_NAN_WKB"),
  965. pytest.param(MULTIPOINTZM_NAN_WKB, 4, id="MULTIPOINTZM_NAN_WKB"),
  966. pytest.param(
  967. GEOMETRYCOLLECTIONZM_NAN_WKB, 7, id="GEOMETRYCOLLECTIONZM_NAN_WKB"
  968. ),
  969. pytest.param(NESTED_COLLECTIONZM_NAN_WKB, 7, id="NESTED_COLLECTIONZM_NAN_WKB"),
  970. ],
  971. )
  972. def test_from_wkb_point_empty_zm(wkb, expected_type):
  973. geom = shapely.from_wkb(wkb)
  974. assert shapely.is_empty(geom)
  975. assert shapely.get_type_id(geom) == expected_type
  976. assert shapely.get_coordinate_dimension(geom) == 4
  977. assert shapely.has_z(geom)
  978. assert shapely.has_m(geom)
  979. def test_to_wkb_point_empty_srid():
  980. expected = shapely.set_srid(empty_point, 4236)
  981. wkb = shapely.to_wkb(expected, include_srid=True)
  982. actual = shapely.from_wkb(wkb)
  983. assert shapely.get_srid(actual) == 4236
  984. @pytest.mark.parametrize("geom", all_types + (point_z, empty_point))
  985. def test_pickle(geom):
  986. pickled = pickle.dumps(geom)
  987. assert_geometries_equal(pickle.loads(pickled), geom, tolerance=0)
  988. @pytest.mark.parametrize("geom", all_types_z)
  989. def test_pickle_z(geom):
  990. pickled = pickle.dumps(geom)
  991. actual = pickle.loads(pickled)
  992. assert_geometries_equal(actual, geom, tolerance=0)
  993. if not actual.is_empty: # GEOSHasZ with EMPTY geometries is inconsistent
  994. assert actual.has_z
  995. if shapely.geos_version >= (3, 12, 0):
  996. assert not actual.has_m
  997. @pytest.mark.skipif(
  998. shapely.geos_version < (3, 12, 0),
  999. reason="M coordinates not supported with GEOS < 3.12",
  1000. )
  1001. @pytest.mark.parametrize("geom", all_types_m)
  1002. def test_pickle_m(geom):
  1003. pickled = pickle.dumps(geom)
  1004. actual = pickle.loads(pickled)
  1005. assert_geometries_equal(actual, geom, tolerance=0)
  1006. assert not actual.has_z
  1007. if not actual.is_empty: # GEOSHasM with EMPTY geometries is inconsistent
  1008. assert actual.has_m
  1009. @pytest.mark.skipif(
  1010. shapely.geos_version < (3, 12, 0),
  1011. reason="M coordinates not supported with GEOS < 3.12",
  1012. )
  1013. @pytest.mark.parametrize("geom", all_types_zm)
  1014. def test_pickle_zm(geom):
  1015. pickled = pickle.dumps(geom)
  1016. actual = pickle.loads(pickled)
  1017. assert_geometries_equal(actual, geom, tolerance=0)
  1018. if not actual.is_empty: # GEOSHasZ with EMPTY geometries is inconsistent
  1019. assert actual.has_z
  1020. assert actual.has_m
  1021. @pytest.mark.parametrize("geom", all_types + (point_z, empty_point))
  1022. def test_pickle_with_srid(geom):
  1023. geom = shapely.set_srid(geom, 4326)
  1024. pickled = pickle.dumps(geom)
  1025. assert shapely.get_srid(pickle.loads(pickled)) == 4326
  1026. @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
  1027. @pytest.mark.parametrize(
  1028. "geojson,expected",
  1029. [
  1030. pytest.param(
  1031. GEOJSON_GEOMETRY, GEOJSON_GEOMETRY_EXPECTED, id="GEOJSON_GEOMETRY"
  1032. ),
  1033. pytest.param(GEOJSON_FEATURE, GEOJSON_GEOMETRY_EXPECTED, id="GEOJSON_FEATURE"),
  1034. pytest.param(
  1035. GEOJSON_FEATURECOLECTION,
  1036. shapely.geometrycollections(GEOJSON_COLLECTION_EXPECTED),
  1037. id="GEOJSON_FEATURECOLECTION",
  1038. ),
  1039. pytest.param(
  1040. [GEOJSON_GEOMETRY] * 2,
  1041. [GEOJSON_GEOMETRY_EXPECTED] * 2,
  1042. id="GEOJSON_GEOMETRYx2",
  1043. ),
  1044. pytest.param(None, None, id="None"),
  1045. pytest.param(
  1046. [GEOJSON_GEOMETRY, None],
  1047. [GEOJSON_GEOMETRY_EXPECTED, None],
  1048. id="GEOJSON_GEOMETRY_None",
  1049. ),
  1050. ],
  1051. )
  1052. def test_from_geojson(geojson, expected):
  1053. actual = shapely.from_geojson(geojson)
  1054. assert_geometries_equal(actual, expected)
  1055. @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
  1056. def test_from_geojson_exceptions():
  1057. with pytest.raises(TypeError, match="Expected bytes or string, got int"):
  1058. shapely.from_geojson(1)
  1059. with pytest.raises(shapely.GEOSException, match="Error parsing JSON"):
  1060. shapely.from_geojson("")
  1061. with pytest.raises(shapely.GEOSException, match="Unknown geometry type"):
  1062. shapely.from_geojson('{"type": "NoGeometry", "coordinates": []}')
  1063. with pytest.raises(shapely.GEOSException, match="type must be array, but is null"):
  1064. shapely.from_geojson('{"type": "LineString", "coordinates": null}')
  1065. # Note: The two below tests are the reason that from_geojson is disabled for
  1066. # GEOS 3.10.0 See https://trac.osgeo.org/geos/ticket/1138
  1067. with pytest.raises(shapely.GEOSException, match="key 'type' not found"):
  1068. shapely.from_geojson('{"geometry": null, "properties": []}')
  1069. with pytest.raises(shapely.GEOSException, match="key 'type' not found"):
  1070. shapely.from_geojson('{"no": "geojson"}')
  1071. @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
  1072. def test_from_geojson_warn_on_invalid():
  1073. with pytest.warns(Warning, match="Invalid GeoJSON"):
  1074. assert shapely.from_geojson("", on_invalid="warn") is None
  1075. @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
  1076. def test_from_geojson_ignore_on_invalid():
  1077. with warnings.catch_warnings():
  1078. warnings.simplefilter("error")
  1079. assert shapely.from_geojson("", on_invalid="ignore") is None
  1080. @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
  1081. def test_from_geojson_on_invalid_unsupported_option():
  1082. with pytest.raises(ValueError, match="not a valid option"):
  1083. shapely.from_geojson(GEOJSON_GEOMETRY, on_invalid="unsupported_option")
  1084. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  1085. @pytest.mark.parametrize(
  1086. "expected,geometry",
  1087. [
  1088. pytest.param(
  1089. GEOJSON_GEOMETRY, GEOJSON_GEOMETRY_EXPECTED, id="GEOJSON_GEOMETRY"
  1090. ),
  1091. pytest.param(
  1092. [GEOJSON_GEOMETRY] * 2,
  1093. [GEOJSON_GEOMETRY_EXPECTED] * 2,
  1094. id="GEOJSON_GEOMETRYx2",
  1095. ),
  1096. pytest.param(None, None, id="None"),
  1097. pytest.param(
  1098. [GEOJSON_GEOMETRY, None],
  1099. [GEOJSON_GEOMETRY_EXPECTED, None],
  1100. id="GEOJSON_GEOMETRY_None",
  1101. ),
  1102. ],
  1103. )
  1104. def test_to_geojson(geometry, expected):
  1105. actual = shapely.to_geojson(geometry, indent=4)
  1106. assert np.all(actual == np.asarray(expected))
  1107. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  1108. @pytest.mark.parametrize("indent", [None, 0, 4])
  1109. def test_to_geojson_indent(indent):
  1110. separators = (",", ":") if indent is None else (",", ": ")
  1111. expected = json.dumps(
  1112. json.loads(GEOJSON_GEOMETRY), indent=indent, separators=separators
  1113. )
  1114. actual = shapely.to_geojson(GEOJSON_GEOMETRY_EXPECTED, indent=indent)
  1115. assert actual == expected
  1116. @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
  1117. def test_to_geojson_exceptions():
  1118. with pytest.raises(TypeError):
  1119. shapely.to_geojson(1)
  1120. @pytest.mark.skipif(shapely.geos_version < (3, 10, 2), reason="GEOS < 3.10.2")
  1121. @pytest.mark.parametrize(
  1122. "geom",
  1123. [
  1124. empty_point,
  1125. shapely.multipoints([empty_point, point]),
  1126. shapely.geometrycollections([empty_point, point]),
  1127. shapely.geometrycollections(
  1128. [shapely.geometrycollections([empty_point]), point]
  1129. ),
  1130. ],
  1131. )
  1132. def test_to_geojson_point_empty(geom):
  1133. assert geom.equals(shapely.from_geojson(shapely.to_geojson(geom)))
  1134. @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
  1135. @pytest.mark.parametrize("geom", all_types)
  1136. def test_geojson_all_types(geom):
  1137. type_id = shapely.get_type_id(geom)
  1138. if type_id == shapely.GeometryType.LINEARRING:
  1139. pytest.skip("Linearrings are not preserved in GeoJSON")
  1140. elif (
  1141. geom.is_empty
  1142. and type_id == shapely.GeometryType.POINT
  1143. and shapely.geos_version < (3, 10, 2)
  1144. ):
  1145. pytest.skip("GEOS < 3.10.2 with POINT EMPTY") # TRAC-1139
  1146. geojson = shapely.to_geojson(geom)
  1147. actual = shapely.from_geojson(geojson)
  1148. assert not actual.has_z
  1149. geoms_are_empty = shapely.is_empty([geom, actual])
  1150. if geoms_are_empty.any():
  1151. # Ensure both are EMPTY
  1152. assert geoms_are_empty.all()
  1153. else:
  1154. assert_geometries_equal(actual, geom)