1352 lines
49 KiB
Python
1352 lines
49 KiB
Python
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("<BI2d", 1, 1, 1.0, 1.0)
|
|
NAN = struct.pack("<d", float("nan"))
|
|
POINT_NAN_WKB = struct.pack("<BI", 1, 1) + (NAN * 2)
|
|
POINTZ_NAN_WKB = struct.pack("<BI", 1, 1 | EWKBZ) + (NAN * 3)
|
|
POINTM_NAN_WKB = struct.pack("<BI", 1, 1 | EWKBM) + (NAN * 3)
|
|
POINTZM_NAN_WKB = struct.pack("<BI", 1, 1 | EWKBZM) + (NAN * 4)
|
|
MULTIPOINT_NAN_WKB = struct.pack("<BII", 1, 4, 1) + POINT_NAN_WKB
|
|
MULTIPOINTZ_NAN_WKB = struct.pack("<BII", 1, 4 | EWKBZ, 1) + POINTZ_NAN_WKB
|
|
MULTIPOINTM_NAN_WKB = struct.pack("<BII", 1, 4 | EWKBM, 1) + POINTM_NAN_WKB
|
|
MULTIPOINTZM_NAN_WKB = struct.pack("<BII", 1, 4 | EWKBZM, 1) + POINTZM_NAN_WKB
|
|
GEOMETRYCOLLECTION_NAN_WKB = struct.pack("<BII", 1, 7, 1) + POINT_NAN_WKB
|
|
GEOMETRYCOLLECTIONZ_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBZ, 1) + POINTZ_NAN_WKB
|
|
GEOMETRYCOLLECTIONM_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBM, 1) + POINTM_NAN_WKB
|
|
GEOMETRYCOLLECTIONZM_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBZM, 1) + POINTZM_NAN_WKB
|
|
NESTED_COLLECTION_NAN_WKB = struct.pack("<BII", 1, 7, 1) + MULTIPOINT_NAN_WKB
|
|
NESTED_COLLECTIONZ_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBZ, 1) + MULTIPOINTZ_NAN_WKB
|
|
NESTED_COLLECTIONM_NAN_WKB = struct.pack("<BII", 1, 7 | EWKBM, 1) + MULTIPOINTM_NAN_WKB
|
|
NESTED_COLLECTIONZM_NAN_WKB = (
|
|
struct.pack("<BII", 1, 7 | EWKBZM, 1) + MULTIPOINTZM_NAN_WKB
|
|
)
|
|
INVALID_WKB = "01030000000100000002000000507daec600b1354100de02498e5e3d41306ea321fcb03541a011a53d905e3d41" # noqa: E501
|
|
|
|
GEOJSON_GEOMETRY = json.dumps({"type": "Point", "coordinates": [125.6, 10.1]}, indent=4)
|
|
GEOJSON_FEATURE = json.dumps(
|
|
{
|
|
"type": "Feature",
|
|
"geometry": {"type": "Point", "coordinates": [125.6, 10.1]},
|
|
"properties": {"name": "Dinagat Islands"},
|
|
},
|
|
indent=4,
|
|
)
|
|
GEOJSON_FEATURECOLECTION = json.dumps(
|
|
{
|
|
"type": "FeatureCollection",
|
|
"features": [
|
|
{
|
|
"type": "Feature",
|
|
"geometry": {"type": "Point", "coordinates": [102.0, 0.6]},
|
|
"properties": {"prop0": "value0"},
|
|
},
|
|
{
|
|
"type": "Feature",
|
|
"geometry": {
|
|
"type": "LineString",
|
|
"coordinates": [
|
|
[102.0, 0.0],
|
|
[103.0, 1.0],
|
|
[104.0, 0.0],
|
|
[105.0, 1.0],
|
|
],
|
|
},
|
|
"properties": {"prop1": 0.0, "prop0": "value0"},
|
|
},
|
|
{
|
|
"type": "Feature",
|
|
"geometry": {
|
|
"type": "Polygon",
|
|
"coordinates": [
|
|
[
|
|
[100.0, 0.0],
|
|
[101.0, 0.0],
|
|
[101.0, 1.0],
|
|
[100.0, 1.0],
|
|
[100.0, 0.0],
|
|
]
|
|
],
|
|
},
|
|
"properties": {"prop1": {"this": "that"}, "prop0": "value0"},
|
|
},
|
|
],
|
|
},
|
|
indent=4,
|
|
)
|
|
|
|
GEOJSON_GEOMETRY_EXPECTED = shapely.points(125.6, 10.1)
|
|
GEOJSON_COLLECTION_EXPECTED = [
|
|
shapely.points([102.0, 0.6]),
|
|
shapely.linestrings([[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]),
|
|
shapely.polygons(
|
|
[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]
|
|
),
|
|
]
|
|
|
|
|
|
def test_from_wkt():
|
|
expected = shapely.points(1, 1)
|
|
actual = shapely.from_wkt("POINT (1 1)")
|
|
assert_geometries_equal(actual, expected)
|
|
# also accept bytes
|
|
actual = shapely.from_wkt(b"POINT (1 1)")
|
|
assert_geometries_equal(actual, expected)
|
|
|
|
|
|
def test_from_wkt_none():
|
|
# None propagates
|
|
assert shapely.from_wkt(None) is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"wkt, on_invalid, error, message",
|
|
[
|
|
(1, "raise", TypeError, "Expected bytes or string, got int"),
|
|
("", "ignore", None, None),
|
|
("", "warn", Warning, "Expected word but encountered end of stream"),
|
|
("", "raise", GEOSException, "Expected word but encountered end of stream"),
|
|
("", "unsupported_option", ValueError, "not a valid option"),
|
|
("LINESTRING (0 0)", "ignore", None, None),
|
|
("LINESTRING (0 0)", "raise", GEOSException, "must contain 0 or >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) == "<POINT (2 3)>"
|
|
assert repr(point_z) == "<POINT Z (2 3 4)>"
|
|
|
|
|
|
@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) == "<POINT M (2 3 5)>"
|
|
assert repr(point_zm) == "<POINT ZM (2 3 4 5)>"
|
|
|
|
|
|
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) == "<POINT Z EMPTY>"
|
|
|
|
|
|
@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) == "<POINT M EMPTY>"
|
|
assert repr(empty_point_zm) == "<POINT ZM EMPTY>"
|
|
|
|
|
|
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("<BI2d", 1, 1, 1.0, 2.0)
|
|
expected_wkb_z = struct.pack("<BI3d", 1, 1 | EWKBZ, 1.0, 2.0, 3.0)
|
|
|
|
assert shapely.to_wkb(point, byte_order=1) == expected_wkb_z
|
|
assert shapely.to_wkb(point, output_dimension=2, byte_order=1) == expected_wkb
|
|
assert shapely.to_wkb(point, output_dimension=3, byte_order=1) == expected_wkb_z
|
|
if shapely.geos_version >= (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("<BI3d", 1, 1 | EWKBM, 1.0, 2.0, 4.0))
|
|
|
|
expected_wkb = struct.pack("<BI2d", 1, 1, 1.0, 2.0)
|
|
expected_wkb_m = struct.pack("<BI3d", 1, 1 | EWKBM, 1.0, 2.0, 4.0)
|
|
if shapely.geos_version < (3, 12, 0):
|
|
# previous behavior was to ignore M, treat as 2D
|
|
expected_wkb_m = expected_wkb
|
|
|
|
assert shapely.to_wkb(point, byte_order=1) == expected_wkb_m
|
|
assert shapely.to_wkb(point, output_dimension=2, byte_order=1) == expected_wkb
|
|
assert shapely.to_wkb(point, output_dimension=3, byte_order=1) == expected_wkb_m
|
|
if shapely.geos_version >= (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("<BI4d", 1, 1 | EWKBZM, 1.0, 2.0, 3.0, 4.0))
|
|
|
|
expected_wkb = struct.pack("<BI2d", 1, 1, 1.0, 2.0)
|
|
expected_wkb_z = struct.pack("<BI3d", 1, 1 | EWKBZ, 1.0, 2.0, 3.0)
|
|
expected_wkb_zm = struct.pack("<BI4d", 1, 1 | EWKBZM, 1.0, 2.0, 3.0, 4.0)
|
|
if shapely.geos_version < (3, 12, 0):
|
|
# previous behavior was to ignore M, treat as XYZ
|
|
expected_wkb_zm = expected_wkb_z
|
|
|
|
assert shapely.to_wkb(point, byte_order=1) == expected_wkb_zm
|
|
assert shapely.to_wkb(point, output_dimension=2, byte_order=1) == expected_wkb
|
|
assert shapely.to_wkb(point, output_dimension=3, byte_order=1) == expected_wkb_z
|
|
if shapely.geos_version >= (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], "<u4").item() == 4326
|
|
|
|
|
|
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10.0")
|
|
def test_to_wkb_flavor():
|
|
# http://libgeos.org/specifications/wkb/#extended-wkb
|
|
actual = shapely.to_wkb(point_z, byte_order=1) # default "extended"
|
|
assert actual.hex()[2:10] == struct.pack("<I", 1 | EWKBZ).hex()
|
|
actual = shapely.to_wkb(point_z, byte_order=1, flavor="extended")
|
|
assert actual.hex()[2:10] == struct.pack("<I", 1 | EWKBZ).hex()
|
|
actual = shapely.to_wkb(point_z, byte_order=1, flavor="iso")
|
|
assert actual.hex()[2:10] == struct.pack("<I", 1 | ISOWKBZ).hex()
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
shapely.geos_version < (3, 12, 0),
|
|
reason="M coordinates not supported with GEOS < 3.12",
|
|
)
|
|
def test_to_wkb_m_flavor():
|
|
# XYM
|
|
actual = shapely.to_wkb(point_m, byte_order=1) # default "extended"
|
|
assert actual.hex()[2:10] == struct.pack("<I", 1 | EWKBM).hex()
|
|
actual = shapely.to_wkb(point_m, byte_order=1, flavor="iso")
|
|
assert actual.hex()[2:10] == struct.pack("<I", 1 | ISOWKBM).hex()
|
|
|
|
# XYZM
|
|
actual = shapely.to_wkb(point_zm, byte_order=1) # default "extended"
|
|
assert actual.hex()[2:10] == struct.pack("<I", 1 | EWKBZM).hex()
|
|
actual = shapely.to_wkb(point_zm, byte_order=1, flavor="iso")
|
|
assert actual.hex()[2:10] == struct.pack("<I", 1 | ISOWKBZM).hex()
|
|
|
|
|
|
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10.0")
|
|
def test_to_wkb_flavor_srid():
|
|
with pytest.raises(ValueError, match="cannot be used together"):
|
|
shapely.to_wkb(point_z, include_srid=True, flavor="iso")
|
|
|
|
|
|
@pytest.mark.skipif(shapely.geos_version >= (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)
|