import json import pickle import struct import warnings from contextlib import nullcontext import numpy as np import pytest import shapely from shapely import ( GeometryCollection, GEOSException, LinearRing, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, ) from shapely.errors import UnsupportedGEOSVersionError from shapely.testing import assert_geometries_equal from shapely.tests.common import ( all_types, all_types_m, all_types_z, all_types_zm, empty_point, empty_point_m, empty_point_z, empty_point_zm, equal_geometries_abnormally_yield_unequal, multi_point_empty, multi_point_empty_m, multi_point_empty_z, multi_point_empty_zm, point, point_m, point_z, point_zm, polygon_z, ) EWKBZ = 0x80000000 EWKBM = 0x40000000 EWKBZM = EWKBZ | EWKBM ISOWKBZ = 1000 ISOWKBM = 2000 ISOWKBZM = ISOWKBZ + ISOWKBM POINT11_WKB = struct.pack("1 elements"), ("LINESTRING (0 0)", "warn", Warning, "must contain 0 or >1 elements"), ("NOT A WKT STRING", "ignore", None, None), ("NOT A WKT STRING", "warn", Warning, "Unknown type: 'NOT'"), ("POLYGON ((0 0, 0 0))", "ignore", None, None), ("POLYGON ((0 0, 0 0))", "raise", GEOSException, "Invalid number of points"), ("POLYGON ((0 0, 0 0))", "warn", Warning, "Invalid number of points"), ], ) def test_from_wkt_on_invalid(wkt, on_invalid, error, message): if on_invalid == "warn": handler = pytest.warns(error, match=message) elif on_invalid == "raise": handler = pytest.raises(error, match=message) elif on_invalid == "ignore": handler = nullcontext() else: handler = pytest.raises(error, match=message) with handler: result = shapely.from_wkt(wkt, on_invalid=on_invalid) assert result is None @pytest.mark.skipif( shapely.geos_version < (3, 11, 0), reason="on_invalid='fix' not supported with GEOS < 3.11", ) @pytest.mark.parametrize( "wkt, expected_wkt", [ ("", None), ("LINESTRING (0 0)", None), ("NOT A WKT STRING", None), ("POLYGON ((0 0, 0 0))", None), ("POLYGON ((0 0, 1 1, 0 1))", "POLYGON ((0 0, 1 1, 0 1, 0 0))"), ("POLYGON ((0 0, 1 1))", "POLYGON ((0 0, 1 1, 0 0))"), ("MULTIPOLYGON (((5 5, 6 6, 6 5, 5 5)), ((0 0, 0 0)))", None), ( "MULTIPOLYGON (((5 5, 6 6, 6 5, 5 5)), ((0 0, 1 1)))", "MULTIPOLYGON (((5 5, 6 6, 6 5, 5 5)), ((0 0, 1 1, 0 0)))", ), ( "GEOMETRYCOLLECTION (POLYGON ((5 5, 6 6, 6 5, 5 5)), POLYGON ((0 0, 0 0)))", None, ), ], ) def test_from_wkt_on_invalid_fix(wkt, expected_wkt): """Tests for on_invalid="fix". Geometries that cannot be fixed are returned as None. """ geom = shapely.from_wkt(wkt, on_invalid="fix") assert shapely.to_wkt(geom) == expected_wkt @pytest.mark.skipif( shapely.geos_version >= (3, 11, 0), reason="on_invalid='fix' is supported with GEOS >= 3.11", ) def test_from_wkt_on_invalid_fix_unsupported_geos(): """on_invalid="fix" not supported with GEOS < 3.11""" with pytest.raises( ValueError, match="on_invalid='fix' only supported for GEOS >= 3.11" ): _ = shapely.from_wkt("", on_invalid="fix") @pytest.mark.parametrize("geom", all_types) def test_from_wkt_all_types(geom): wkt = shapely.to_wkt(geom) actual = shapely.from_wkt(wkt) if equal_geometries_abnormally_yield_unequal(geom): # check abnormal test with pytest.raises(AssertionError): assert_geometries_equal(actual, geom) else: # normal test assert_geometries_equal(actual, geom) @pytest.mark.parametrize( "wkt", ("POINT EMPTY", "LINESTRING EMPTY", "POLYGON EMPTY", "GEOMETRYCOLLECTION EMPTY"), ) def test_from_wkt_empty(wkt): geom = shapely.from_wkt(wkt) assert shapely.is_geometry(geom).all() assert shapely.is_empty(geom).all() assert shapely.to_wkt(geom) == wkt # WKT from https://github.com/libgeos/geos/blob/main/tests/unit/io/WKBReaderTest.cpp @pytest.mark.parametrize( "wkt", ( "CIRCULARSTRING(1 3,2 4,3 1)", "COMPOUNDCURVE(CIRCULARSTRING(1 3,2 4,3 1),(3 1,0 0))", "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 "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 "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 ), ) def test_from_wkt_nonlinear_unsupported(wkt): if shapely.geos_version >= (3, 13, 0): with pytest.raises( NotImplementedError, match="Nonlinear geometry types are not currently supported", ): shapely.from_wkt(wkt) else: # prior to GEOS 3.13 nonlinear types were rejected by GEOS on read from WKT with pytest.raises(shapely.errors.GEOSException, match="Unknown type"): shapely.from_wkt(wkt) def test_from_wkb(): expected = shapely.points(1, 1) actual = shapely.from_wkb(POINT11_WKB) assert_geometries_equal(actual, expected) def test_from_wkb_hex(): # HEX form expected = shapely.points(1, 1) actual = shapely.from_wkb("0101000000000000000000F03F000000000000F03F") assert_geometries_equal(actual, expected) actual = shapely.from_wkb(b"0101000000000000000000F03F000000000000F03F") assert_geometries_equal(actual, expected) def test_from_wkb_none(): # None propagates assert shapely.from_wkb(None) is None @pytest.mark.parametrize( "wkb, on_invalid, error, message", [ (1, "raise", TypeError, "Expected bytes or string, got int"), ("", "ignore", None, None), ("", "raise", GEOSException, "Unexpected EOF parsing WKB"), ("", "warn", Warning, "Unexpected EOF parsing WKB"), ("", "unsupported_option", ValueError, "not a valid option"), (b"\x01\x01\x00\x00\x00\x00", "ignore", None, None), (b"\x01\x01\x00\x00\x00\x00", "raise", GEOSException, "ParseException"), (b"\x01\x01\x00\x00\x00\x00", "warn", Warning, "ParseException"), (INVALID_WKB, "ignore", None, None), ( INVALID_WKB, "raise", GEOSException, "Points of LinearRing do not form a closed linestring", ), ( INVALID_WKB, "warn", Warning, "Points of LinearRing do not form a closed linestring", ), ], ) def test_from_wkb_on_invalid(wkb, on_invalid, error, message): if on_invalid == "warn": handler = pytest.warns(error, match=message) elif on_invalid == "raise": handler = pytest.raises(error, match=message) elif on_invalid == "ignore": handler = nullcontext() else: handler = pytest.raises(error, match=message) with handler: result = shapely.from_wkb(wkb, on_invalid=on_invalid) assert result is None @pytest.mark.skipif( shapely.geos_version < (3, 11, 0), reason="on_invalid='fix' not supported with GEOS < 3.11", ) @pytest.mark.parametrize( "wkb, expected_wkt", [ (b"", None), (b"\x01\x01\x00\x00\x00\x00", None), ( INVALID_WKB, "POLYGON ((1421568.7761 1924750.2852, 1421564.1314 1924752.2408, 1421568.7761 1924750.2852))", # noqa: E501 ), ], ) def test_from_wkb_on_invalid_fix(wkb, expected_wkt): """Tests for on_invalid="fix". Geometries that cannot be fixed are returned as None. """ geom = shapely.from_wkb(wkb, on_invalid="fix") assert shapely.to_wkt(geom) == expected_wkt @pytest.mark.skipif( shapely.geos_version >= (3, 11, 0), reason="on_invalid='fix' is supported with GEOS >= 3.11", ) def test_from_wkb_on_invalid_fix_unsupported_geos(): """on_invalid="fix" not supported with GEOS < 3.11""" with pytest.raises( ValueError, match="on_invalid='fix' only supported for GEOS >= 3.11" ): _ = shapely.from_wkb(b"", on_invalid="fix") @pytest.mark.parametrize("geom", all_types) @pytest.mark.parametrize("use_hex", [False, True]) @pytest.mark.parametrize("byte_order", [0, 1]) def test_from_wkb_all_types(geom, use_hex, byte_order): if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING: pytest.skip("Linearrings are not preserved in WKB") wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order) actual = shapely.from_wkb(wkb) assert_geometries_equal(actual, geom) @pytest.mark.parametrize("geom", all_types_z) @pytest.mark.parametrize("use_hex", [False, True]) @pytest.mark.parametrize("byte_order", [0, 1]) def test_from_wkb_all_types_z(geom, use_hex, byte_order): if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING: pytest.skip("Linearrings are not preserved in WKB") wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order) actual = shapely.from_wkb(wkb) assert_geometries_equal(actual, geom) @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) @pytest.mark.parametrize("geom", all_types_m) @pytest.mark.parametrize("use_hex", [False, True]) @pytest.mark.parametrize("byte_order", [0, 1]) def test_from_wkb_all_types_m(geom, use_hex, byte_order): if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING: pytest.skip("Linearrings are not preserved in WKB") wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order) actual = shapely.from_wkb(wkb) assert_geometries_equal(actual, geom) @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) @pytest.mark.parametrize("geom", all_types_zm) @pytest.mark.parametrize("use_hex", [False, True]) @pytest.mark.parametrize("byte_order", [0, 1]) def test_from_wkb_all_types_zm(geom, use_hex, byte_order): if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING: pytest.skip("Linearrings are not preserved in WKB") wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order) actual = shapely.from_wkb(wkb) assert_geometries_equal(actual, geom) @pytest.mark.parametrize( "geom", (Point(), LineString(), Polygon(), GeometryCollection()), ) def test_from_wkb_empty(geom): wkb = shapely.to_wkb(geom) geom = shapely.from_wkb(wkb) assert shapely.is_geometry(geom).all() assert shapely.is_empty(geom).all() assert shapely.to_wkb(geom) == wkb # WKB from https://github.com/libgeos/geos/blob/main/tests/unit/io/WKBReaderTest.cpp @pytest.mark.parametrize( "wkb", ( # "CIRCULARSTRING(1 3,2 4,3 1)", "010800000003000000000000000000F03F0000000000000840000000000000004000000000000010400000000000000840000000000000F03F", # "COMPOUNDCURVE(CIRCULARSTRING(1 3,2 4,3 1),(3 1,0 0))", "01090000200E16000002000000010800000003000000000000000000F03F0000000000000840000000000000004000000000000010400000000000000840000000000000F03F0102000000020000000000000000000840000000000000F03F00000000000000000000000000000000", # "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 "010A0000200E1600000200000001090000000200000001080000000500000000000000000000000000000000000000000000000000004000000000000000000000000000000040000000000000F03F00000000000000400000000000000840000000000000104000000000000008400102000000040000000000000000001040000000000000084000000000000010400000000000001440000000000000F03F000000000000104000000000000000000000000000000000010800000005000000333333333333FB3F000000000000F03F666666666666F63F9A9999999999D93F9A9999999999F93F9A9999999999D93F9A9999999999F93F000000000000E03F333333333333FB3F000000000000F03F", # "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 "010B000000030000000102000000020000000000000000000000000000000000000000000000000014400000000000001440010900000002000000010200000002000000000000000000F0BF000000000000F0BF0000000000000000000000000000000001080000000300000000000000000000000000000000000000000000000000F03F000000000000F03F00000000000000400000000000000000010800000003000000000000000000104000000000000000000000000000001040000000000000104000000000000020400000000000001040", # "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 "010C00000002000000010A000000020000000108000000050000000000000000000000000000000000000000000000000010400000000000000000000000000000104000000000000010400000000000000000000000000000104000000000000000000000000000000000010200000004000000000000000000F03F000000000000F03F000000000000084000000000000008400000000000000840000000000000F03F000000000000F03F000000000000F03F01030000000200000004000000000000000000244000000000000024400000000000002C40000000000000284000000000000026400000000000002440000000000000244000000000000024400400000000000000000026400000000000002640000000000000274000000000000026400000000000002640000000000000274000000000000026400000000000002640", ), ) def test_from_wkb_nonlinear_unsupported(wkb): if shapely.geos_version >= (3, 13, 0): with pytest.raises( NotImplementedError, match="Nonlinear geometry types are not currently supported", ): shapely.from_wkb(wkb) else: # prior to GEOS 3.13 nonlinear types were rejected by GEOS on read from WKB with pytest.raises(shapely.errors.GEOSException, match="Unknown WKB type"): shapely.from_wkb(wkb) def test_to_wkt(): point = shapely.points(1, 1) actual = shapely.to_wkt(point) assert actual == "POINT (1 1)" actual = shapely.to_wkt(point, trim=False) assert actual == "POINT (1.000000 1.000000)" actual = shapely.to_wkt(point, rounding_precision=3, trim=False) assert actual == "POINT (1.000 1.000)" def test_to_wkt_z(): point = shapely.points(1, 2, 3) assert shapely.to_wkt(point) == "POINT Z (1 2 3)" assert shapely.to_wkt(point, output_dimension=2) == "POINT (1 2)" assert shapely.to_wkt(point, output_dimension=3) == "POINT Z (1 2 3)" assert shapely.to_wkt(point, old_3d=True) == "POINT (1 2 3)" if shapely.geos_version >= (3, 12, 0): assert shapely.to_wkt(point, output_dimension=4) == "POINT Z (1 2 3)" def test_to_wkt_m(): point = shapely.from_wkt("POINT M (1 2 4)") assert shapely.to_wkt(point, output_dimension=2) == "POINT (1 2)" if shapely.geos_version < (3, 12, 0): # previous behavior was to incorrectly parse M as Z assert shapely.to_wkt(point) == "POINT Z (1 2 4)" assert shapely.to_wkt(point, output_dimension=3) == "POINT Z (1 2 4)" assert shapely.to_wkt(point, old_3d=True) == "POINT (1 2 4)" else: assert shapely.to_wkt(point) == "POINT M (1 2 4)" assert shapely.to_wkt(point, output_dimension=3) == "POINT M (1 2 4)" assert shapely.to_wkt(point, output_dimension=4) == "POINT M (1 2 4)" assert shapely.to_wkt(point, old_3d=True) == "POINT M (1 2 4)" def test_to_wkt_zm(): point = shapely.from_wkt("POINT ZM (1 2 3 4)") assert shapely.to_wkt(point, output_dimension=2) == "POINT (1 2)" assert shapely.to_wkt(point, output_dimension=3) == "POINT Z (1 2 3)" if shapely.geos_version < (3, 12, 0): # previous behavior was to parse and ignore M assert shapely.to_wkt(point) == "POINT Z (1 2 3)" assert shapely.to_wkt(point, old_3d=True) == "POINT (1 2 3)" else: assert shapely.to_wkt(point) == "POINT ZM (1 2 3 4)" assert shapely.to_wkt(point, output_dimension=4) == "POINT ZM (1 2 3 4)" assert shapely.to_wkt(point, old_3d=True) == "POINT (1 2 3 4)" def test_to_wkt_none(): # None propagates assert shapely.to_wkt(None) is None def test_to_wkt_array_with_empty_z(): # See GH-2004 empty_wkt = ["POINT Z EMPTY", None, "POLYGON Z EMPTY"] empty_geoms = shapely.from_wkt(empty_wkt) assert list(shapely.to_wkt(empty_geoms)) == empty_wkt def test_to_wkt_exceptions(): with pytest.raises(TypeError): shapely.to_wkt(1) with pytest.raises(shapely.GEOSException): shapely.to_wkt(point, output_dimension=5) def test_to_wkt_point_empty(): assert shapely.to_wkt(empty_point) == "POINT EMPTY" @pytest.mark.parametrize( "wkt", [ "POINT Z EMPTY", "LINESTRING Z EMPTY", "LINEARRING Z EMPTY", "POLYGON Z EMPTY", ], ) def test_to_wkt_empty_z(wkt): assert shapely.to_wkt(shapely.from_wkt(wkt)) == wkt def test_to_wkt_geometrycollection_with_point_empty(): collection = shapely.geometrycollections([empty_point, point]) # do not check the full value as some GEOS versions give # GEOMETRYCOLLECTION Z (...) and others give GEOMETRYCOLLECTION (...) assert shapely.to_wkt(collection).endswith("(POINT EMPTY, POINT (2 3))") def test_to_wkt_multipoint_with_point_empty(): geom = shapely.multipoints([empty_point, point]) if shapely.geos_version >= (3, 12, 0): expected = "MULTIPOINT (EMPTY, (2 3))" else: # invalid WKT form expected = "MULTIPOINT (EMPTY, 2 3)" assert shapely.to_wkt(geom) == expected @pytest.mark.parametrize("geom", [Point(1e100, 0), Point(0, 1e100)]) def test_to_wkt_large_float_ok(geom): # https://github.com/shapely/shapely/issues/1903 shapely.to_wkt(geom) assert "Exception in WKT writer" not in repr(geom) @pytest.mark.parametrize("geom", [Point(1e101, 0), Point(0, 1e101)]) def test_to_wkt_large_float(geom): if shapely.geos_version >= (3, 13, 0): # round-trip WKT assert geom.equals(shapely.from_wkt(shapely.to_wkt(geom))) else: # https://github.com/shapely/shapely/issues/1903 with pytest.raises( ValueError, match="WKT output of coordinates greater than.*" ): shapely.to_wkt(geom) assert "Exception in WKT writer" in repr(geom) @pytest.mark.parametrize( "geom", [ # We implemented our own "GetZMax", so go through all geometry types: Point(0, 0, 1e101), LineString([(0, 0, 0), (0, 0, 1e101)]), LinearRing([(0, 0, 0), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)]), Polygon([(0, 0, 0), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)]), Polygon( [(0, 0, 0), (0, 10, 0), (10, 0, 0), (0, 0, 0)], [[(0, 0, 0), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)]], ), MultiPoint([(0, 0, 0), (0, 0, 1e101)]), MultiLineString( [LineString([(0, 0, 0), (0, 1, 0)]), LineString([(0, 0, 0), (0, 1, 1e101)])] ), MultiPolygon( [polygon_z, Polygon([(0, 0, 0), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)])] ), GeometryCollection([point_z, Point(0, 0, 1e101)]), GeometryCollection([GeometryCollection([Point(0, 0, 1e101)])]), LineString([(0, 0, np.nan), (0, 0, 1e101)]), Polygon([(0, 0, np.nan), (0, 1, 0), (1, 0, 1e101), (0, 0, 0)]), GeometryCollection([Point(0, 0), Point(0, 0, 1e101)]), ], ) def test_to_wkt_large_float_3d_no_crash(geom): # https://github.com/shapely/shapely/issues/1903 # just test if there is a crash (detailed behaviour differs per GEOS version) try: shapely.to_wkt(geom) except ValueError as e: assert str(e).startswith("WKT output of coordinates greater than") repr(geom) def test_to_wkt_large_float_skip_z(): # https://github.com/shapely/shapely/issues/1903 assert shapely.to_wkt(Point(0, 0, 1e101), output_dimension=2) == "POINT (0 0)" def test_to_wkt_large_float_no_trim(): # https://github.com/shapely/shapely/issues/1903 # don't test the exact number, it is ridiculously large and probably platform # dependent assert shapely.to_wkt(Point(1e101, 0), trim=False).startswith("POINT (") def test_repr(): assert repr(point) == "" assert repr(point_z) == "" @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) def test_repr_m(): assert repr(point_m) == "" assert repr(point_zm) == "" def test_repr_max_length(): # the repr is limited to 80 characters geom = shapely.linestrings(np.arange(1000), np.arange(1000)) representation = repr(geom) assert len(representation) == 80 assert representation.endswith("...>") def test_repr_point_z_empty(): assert repr(empty_point_z) == "" @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) def test_repr_point_m_empty(): assert repr(empty_point_m) == "" assert repr(empty_point_zm) == "" def test_to_wkb(): point = shapely.points(1, 1) actual = shapely.to_wkb(point, byte_order=1) assert actual == POINT11_WKB def test_to_wkb_hex(): point = shapely.points(1, 1) actual = shapely.to_wkb(point, hex=True, byte_order=1) le = "01" point_type = "01000000" coord = "000000000000F03F" # 1.0 as double (LE) assert actual == le + point_type + 2 * coord def test_to_wkb_z(): point = shapely.points(1, 2, 3) expected_wkb = struct.pack("= (3, 12, 0): assert shapely.to_wkb(point, output_dimension=4, byte_order=1) == expected_wkb_z def test_to_wkb_m(): # POINT M (1 2 4) point = shapely.from_wkb(struct.pack("= (3, 12, 0): assert shapely.to_wkb(point, output_dimension=4, byte_order=1) == expected_wkb_m def test_to_wkb_zm(): # POINT ZM (1 2 3 4) point = shapely.from_wkb(struct.pack("= (3, 12, 0): assert ( shapely.to_wkb(point, output_dimension=4, byte_order=1) == expected_wkb_zm ) def test_to_wkb_none(): # None propagates assert shapely.to_wkb(None) is None def test_to_wkb_exceptions(): with pytest.raises(TypeError): shapely.to_wkb(1) with pytest.raises(shapely.GEOSException): shapely.to_wkb(point, output_dimension=5) with pytest.raises(ValueError): shapely.to_wkb(point, flavor="other") def test_to_wkb_byte_order(): point = shapely.points(1.0, 1.0) be = b"\x00" le = b"\x01" point_type = b"\x01\x00\x00\x00" # 1 as 32-bit uint (LE) coord = b"\x00\x00\x00\x00\x00\x00\xf0?" # 1.0 as double (LE) assert shapely.to_wkb(point, byte_order=1) == le + point_type + 2 * coord assert ( shapely.to_wkb(point, byte_order=0) == be + point_type[::-1] + 2 * coord[::-1] ) def test_to_wkb_srid(): # hex representation of POINT (0 0) with SRID=4 ewkb = "01010000200400000000000000000000000000000000000000" wkb = "010100000000000000000000000000000000000000" actual = shapely.from_wkb(ewkb) assert shapely.to_wkt(actual, trim=True) == "POINT (0 0)" assert shapely.to_wkb(actual, hex=True, byte_order=1) == wkb assert shapely.to_wkb(actual, hex=True, include_srid=True, byte_order=1) == ewkb point = shapely.points(1, 1) point_with_srid = shapely.set_srid(point, np.int32(4326)) result = shapely.to_wkb(point_with_srid, include_srid=True, byte_order=1) assert np.frombuffer(result[5:9], "= (3, 10, 0), reason="GEOS < 3.10.0") def test_to_wkb_flavor_unsupported_geos(): with pytest.raises(UnsupportedGEOSVersionError): shapely.to_wkb(point_z, flavor="iso") @pytest.mark.parametrize( "geom,expected", [ pytest.param(empty_point, POINT_NAN_WKB, id="POINT EMPTY"), pytest.param(empty_point_z, POINT_NAN_WKB, id="POINT Z EMPTY"), pytest.param(empty_point_m, POINT_NAN_WKB, id="POINT M EMPTY"), pytest.param(empty_point_zm, POINT_NAN_WKB, id="POINT ZM EMPTY"), pytest.param( multi_point_empty, MULTIPOINT_NAN_WKB, id="MULTIPOINT EMPTY", ), pytest.param( multi_point_empty_z, MULTIPOINT_NAN_WKB, id="MULTIPOINT Z EMPTY", ), pytest.param( multi_point_empty_m, MULTIPOINT_NAN_WKB, id="MULTIPOINT M EMPTY", ), pytest.param( multi_point_empty_zm, MULTIPOINT_NAN_WKB, id="MULTIPOINT ZM EMPTY", ), pytest.param( shapely.geometrycollections([empty_point]), GEOMETRYCOLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (POINT EMPTY)", ), pytest.param( shapely.geometrycollections([empty_point_z]), GEOMETRYCOLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (POINT Z EMPTY)", ), pytest.param( shapely.geometrycollections([empty_point_m]), GEOMETRYCOLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (POINT M EMPTY)", ), pytest.param( shapely.geometrycollections([empty_point_zm]), GEOMETRYCOLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (POINT ZM EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty]), NESTED_COLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty_z]), NESTED_COLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT Z EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty_m]), NESTED_COLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT M EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty_zm]), NESTED_COLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT ZM EMPTY)", ), ], ) def test_to_wkb_point_empty_2d(geom, expected): actual = shapely.to_wkb(geom, output_dimension=2, byte_order=1) # Split 'actual' into header and coordinates coordinate_length = 16 header_length = len(expected) - coordinate_length # Check the total length (this checks the correct dimensionality) assert len(actual) == header_length + coordinate_length # Check the header assert actual[:header_length] == expected[:header_length] # Check the coordinates (using numpy.isnan; there are many byte representations for # NaN) assert np.isnan(struct.unpack("<2d", actual[header_length:])).all() @pytest.mark.parametrize( "geom,expected", [ pytest.param(empty_point_z, POINTZ_NAN_WKB, id="POINT Z EMPTY"), pytest.param(empty_point_zm, POINTZ_NAN_WKB, id="POINT ZM EMPTY"), pytest.param( multi_point_empty_z, MULTIPOINTZ_NAN_WKB, id="MULTIPOINT Z EMPTY", ), pytest.param( multi_point_empty_zm, MULTIPOINTZ_NAN_WKB, id="MULTIPOINT ZM EMPTY", ), pytest.param( shapely.geometrycollections([empty_point_z]), GEOMETRYCOLLECTIONZ_NAN_WKB, id="GEOMETRYCOLLECTION (POINT Z EMPTY)", ), pytest.param( shapely.geometrycollections([empty_point_zm]), GEOMETRYCOLLECTIONZ_NAN_WKB, id="GEOMETRYCOLLECTION (POINT ZM EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty_z]), NESTED_COLLECTIONZ_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT Z EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty_zm]), NESTED_COLLECTIONZ_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT ZM EMPTY)", ), ], ) def test_to_wkb_point_empty_z(geom, expected): actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1) # Split 'actual' into header and coordinates coordinate_length = 8 * 3 header_length = len(expected) - coordinate_length # Check the total length (this checks the correct dimensionality) assert len(actual) == header_length + coordinate_length # Check the header assert actual[:header_length] == expected[:header_length] # Check the coordinates (using numpy.isnan; there are many byte representations for # NaN) assert np.isnan(struct.unpack("<3d", actual[header_length:])).all() @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) @pytest.mark.parametrize( "geom,expected", [ pytest.param(empty_point_m, POINTM_NAN_WKB, id="POINT M EMPTY"), pytest.param( multi_point_empty_m, MULTIPOINTM_NAN_WKB, id="MULTIPOINT M EMPTY", ), pytest.param( shapely.geometrycollections([empty_point_m]), GEOMETRYCOLLECTIONM_NAN_WKB, id="GEOMETRYCOLLECTION (POINT M EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty_m]), NESTED_COLLECTIONM_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT M EMPTY)", ), ], ) def test_to_wkb_point_empty_m(geom, expected): actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1) # Split 'actual' into header and coordinates coordinate_length = 8 * 3 header_length = len(expected) - coordinate_length assert len(actual) == header_length + coordinate_length assert actual[:header_length] == expected[:header_length] assert np.isnan(struct.unpack("<3d", actual[header_length:])).all() @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) @pytest.mark.parametrize( "geom,expected", [ pytest.param(empty_point_zm, POINTZM_NAN_WKB, id="POINT ZM EMPTY"), pytest.param( multi_point_empty_zm, MULTIPOINTZM_NAN_WKB, id="MULTIPOINT ZM EMPTY", ), pytest.param( shapely.geometrycollections([empty_point_zm]), GEOMETRYCOLLECTIONZM_NAN_WKB, id="GEOMETRYCOLLECTION (POINT ZM EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty_zm]), NESTED_COLLECTIONZM_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT ZM EMPTY)", ), ], ) def test_to_wkb_point_empty_zm(geom, expected): actual = shapely.to_wkb(geom, output_dimension=4, byte_order=1) # Split 'actual' into header and coordinates coordinate_length = 8 * 4 header_length = len(expected) - coordinate_length assert len(actual) == header_length + coordinate_length assert actual[:header_length] == expected[:header_length] assert np.isnan(struct.unpack("<4d", actual[header_length:])).all() @pytest.mark.parametrize( "geom,expected", [ pytest.param(empty_point, POINT_NAN_WKB, id="POINT EMPTY"), pytest.param(multi_point_empty, MULTIPOINT_NAN_WKB, id="MULTIPOINT EMPTY"), pytest.param( shapely.geometrycollections([empty_point]), GEOMETRYCOLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (POINT EMPTY)", ), pytest.param( shapely.geometrycollections([multi_point_empty]), NESTED_COLLECTION_NAN_WKB, id="GEOMETRYCOLLECTION (MULTIPOINT EMPTY)", ), ], ) def test_to_wkb_point_empty_2d_output_dim_3(geom, expected): actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1) # Split 'actual' into header and coordinates coordinate_length = 16 header_length = len(expected) - coordinate_length # Check the total length (this checks the correct dimensionality) assert len(actual) == header_length + coordinate_length # Check the header assert actual[:header_length] == expected[:header_length] # Check the coordinates (using numpy.isnan; there are many byte representations for # NaN) assert np.isnan(struct.unpack("<2d", actual[header_length:])).all() @pytest.mark.parametrize( "wkb,expected_type,expected_dim", [ pytest.param(POINT_NAN_WKB, 0, 2, id="POINT_NAN_WKB"), pytest.param(POINTZ_NAN_WKB, 0, 3, id="POINTZ_NAN_WKB"), pytest.param(MULTIPOINT_NAN_WKB, 4, 2, id="MULTIPOINT_NAN_WKB"), pytest.param(MULTIPOINTZ_NAN_WKB, 4, 3, id="MULTIPOINTZ_NAN_WKB"), pytest.param(GEOMETRYCOLLECTION_NAN_WKB, 7, 2, id="GEOMETRYCOLLECTION_NAN_WKB"), pytest.param( GEOMETRYCOLLECTIONZ_NAN_WKB, 7, 3, id="GEOMETRYCOLLECTIONZ_NAN_WKB" ), pytest.param(NESTED_COLLECTION_NAN_WKB, 7, 2, id="NESTED_COLLECTION_NAN_WKB"), pytest.param(NESTED_COLLECTIONZ_NAN_WKB, 7, 3, id="NESTED_COLLECTIONZ_NAN_WKB"), ], ) def test_from_wkb_point_empty(wkb, expected_type, expected_dim): geom = shapely.from_wkb(wkb) # POINT (nan nan) transforms to an empty point assert shapely.is_empty(geom) assert shapely.get_type_id(geom) == expected_type assert shapely.get_coordinate_dimension(geom) == expected_dim @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) @pytest.mark.parametrize( "wkb,expected_type", [ pytest.param(POINTM_NAN_WKB, 0, id="POINTM_NAN_WKB"), pytest.param(MULTIPOINTM_NAN_WKB, 4, id="MULTIPOINTM_NAN_WKB"), pytest.param(GEOMETRYCOLLECTIONM_NAN_WKB, 7, id="GEOMETRYCOLLECTIONM_NAN_WKB"), pytest.param(NESTED_COLLECTIONM_NAN_WKB, 7, id="NESTED_COLLECTIONM_NAN_WKB"), ], ) def test_from_wkb_point_empty_m(wkb, expected_type): geom = shapely.from_wkb(wkb) assert shapely.is_empty(geom) assert shapely.get_type_id(geom) == expected_type assert shapely.get_coordinate_dimension(geom) == 3 assert not shapely.has_z(geom) assert shapely.has_m(geom) @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) @pytest.mark.parametrize( "wkb,expected_type", [ pytest.param(POINTZM_NAN_WKB, 0, id="POINTZM_NAN_WKB"), pytest.param(MULTIPOINTZM_NAN_WKB, 4, id="MULTIPOINTZM_NAN_WKB"), pytest.param( GEOMETRYCOLLECTIONZM_NAN_WKB, 7, id="GEOMETRYCOLLECTIONZM_NAN_WKB" ), pytest.param(NESTED_COLLECTIONZM_NAN_WKB, 7, id="NESTED_COLLECTIONZM_NAN_WKB"), ], ) def test_from_wkb_point_empty_zm(wkb, expected_type): geom = shapely.from_wkb(wkb) assert shapely.is_empty(geom) assert shapely.get_type_id(geom) == expected_type assert shapely.get_coordinate_dimension(geom) == 4 assert shapely.has_z(geom) assert shapely.has_m(geom) def test_to_wkb_point_empty_srid(): expected = shapely.set_srid(empty_point, 4236) wkb = shapely.to_wkb(expected, include_srid=True) actual = shapely.from_wkb(wkb) assert shapely.get_srid(actual) == 4236 @pytest.mark.parametrize("geom", all_types + (point_z, empty_point)) def test_pickle(geom): pickled = pickle.dumps(geom) assert_geometries_equal(pickle.loads(pickled), geom, tolerance=0) @pytest.mark.parametrize("geom", all_types_z) def test_pickle_z(geom): pickled = pickle.dumps(geom) actual = pickle.loads(pickled) assert_geometries_equal(actual, geom, tolerance=0) if not actual.is_empty: # GEOSHasZ with EMPTY geometries is inconsistent assert actual.has_z if shapely.geos_version >= (3, 12, 0): assert not actual.has_m @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) @pytest.mark.parametrize("geom", all_types_m) def test_pickle_m(geom): pickled = pickle.dumps(geom) actual = pickle.loads(pickled) assert_geometries_equal(actual, geom, tolerance=0) assert not actual.has_z if not actual.is_empty: # GEOSHasM with EMPTY geometries is inconsistent assert actual.has_m @pytest.mark.skipif( shapely.geos_version < (3, 12, 0), reason="M coordinates not supported with GEOS < 3.12", ) @pytest.mark.parametrize("geom", all_types_zm) def test_pickle_zm(geom): pickled = pickle.dumps(geom) actual = pickle.loads(pickled) assert_geometries_equal(actual, geom, tolerance=0) if not actual.is_empty: # GEOSHasZ with EMPTY geometries is inconsistent assert actual.has_z assert actual.has_m @pytest.mark.parametrize("geom", all_types + (point_z, empty_point)) def test_pickle_with_srid(geom): geom = shapely.set_srid(geom, 4326) pickled = pickle.dumps(geom) assert shapely.get_srid(pickle.loads(pickled)) == 4326 @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1") @pytest.mark.parametrize( "geojson,expected", [ pytest.param( GEOJSON_GEOMETRY, GEOJSON_GEOMETRY_EXPECTED, id="GEOJSON_GEOMETRY" ), pytest.param(GEOJSON_FEATURE, GEOJSON_GEOMETRY_EXPECTED, id="GEOJSON_FEATURE"), pytest.param( GEOJSON_FEATURECOLECTION, shapely.geometrycollections(GEOJSON_COLLECTION_EXPECTED), id="GEOJSON_FEATURECOLECTION", ), pytest.param( [GEOJSON_GEOMETRY] * 2, [GEOJSON_GEOMETRY_EXPECTED] * 2, id="GEOJSON_GEOMETRYx2", ), pytest.param(None, None, id="None"), pytest.param( [GEOJSON_GEOMETRY, None], [GEOJSON_GEOMETRY_EXPECTED, None], id="GEOJSON_GEOMETRY_None", ), ], ) def test_from_geojson(geojson, expected): actual = shapely.from_geojson(geojson) assert_geometries_equal(actual, expected) @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1") def test_from_geojson_exceptions(): with pytest.raises(TypeError, match="Expected bytes or string, got int"): shapely.from_geojson(1) with pytest.raises(shapely.GEOSException, match="Error parsing JSON"): shapely.from_geojson("") with pytest.raises(shapely.GEOSException, match="Unknown geometry type"): shapely.from_geojson('{"type": "NoGeometry", "coordinates": []}') with pytest.raises(shapely.GEOSException, match="type must be array, but is null"): shapely.from_geojson('{"type": "LineString", "coordinates": null}') # Note: The two below tests are the reason that from_geojson is disabled for # GEOS 3.10.0 See https://trac.osgeo.org/geos/ticket/1138 with pytest.raises(shapely.GEOSException, match="key 'type' not found"): shapely.from_geojson('{"geometry": null, "properties": []}') with pytest.raises(shapely.GEOSException, match="key 'type' not found"): shapely.from_geojson('{"no": "geojson"}') @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1") def test_from_geojson_warn_on_invalid(): with pytest.warns(Warning, match="Invalid GeoJSON"): assert shapely.from_geojson("", on_invalid="warn") is None @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1") def test_from_geojson_ignore_on_invalid(): with warnings.catch_warnings(): warnings.simplefilter("error") assert shapely.from_geojson("", on_invalid="ignore") is None @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1") def test_from_geojson_on_invalid_unsupported_option(): with pytest.raises(ValueError, match="not a valid option"): shapely.from_geojson(GEOJSON_GEOMETRY, on_invalid="unsupported_option") @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize( "expected,geometry", [ pytest.param( GEOJSON_GEOMETRY, GEOJSON_GEOMETRY_EXPECTED, id="GEOJSON_GEOMETRY" ), pytest.param( [GEOJSON_GEOMETRY] * 2, [GEOJSON_GEOMETRY_EXPECTED] * 2, id="GEOJSON_GEOMETRYx2", ), pytest.param(None, None, id="None"), pytest.param( [GEOJSON_GEOMETRY, None], [GEOJSON_GEOMETRY_EXPECTED, None], id="GEOJSON_GEOMETRY_None", ), ], ) def test_to_geojson(geometry, expected): actual = shapely.to_geojson(geometry, indent=4) assert np.all(actual == np.asarray(expected)) @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize("indent", [None, 0, 4]) def test_to_geojson_indent(indent): separators = (",", ":") if indent is None else (",", ": ") expected = json.dumps( json.loads(GEOJSON_GEOMETRY), indent=indent, separators=separators ) actual = shapely.to_geojson(GEOJSON_GEOMETRY_EXPECTED, indent=indent) assert actual == expected @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") def test_to_geojson_exceptions(): with pytest.raises(TypeError): shapely.to_geojson(1) @pytest.mark.skipif(shapely.geos_version < (3, 10, 2), reason="GEOS < 3.10.2") @pytest.mark.parametrize( "geom", [ empty_point, shapely.multipoints([empty_point, point]), shapely.geometrycollections([empty_point, point]), shapely.geometrycollections( [shapely.geometrycollections([empty_point]), point] ), ], ) def test_to_geojson_point_empty(geom): assert geom.equals(shapely.from_geojson(shapely.to_geojson(geom))) @pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1") @pytest.mark.parametrize("geom", all_types) def test_geojson_all_types(geom): type_id = shapely.get_type_id(geom) if type_id == shapely.GeometryType.LINEARRING: pytest.skip("Linearrings are not preserved in GeoJSON") elif ( geom.is_empty and type_id == shapely.GeometryType.POINT and shapely.geos_version < (3, 10, 2) ): pytest.skip("GEOS < 3.10.2 with POINT EMPTY") # TRAC-1139 geojson = shapely.to_geojson(geom) actual = shapely.from_geojson(geojson) assert not actual.has_z geoms_are_empty = shapely.is_empty([geom, actual]) if geoms_are_empty.any(): # Ensure both are EMPTY assert geoms_are_empty.all() else: assert_geometries_equal(actual, geom)