import json import pickle import struct import warnings import numpy as np import pytest import shapely from shapely import GeometryCollection, LineString, Point, Polygon from shapely.errors import UnsupportedGEOSVersionError from shapely.testing import assert_geometries_equal from .common import all_types, empty_point, empty_point_z, point, point_z # fmt: off POINT11_WKB = b"\x01\x01\x00\x00\x00" + struct.pack("<2d", 1.0, 1.0) NAN = struct.pack("= (3, 9, 0), reason="MULTIPOINT (EMPTY, 2 3) gives ValueError on GEOS < 3.9", ) def test_to_wkt_multipoint_with_point_empty_errors(): # test if segfault is prevented geom = shapely.multipoints([empty_point, point]) with pytest.raises(ValueError): shapely.to_wkt(geom) def test_repr(): assert repr(point) == "" 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("...>") @pytest.mark.skipif( shapely.geos_version >= (3, 9, 0), reason="MULTIPOINT (EMPTY, 2 3) gives Exception on GEOS < 3.9", ) def test_repr_multipoint_with_point_empty(): # Test if segfault is prevented geom = shapely.multipoints([point, empty_point]) assert repr(geom) == "" @pytest.mark.skipif( shapely.geos_version < (3, 9, 0), reason="Empty geometries have no dimensionality on GEOS < 3.9", ) def test_repr_point_z_empty(): assert repr(empty_point_z) == "" 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_3D(): point_z = shapely.points(1, 1, 1) actual = shapely.to_wkb(point_z, byte_order=1) # fmt: off assert actual == b"\x01\x01\x00\x00\x80\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?" # noqa # fmt: on actual = shapely.to_wkb(point_z, output_dimension=2, byte_order=1) assert actual == POINT11_WKB 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", [ (empty_point, POINT_NAN_WKB), (empty_point_z, POINT_NAN_WKB), (shapely.multipoints([empty_point]), MULTIPOINT_NAN_WKB), (shapely.multipoints([empty_point_z]), MULTIPOINT_NAN_WKB), (shapely.geometrycollections([empty_point]), GEOMETRYCOLLECTION_NAN_WKB), (shapely.geometrycollections([empty_point_z]), GEOMETRYCOLLECTION_NAN_WKB), ( shapely.geometrycollections([shapely.multipoints([empty_point])]), NESTED_COLLECTION_NAN_WKB, ), ( shapely.geometrycollections([shapely.multipoints([empty_point_z])]), NESTED_COLLECTION_NAN_WKB, ), ], ) 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.xfail( shapely.geos_version[:2] == (3, 8), reason="GEOS==3.8 never outputs 3D empty points" ) @pytest.mark.parametrize( "geom,expected", [ (empty_point_z, POINTZ_NAN_WKB), (shapely.multipoints([empty_point_z]), MULTIPOINTZ_NAN_WKB), (shapely.geometrycollections([empty_point_z]), GEOMETRYCOLLECTIONZ_NAN_WKB), ( shapely.geometrycollections([shapely.multipoints([empty_point_z])]), NESTED_COLLECTIONZ_NAN_WKB, ), ], ) def test_to_wkb_point_empty_3d(geom, expected): actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1) # Split 'actual' into header and coordinates coordinate_length = 24 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.xfail( shapely.geos_version < (3, 8, 0), reason="GEOS<3.8 always outputs 3D empty points if output_dimension=3", ) @pytest.mark.parametrize( "geom,expected", [ (empty_point, POINT_NAN_WKB), (shapely.multipoints([empty_point]), MULTIPOINT_NAN_WKB), (shapely.geometrycollections([empty_point]), GEOMETRYCOLLECTION_NAN_WKB), ( shapely.geometrycollections([shapely.multipoints([empty_point])]), NESTED_COLLECTION_NAN_WKB, ), ], ) 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", [ (POINT_NAN_WKB, 0, 2), (POINTZ_NAN_WKB, 0, 3), (MULTIPOINT_NAN_WKB, 4, 2), (MULTIPOINTZ_NAN_WKB, 4, 3), (GEOMETRYCOLLECTION_NAN_WKB, 7, 2), (GEOMETRYCOLLECTIONZ_NAN_WKB, 7, 3), (NESTED_COLLECTION_NAN_WKB, 7, 2), (NESTED_COLLECTIONZ_NAN_WKB, 7, 3), ], ) 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 # The dimensionality (2D/3D) is only read correctly for GEOS >= 3.9.0 if shapely.geos_version >= (3, 9, 0): assert shapely.get_coordinate_dimension(geom) == expected_dim 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 + (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", [ (GEOJSON_GEOMETRY, GEOJSON_GEOMETRY_EXPECTED), (GEOJSON_FEATURE, GEOJSON_GEOMETRY_EXPECTED), ( GEOJSON_FEATURECOLECTION, shapely.geometrycollections(GEOJSON_COLLECTION_EXPECTED), ), ([GEOJSON_GEOMETRY] * 2, [GEOJSON_GEOMETRY_EXPECTED] * 2), (None, None), ([GEOJSON_GEOMETRY, None], [GEOJSON_GEOMETRY_EXPECTED, 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", [ (GEOJSON_GEOMETRY, GEOJSON_GEOMETRY_EXPECTED), ([GEOJSON_GEOMETRY] * 2, [GEOJSON_GEOMETRY_EXPECTED] * 2), (None, None), ([GEOJSON_GEOMETRY, None], [GEOJSON_GEOMETRY_EXPECTED, 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, 0), reason="GEOS < 3.10") @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): # Pending GEOS ticket: https://trac.osgeo.org/geos/ticket/1139 with pytest.raises(ValueError): assert 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): if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING: pytest.skip("Linearrings are not preserved in GeoJSON") geojson = shapely.to_geojson(geom) actual = shapely.from_geojson(geojson) assert_geometries_equal(actual, geom)