1036 lines
29 KiB
Python
1036 lines
29 KiB
Python
import random
|
|
import warnings
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
import shapely
|
|
import shapely.affinity
|
|
import shapely.geometry
|
|
import shapely.wkb
|
|
import shapely.wkt
|
|
from shapely import geos_version
|
|
from shapely.geometry.base import CAP_STYLE, JOIN_STYLE
|
|
|
|
import geopandas
|
|
from geopandas._compat import HAS_PYPROJ
|
|
from geopandas.array import (
|
|
GeometryArray,
|
|
_check_crs,
|
|
_crs_mismatch_warn,
|
|
from_shapely,
|
|
from_wkb,
|
|
from_wkt,
|
|
points_from_xy,
|
|
to_wkb,
|
|
to_wkt,
|
|
)
|
|
|
|
import pytest
|
|
|
|
triangle_no_missing = [
|
|
shapely.geometry.Polygon([(random.random(), random.random()) for i in range(3)])
|
|
for _ in range(10)
|
|
]
|
|
triangles = triangle_no_missing + [shapely.wkt.loads("POLYGON EMPTY"), None]
|
|
T = from_shapely(triangles)
|
|
|
|
points_no_missing = [
|
|
shapely.geometry.Point(random.random(), random.random()) for _ in range(20)
|
|
]
|
|
points = points_no_missing + [None]
|
|
P = from_shapely(points)
|
|
|
|
|
|
def equal_geometries(result, expected):
|
|
for r, e in zip(result, expected):
|
|
if r is None or e is None:
|
|
if not (r is None and e is None):
|
|
return False
|
|
elif not r.equals(e):
|
|
return False
|
|
return True
|
|
|
|
|
|
def test_points():
|
|
x = np.arange(10).astype(np.float64)
|
|
y = np.arange(10).astype(np.float64) ** 2
|
|
|
|
points = points_from_xy(x, y)
|
|
assert isinstance(points, GeometryArray)
|
|
|
|
for i in range(10):
|
|
assert isinstance(points[i], shapely.geometry.Point)
|
|
assert points[i].x == x[i]
|
|
assert points[i].y == y[i]
|
|
|
|
|
|
def test_points_from_xy():
|
|
# testing the top-level interface
|
|
|
|
# using DataFrame column
|
|
df = pd.DataFrame([{"x": x, "y": x, "z": x} for x in range(10)])
|
|
gs = [shapely.geometry.Point(x, x) for x in range(10)]
|
|
gsz = [shapely.geometry.Point(x, x, x) for x in range(10)]
|
|
geometry1 = geopandas.points_from_xy(df["x"], df["y"])
|
|
geometry2 = geopandas.points_from_xy(df["x"], df["y"], df["z"])
|
|
assert isinstance(geometry1, GeometryArray)
|
|
assert isinstance(geometry2, GeometryArray)
|
|
assert list(geometry1) == gs
|
|
assert list(geometry2) == gsz
|
|
|
|
# using Series or numpy arrays or lists
|
|
for s in [pd.Series(range(10)), np.arange(10), list(range(10))]:
|
|
geometry1 = geopandas.points_from_xy(s, s)
|
|
geometry2 = geopandas.points_from_xy(s, s, s)
|
|
assert isinstance(geometry1, GeometryArray)
|
|
assert isinstance(geometry2, GeometryArray)
|
|
assert list(geometry1) == gs
|
|
assert list(geometry2) == gsz
|
|
|
|
# using different lengths should throw error
|
|
arr_10 = np.arange(10)
|
|
arr_20 = np.arange(20)
|
|
with pytest.raises(ValueError):
|
|
geopandas.points_from_xy(x=arr_10, y=arr_20)
|
|
geopandas.points_from_xy(x=arr_10, y=arr_10, z=arr_20)
|
|
|
|
# Using incomplete arguments should throw error
|
|
with pytest.raises(TypeError):
|
|
geopandas.points_from_xy(x=s)
|
|
geopandas.points_from_xy(y=s)
|
|
geopandas.points_from_xy(z=s)
|
|
|
|
|
|
def test_from_shapely():
|
|
assert isinstance(T, GeometryArray)
|
|
assert equal_geometries(T, triangles)
|
|
|
|
|
|
def test_from_shapely_geo_interface():
|
|
class Point:
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
|
|
@property
|
|
def __geo_interface__(self):
|
|
return {"type": "Point", "coordinates": (self.x, self.y)}
|
|
|
|
result = from_shapely([Point(1.0, 2.0), Point(3.0, 4.0)])
|
|
|
|
expected = from_shapely(
|
|
[shapely.geometry.Point(1.0, 2.0), shapely.geometry.Point(3.0, 4.0)]
|
|
)
|
|
|
|
assert all(v.equals(t) for v, t in zip(result, expected))
|
|
|
|
|
|
def test_from_wkb():
|
|
# list
|
|
L_wkb = [p.wkb for p in points_no_missing]
|
|
res = from_wkb(L_wkb)
|
|
assert isinstance(res, GeometryArray)
|
|
assert all(v.equals(t) for v, t in zip(res, points_no_missing))
|
|
|
|
# array
|
|
res = from_wkb(np.array(L_wkb, dtype=object))
|
|
assert isinstance(res, GeometryArray)
|
|
assert all(v.equals(t) for v, t in zip(res, points_no_missing))
|
|
|
|
# missing values
|
|
# TODO(shapely) does not support empty strings, np.nan, or pd.NA
|
|
missing_values = [None]
|
|
|
|
res = from_wkb(missing_values)
|
|
np.testing.assert_array_equal(res, np.full(len(missing_values), None))
|
|
|
|
# single MultiPolygon
|
|
multi_poly = shapely.geometry.MultiPolygon(
|
|
[shapely.geometry.box(0, 0, 1, 1), shapely.geometry.box(3, 3, 4, 4)]
|
|
)
|
|
res = from_wkb([multi_poly.wkb])
|
|
assert res[0] == multi_poly
|
|
|
|
|
|
def test_from_wkb_hex():
|
|
geometry_hex = ["0101000000CDCCCCCCCCCC1440CDCCCCCCCC0C4A40"]
|
|
res = from_wkb(geometry_hex)
|
|
assert isinstance(res, GeometryArray)
|
|
|
|
# array
|
|
res = from_wkb(np.array(geometry_hex, dtype=object))
|
|
assert isinstance(res, GeometryArray)
|
|
|
|
|
|
def test_from_wkb_on_invalid():
|
|
# Single point LineString hex WKB: invalid
|
|
invalid_wkb_hex = "01020000000100000000000000000008400000000000000840"
|
|
message = "point array must contain 0 or >1 elements"
|
|
|
|
with pytest.raises(Exception, match=message):
|
|
from_wkb([invalid_wkb_hex], on_invalid="raise")
|
|
|
|
with pytest.warns(Warning, match=message):
|
|
res = from_wkb([invalid_wkb_hex], on_invalid="warn")
|
|
assert res == [None]
|
|
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
res = from_wkb([invalid_wkb_hex], on_invalid="ignore")
|
|
assert res == [None]
|
|
|
|
|
|
def test_to_wkb():
|
|
P = from_shapely(points_no_missing)
|
|
res = to_wkb(P)
|
|
exp = np.array([p.wkb for p in points_no_missing], dtype=object)
|
|
assert isinstance(res, np.ndarray)
|
|
np.testing.assert_array_equal(res, exp)
|
|
|
|
res = to_wkb(P, hex=True)
|
|
exp = np.array([p.wkb_hex for p in points_no_missing], dtype=object)
|
|
assert isinstance(res, np.ndarray)
|
|
np.testing.assert_array_equal(res, exp)
|
|
|
|
# missing values
|
|
a = from_shapely([None, points_no_missing[0]])
|
|
res = to_wkb(a)
|
|
assert res[0] is None
|
|
|
|
|
|
@pytest.mark.parametrize("string_type", ["str", "bytes"])
|
|
def test_from_wkt(string_type):
|
|
if string_type == "str":
|
|
f = str
|
|
else:
|
|
|
|
def f(x):
|
|
return bytes(x, "utf8")
|
|
|
|
# list
|
|
L_wkt = [f(p.wkt) for p in points_no_missing]
|
|
res = from_wkt(L_wkt)
|
|
assert isinstance(res, GeometryArray)
|
|
tol = 0.5 * 10 ** (-6)
|
|
assert all(v.equals_exact(t, tolerance=tol) for v, t in zip(res, points_no_missing))
|
|
assert all(v.equals_exact(t, tolerance=tol) for v, t in zip(res, points_no_missing))
|
|
|
|
# array
|
|
res = from_wkt(np.array(L_wkt, dtype=object))
|
|
assert isinstance(res, GeometryArray)
|
|
assert all(v.equals_exact(t, tolerance=tol) for v, t in zip(res, points_no_missing))
|
|
|
|
# missing values
|
|
# TODO(shapely) does not support empty strings, np.nan, or pd.NA
|
|
missing_values = [None]
|
|
|
|
res = from_wkt(missing_values)
|
|
np.testing.assert_array_equal(res, np.full(len(missing_values), None))
|
|
|
|
# single MultiPolygon
|
|
multi_poly = shapely.geometry.MultiPolygon(
|
|
[shapely.geometry.box(0, 0, 1, 1), shapely.geometry.box(3, 3, 4, 4)]
|
|
)
|
|
res = from_wkt([f(multi_poly.wkt)])
|
|
assert res[0] == multi_poly
|
|
|
|
|
|
def test_from_wkt_on_invalid():
|
|
# Single point LineString WKT: invalid
|
|
invalid_wkt = "LINESTRING(0 0)"
|
|
message = "point array must contain 0 or >1 elements"
|
|
|
|
with pytest.raises(Exception, match=message):
|
|
from_wkt([invalid_wkt], on_invalid="raise")
|
|
|
|
with pytest.warns(Warning, match=message):
|
|
res = from_wkt([invalid_wkt], on_invalid="warn")
|
|
assert res == [None]
|
|
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
res = from_wkt([invalid_wkt], on_invalid="ignore")
|
|
assert res == [None]
|
|
|
|
|
|
def test_to_wkt():
|
|
P = from_shapely(points_no_missing)
|
|
res = to_wkt(P, rounding_precision=-1)
|
|
exp = np.array([p.wkt for p in points_no_missing], dtype=object)
|
|
assert isinstance(res, np.ndarray)
|
|
np.testing.assert_array_equal(res, exp)
|
|
|
|
# missing values
|
|
a = from_shapely([None, points_no_missing[0]])
|
|
res = to_wkt(a)
|
|
assert res[0] is None
|
|
|
|
|
|
def test_as_array():
|
|
arr = from_shapely(points_no_missing)
|
|
np_arr1 = np.asarray(arr)
|
|
np_arr2 = arr.to_numpy()
|
|
assert np_arr1[0] == arr[0]
|
|
np.testing.assert_array_equal(np_arr1, np_arr2)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr,args",
|
|
[
|
|
("contains", ()),
|
|
("covers", ()),
|
|
("crosses", ()),
|
|
("disjoint", ()),
|
|
("geom_equals", ()),
|
|
("intersects", ()),
|
|
("overlaps", ()),
|
|
("touches", ()),
|
|
("within", ()),
|
|
("geom_equals_exact", (0.1,)),
|
|
("geom_almost_equals", (3,)),
|
|
],
|
|
)
|
|
# filters required for attr=geom_almost_equals only
|
|
@pytest.mark.filterwarnings(r"ignore:The \'geom_almost_equals\(\)\' method is deprecat")
|
|
@pytest.mark.filterwarnings(r"ignore:The \'almost_equals\(\)\' method is deprecated")
|
|
def test_predicates_vector_scalar(attr, args):
|
|
na_value = False
|
|
|
|
point = points[0]
|
|
tri = triangles[0]
|
|
|
|
for other in [point, tri, shapely.geometry.Polygon()]:
|
|
result = getattr(T, attr)(other, *args)
|
|
assert isinstance(result, np.ndarray)
|
|
assert result.dtype == bool
|
|
|
|
expected = [
|
|
(
|
|
getattr(tri, attr if "geom" not in attr else attr[5:])(other, *args)
|
|
if tri is not None
|
|
else na_value
|
|
)
|
|
for tri in triangles
|
|
]
|
|
|
|
assert result.tolist() == expected
|
|
|
|
# TODO other is missing
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr,args",
|
|
[
|
|
("contains", ()),
|
|
("covers", ()),
|
|
("crosses", ()),
|
|
("disjoint", ()),
|
|
("geom_equals", ()),
|
|
("intersects", ()),
|
|
("overlaps", ()),
|
|
("touches", ()),
|
|
("within", ()),
|
|
("geom_equals_exact", (0.1,)),
|
|
("geom_almost_equals", (3,)),
|
|
],
|
|
)
|
|
# filters required for attr=geom_almost_equals only
|
|
@pytest.mark.filterwarnings(r"ignore:The \'geom_almost_equals\(\)\' method is deprecat")
|
|
@pytest.mark.filterwarnings(r"ignore:The \'almost_equals\(\)\' method is deprecated")
|
|
def test_predicates_vector_vector(attr, args):
|
|
na_value = False
|
|
empty_value = True if attr == "disjoint" else False
|
|
|
|
A = (
|
|
[shapely.geometry.Polygon(), None]
|
|
+ [
|
|
shapely.geometry.Polygon(
|
|
[(random.random(), random.random()) for i in range(3)]
|
|
)
|
|
for _ in range(100)
|
|
]
|
|
+ [None]
|
|
)
|
|
B = [
|
|
shapely.geometry.Polygon([(random.random(), random.random()) for i in range(3)])
|
|
for _ in range(100)
|
|
] + [shapely.geometry.Polygon(), None, None]
|
|
|
|
vec_A = from_shapely(A)
|
|
vec_B = from_shapely(B)
|
|
|
|
result = getattr(vec_A, attr)(vec_B, *args)
|
|
assert isinstance(result, np.ndarray)
|
|
assert result.dtype == bool
|
|
|
|
expected = []
|
|
for a, b in zip(A, B):
|
|
if a is None or b is None:
|
|
expected.append(na_value)
|
|
elif a.is_empty or b.is_empty:
|
|
expected.append(empty_value)
|
|
else:
|
|
expected.append(
|
|
getattr(a, attr if "geom" not in attr else attr[5:])(b, *args)
|
|
)
|
|
|
|
assert result.tolist() == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr",
|
|
[
|
|
"boundary",
|
|
"centroid",
|
|
"convex_hull",
|
|
"envelope",
|
|
"exterior",
|
|
# 'interiors',
|
|
],
|
|
)
|
|
def test_unary_geo(attr):
|
|
na_value = None
|
|
|
|
result = getattr(T, attr)
|
|
expected = [getattr(t, attr) if t is not None else na_value for t in triangles]
|
|
|
|
assert equal_geometries(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize("attr", ["representative_point"])
|
|
def test_unary_geo_callable(attr):
|
|
na_value = None
|
|
|
|
result = getattr(T, attr)()
|
|
expected = [getattr(t, attr)() if t is not None else na_value for t in triangles]
|
|
|
|
assert equal_geometries(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr", ["difference", "symmetric_difference", "union", "intersection"]
|
|
)
|
|
def test_binary_geo_vector(attr):
|
|
na_value = None
|
|
|
|
quads = [shapely.geometry.Polygon(), None]
|
|
while len(quads) < 12:
|
|
geom = shapely.geometry.Polygon(
|
|
[(random.random(), random.random()) for i in range(4)]
|
|
)
|
|
if geom.is_valid:
|
|
quads.append(geom)
|
|
|
|
Q = from_shapely(quads)
|
|
|
|
result = getattr(T, attr)(Q)
|
|
expected = [
|
|
getattr(t, attr)(q) if t is not None and q is not None else na_value
|
|
for t, q in zip(triangles, quads)
|
|
]
|
|
|
|
assert equal_geometries(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr", ["difference", "symmetric_difference", "union", "intersection"]
|
|
)
|
|
def test_binary_geo_scalar(attr):
|
|
na_value = None
|
|
|
|
quads = []
|
|
while len(quads) < 1:
|
|
geom = shapely.geometry.Polygon(
|
|
[(random.random(), random.random()) for i in range(4)]
|
|
)
|
|
if geom.is_valid:
|
|
quads.append(geom)
|
|
|
|
q = quads[0]
|
|
|
|
for other in [q, shapely.geometry.Polygon()]:
|
|
result = getattr(T, attr)(other)
|
|
expected = [
|
|
getattr(t, attr)(other) if t is not None else na_value for t in triangles
|
|
]
|
|
|
|
assert equal_geometries(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr",
|
|
[
|
|
"is_closed",
|
|
"is_valid",
|
|
"is_empty",
|
|
"is_simple",
|
|
"has_z",
|
|
# for is_ring we raise a warning about the value for Polygon changing
|
|
"is_ring",
|
|
],
|
|
)
|
|
def test_unary_predicates(attr):
|
|
na_value = False
|
|
if attr == "is_simple" and geos_version < (3, 8):
|
|
# poly.is_simple raises an error for empty polygon for GEOS < 3.8
|
|
with pytest.raises(Exception): # noqa: B017
|
|
T.is_simple
|
|
vals = triangle_no_missing
|
|
V = from_shapely(vals)
|
|
else:
|
|
vals = triangles
|
|
V = T
|
|
|
|
result = getattr(V, attr)
|
|
|
|
if attr == "is_ring":
|
|
expected = [
|
|
getattr(t, attr) if t is not None and t.exterior is not None else na_value
|
|
for t in vals
|
|
]
|
|
else:
|
|
expected = [getattr(t, attr) if t is not None else na_value for t in vals]
|
|
|
|
assert result.tolist() == expected
|
|
|
|
|
|
def test_is_ring():
|
|
g = [
|
|
shapely.geometry.LinearRing([(0, 0), (1, 1), (1, -1)]),
|
|
shapely.geometry.LineString([(0, 0), (1, 1), (1, -1)]),
|
|
shapely.geometry.LineString([(0, 0), (1, 1), (1, -1), (0, 0)]),
|
|
shapely.geometry.Polygon([(0, 0), (1, 1), (1, -1)]),
|
|
shapely.wkt.loads("POLYGON EMPTY"),
|
|
None,
|
|
]
|
|
expected = [True, False, True, False, False, False]
|
|
result = from_shapely(g).is_ring
|
|
|
|
assert result.tolist() == expected
|
|
|
|
|
|
@pytest.mark.parametrize("attr", ["area", "length"])
|
|
def test_unary_float(attr):
|
|
na_value = np.nan
|
|
result = getattr(T, attr)
|
|
assert isinstance(result, np.ndarray)
|
|
assert result.dtype == np.dtype("float64")
|
|
expected = [getattr(t, attr) if t is not None else na_value for t in triangles]
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
|
|
def test_geom_types():
|
|
cat = T.geom_type
|
|
# empty polygon has GeometryCollection type
|
|
assert list(cat) == ["Polygon"] * (len(T) - 1) + [None]
|
|
|
|
|
|
def test_geom_types_null_mixed():
|
|
geoms = [
|
|
shapely.geometry.Polygon([(0, 0), (0, 1), (1, 1)]),
|
|
None,
|
|
shapely.geometry.Point(0, 1),
|
|
]
|
|
|
|
G = from_shapely(geoms)
|
|
cat = G.geom_type
|
|
|
|
assert list(cat) == ["Polygon", None, "Point"]
|
|
|
|
|
|
def test_binary_distance():
|
|
attr = "distance"
|
|
na_value = np.nan
|
|
# also use nan for empty
|
|
|
|
# vector - vector
|
|
result = P[: len(T)].distance(T[::-1])
|
|
expected = [
|
|
(
|
|
getattr(p, attr)(t)
|
|
if not ((t is None or t.is_empty) or (p is None or p.is_empty))
|
|
else na_value
|
|
)
|
|
for t, p in zip(triangles[::-1], points)
|
|
]
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
# vector - scalar
|
|
p = points[0]
|
|
result = T.distance(p)
|
|
expected = [
|
|
getattr(t, attr)(p) if not (t is None or t.is_empty) else na_value
|
|
for t in triangles
|
|
]
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
# other is empty
|
|
result = T.distance(shapely.geometry.Polygon())
|
|
expected = [na_value] * len(T)
|
|
np.testing.assert_allclose(result, expected)
|
|
# TODO other is None
|
|
|
|
|
|
def test_binary_relate():
|
|
attr = "relate"
|
|
na_value = None
|
|
|
|
# vector - vector
|
|
result = getattr(P[: len(T)], attr)(T[::-1])
|
|
expected = [
|
|
getattr(p, attr)(t) if t is not None and p is not None else na_value
|
|
for t, p in zip(triangles[::-1], points)
|
|
]
|
|
assert list(result) == expected
|
|
|
|
# vector - scalar
|
|
p = points[0]
|
|
result = getattr(T, attr)(p)
|
|
expected = [getattr(t, attr)(p) if t is not None else na_value for t in triangles]
|
|
assert list(result) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("normalized", [True, False])
|
|
def test_binary_project(normalized):
|
|
na_value = np.nan
|
|
lines = (
|
|
[None]
|
|
+ [
|
|
shapely.geometry.LineString(
|
|
[(random.random(), random.random()) for _ in range(2)]
|
|
)
|
|
for _ in range(len(P) - 2)
|
|
]
|
|
+ [None]
|
|
)
|
|
L = from_shapely(lines)
|
|
|
|
result = L.project(P, normalized=normalized)
|
|
expected = [
|
|
(
|
|
line.project(p, normalized=normalized)
|
|
if line is not None and p is not None
|
|
else na_value
|
|
)
|
|
for p, line in zip(points, lines)
|
|
]
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize("cap_style", [CAP_STYLE.round, CAP_STYLE.square])
|
|
@pytest.mark.parametrize("join_style", [JOIN_STYLE.round, JOIN_STYLE.bevel])
|
|
@pytest.mark.parametrize("resolution", [16, 25])
|
|
def test_buffer(resolution, cap_style, join_style):
|
|
na_value = None
|
|
expected = [
|
|
(
|
|
p.buffer(
|
|
0.1, resolution=resolution, cap_style=cap_style, join_style=join_style
|
|
)
|
|
if p is not None
|
|
else na_value
|
|
)
|
|
for p in points
|
|
]
|
|
result = P.buffer(
|
|
0.1, resolution=resolution, cap_style=cap_style, join_style=join_style
|
|
)
|
|
assert equal_geometries(expected, result)
|
|
|
|
dist = np.array([0.1] * len(P))
|
|
result = P.buffer(
|
|
dist, resolution=resolution, cap_style=cap_style, join_style=join_style
|
|
)
|
|
assert equal_geometries(expected, result)
|
|
|
|
|
|
def test_simplify():
|
|
triangles = [
|
|
shapely.geometry.Polygon(
|
|
[(random.random(), random.random()) for i in range(3)]
|
|
).buffer(10)
|
|
for _ in range(10)
|
|
]
|
|
T = from_shapely(triangles)
|
|
|
|
result = T.simplify(1)
|
|
expected = [t.simplify(1) for t in triangles]
|
|
assert all(a.equals(b) for a, b in zip(expected, result))
|
|
|
|
|
|
def test_unary_union():
|
|
geoms = [
|
|
shapely.geometry.Polygon([(0, 0), (0, 1), (1, 1)]),
|
|
shapely.geometry.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
]
|
|
G = from_shapely(geoms)
|
|
with pytest.warns(
|
|
DeprecationWarning, match="The 'unary_union' attribute is deprecated"
|
|
):
|
|
u = G.unary_union()
|
|
|
|
expected = shapely.geometry.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert u.equals(expected)
|
|
assert u.equals(G.union_all())
|
|
|
|
|
|
def test_union_all():
|
|
geoms = [
|
|
shapely.geometry.Polygon([(0, 0), (0, 1), (1, 1)]),
|
|
shapely.geometry.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
]
|
|
G = from_shapely(geoms)
|
|
u = G.union_all()
|
|
|
|
expected = shapely.geometry.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert u.equals(expected)
|
|
|
|
u_cov = G.union_all(method="coverage")
|
|
assert u_cov.equals(expected)
|
|
|
|
with pytest.raises(ValueError, match="Method 'invalid' not recognized."):
|
|
G.union_all(method="invalid")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr, arg",
|
|
[
|
|
("affine_transform", ([0, 1, 1, 0, 0, 0],)),
|
|
("translate", ()),
|
|
("rotate", (10,)),
|
|
("scale", ()),
|
|
("skew", ()),
|
|
],
|
|
)
|
|
def test_affinity_methods(attr, arg):
|
|
result = getattr(T, attr)(*arg)
|
|
expected = [
|
|
getattr(shapely.affinity, attr)(t, *arg) if not (t is None or t.is_empty) else t
|
|
for t in triangles
|
|
]
|
|
assert equal_geometries(result, expected)
|
|
|
|
|
|
# def test_coords():
|
|
# L = T.exterior.coords
|
|
# assert L == [tuple(t.exterior.coords) for t in triangles]
|
|
|
|
|
|
def test_coords_x_y():
|
|
na_value = np.nan
|
|
result = P.x
|
|
expected = [p.x if p is not None else na_value for p in points]
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
result = P.y
|
|
expected = [p.y if p is not None else na_value for p in points]
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
|
|
def test_bounds():
|
|
result = T.bounds
|
|
expected = [
|
|
t.bounds if not (t is None or t.is_empty) else [np.nan] * 4 for t in triangles
|
|
]
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
# additional check for one empty / missing
|
|
for geom in [None, shapely.geometry.Polygon()]:
|
|
E = from_shapely([geom])
|
|
result = E.bounds
|
|
assert result.ndim == 2
|
|
assert result.dtype == "float64"
|
|
np.testing.assert_allclose(result, np.array([[np.nan] * 4]))
|
|
|
|
# empty array (https://github.com/geopandas/geopandas/issues/1195)
|
|
E = from_shapely([])
|
|
result = E.bounds
|
|
assert result.shape == (0, 4)
|
|
assert result.dtype == "float64"
|
|
|
|
|
|
def test_total_bounds():
|
|
result = T.total_bounds
|
|
bounds = np.array(
|
|
[t.bounds if not (t is None or t.is_empty) else [np.nan] * 4 for t in triangles]
|
|
)
|
|
expected = np.array(
|
|
[
|
|
np.nanmin(bounds[:, 0]), # minx
|
|
np.nanmin(bounds[:, 1]), # miny
|
|
np.nanmax(bounds[:, 2]), # maxx
|
|
np.nanmax(bounds[:, 3]), # maxy
|
|
]
|
|
)
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
# additional check for empty array or one empty / missing
|
|
for geoms in [[], [None], [shapely.geometry.Polygon()]]:
|
|
E = from_shapely(geoms)
|
|
result = E.total_bounds
|
|
assert result.ndim == 1
|
|
assert result.dtype == "float64"
|
|
np.testing.assert_allclose(result, np.array([np.nan] * 4))
|
|
|
|
|
|
def test_getitem():
|
|
points = [shapely.geometry.Point(i, i) for i in range(10)]
|
|
P = from_shapely(points)
|
|
|
|
P2 = P[P.area > 0.3]
|
|
assert isinstance(P2, GeometryArray)
|
|
|
|
P3 = P[[1, 3, 5]]
|
|
assert len(P3) == 3
|
|
assert isinstance(P3, GeometryArray)
|
|
assert [p.x for p in P3] == [1, 3, 5]
|
|
|
|
P4 = P[1::2]
|
|
assert len(P4) == 5
|
|
assert isinstance(P3, GeometryArray)
|
|
assert [p.x for p in P4] == [1, 3, 5, 7, 9]
|
|
|
|
P5 = P[1]
|
|
assert isinstance(P5, shapely.geometry.Point)
|
|
assert P5.equals(points[1])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"item",
|
|
[
|
|
geopandas.GeoDataFrame(
|
|
geometry=[shapely.geometry.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])]
|
|
),
|
|
geopandas.GeoSeries(
|
|
[shapely.geometry.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])]
|
|
),
|
|
np.array([shapely.geometry.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])]),
|
|
[shapely.geometry.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])],
|
|
shapely.geometry.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
|
|
],
|
|
)
|
|
def test_setitem(item):
|
|
points = [shapely.geometry.Point(i, i) for i in range(10)]
|
|
P = from_shapely(points)
|
|
|
|
P[[0]] = item
|
|
|
|
assert isinstance(P[0], shapely.geometry.Polygon)
|
|
|
|
|
|
def test_equality_ops():
|
|
with pytest.raises(ValueError):
|
|
_ = P[:5] == P[:7]
|
|
|
|
a1 = from_shapely([points[1], points[2], points[3]])
|
|
a2 = from_shapely([points[1], points[0], points[3]])
|
|
|
|
res = a1 == a2
|
|
assert res.tolist() == [True, False, True]
|
|
|
|
res = a1 != a2
|
|
assert res.tolist() == [False, True, False]
|
|
|
|
# check the correct expansion of list-like geometry
|
|
multi_poly = shapely.geometry.MultiPolygon(
|
|
[shapely.geometry.box(0, 0, 1, 1), shapely.geometry.box(3, 3, 4, 4)]
|
|
)
|
|
a3 = from_shapely([points[1], points[2], points[3], multi_poly])
|
|
|
|
res = a3 == multi_poly
|
|
assert res.tolist() == [False, False, False, True]
|
|
|
|
|
|
def test_dir():
|
|
assert "contains" in dir(P)
|
|
assert "to_numpy" in dir(P)
|
|
|
|
|
|
def test_chaining():
|
|
# contains will give False for empty / missing
|
|
T = from_shapely(triangle_no_missing)
|
|
assert T.contains(T.centroid).all()
|
|
|
|
|
|
def test_pickle():
|
|
import pickle
|
|
|
|
T2 = pickle.loads(pickle.dumps(T))
|
|
# assert (T.data != T2.data).all()
|
|
assert T2[-1] is None
|
|
assert T2[-2].is_empty
|
|
assert T[:-2].geom_equals(T2[:-2]).all()
|
|
|
|
|
|
def test_raise_on_bad_sizes():
|
|
with pytest.raises(ValueError) as info:
|
|
T.contains(P)
|
|
|
|
assert "lengths" in str(info.value).lower()
|
|
assert "12" in str(info.value)
|
|
assert "21" in str(info.value)
|
|
|
|
|
|
def test_buffer_single_multipolygon():
|
|
# https://github.com/geopandas/geopandas/issues/1130
|
|
multi_poly = shapely.geometry.MultiPolygon(
|
|
[shapely.geometry.box(0, 0, 1, 1), shapely.geometry.box(3, 3, 4, 4)]
|
|
)
|
|
arr = from_shapely([multi_poly])
|
|
result = arr.buffer(1)
|
|
expected = [multi_poly.buffer(1)]
|
|
equal_geometries(result, expected)
|
|
result = arr.buffer(np.array([1]))
|
|
equal_geometries(result, expected)
|
|
|
|
|
|
def test_astype_multipolygon():
|
|
# https://github.com/geopandas/geopandas/issues/1145
|
|
multi_poly = shapely.geometry.MultiPolygon(
|
|
[shapely.geometry.box(0, 0, 1, 1), shapely.geometry.box(3, 3, 4, 4)]
|
|
)
|
|
arr = from_shapely([multi_poly])
|
|
result = arr.astype(str)
|
|
assert isinstance(result[0], str)
|
|
assert result[0] == multi_poly.wkt
|
|
|
|
# astype(object) does not convert to string
|
|
result = arr.astype(object)
|
|
assert isinstance(result[0], shapely.geometry.base.BaseGeometry)
|
|
|
|
# astype(np_dtype) honors the dtype
|
|
result = arr.astype(np.dtype("U10"))
|
|
assert result.dtype == np.dtype("U10")
|
|
assert result[0] == multi_poly.wkt[:10]
|
|
|
|
|
|
@pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not installed")
|
|
def test_check_crs():
|
|
t1 = T.copy()
|
|
t1.crs = 4326
|
|
assert _check_crs(t1, T) is False
|
|
assert _check_crs(t1, t1) is True
|
|
assert _check_crs(t1, T, allow_none=True) is True
|
|
|
|
|
|
@pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not installed")
|
|
def test_crs_mismatch_warn():
|
|
t1 = T.copy()
|
|
t2 = T.copy()
|
|
t1.crs = 4326
|
|
t2.crs = 3857
|
|
|
|
# two different CRS
|
|
with pytest.warns(UserWarning, match="CRS mismatch between the CRS"):
|
|
_crs_mismatch_warn(t1, t2)
|
|
|
|
# left None
|
|
with pytest.warns(UserWarning, match="CRS mismatch between the CRS"):
|
|
_crs_mismatch_warn(T, t2)
|
|
|
|
# right None
|
|
with pytest.warns(UserWarning, match="CRS mismatch between the CRS"):
|
|
_crs_mismatch_warn(t1, T)
|
|
|
|
|
|
@pytest.mark.skipif(HAS_PYPROJ, reason="pyproj installed")
|
|
def test_missing_pyproj():
|
|
with pytest.warns(UserWarning, match="Cannot set the CRS, falling back to None"):
|
|
t = T.copy()
|
|
t.crs = 4326
|
|
assert t.crs is None
|
|
|
|
|
|
@pytest.mark.parametrize("NA", [None, np.nan])
|
|
def test_isna(NA):
|
|
t1 = T.copy()
|
|
t1[0] = NA
|
|
assert t1[0] is None
|
|
|
|
|
|
def test_isna_pdNA():
|
|
t1 = T.copy()
|
|
t1[0] = pd.NA
|
|
assert t1[0] is None
|
|
|
|
|
|
def test_shift_has_crs():
|
|
t = T.copy()
|
|
t.crs = 4326
|
|
assert t.shift(1).crs == t.crs
|
|
assert t.shift(0).crs == t.crs
|
|
assert t.shift(-1).crs == t.crs
|
|
|
|
|
|
def test_unique_has_crs():
|
|
t = T.copy()
|
|
t.crs = 4326
|
|
assert t.unique().crs == t.crs
|
|
|
|
|
|
@pytest.mark.skipif(HAS_PYPROJ, reason="pyproj installed")
|
|
def test_to_crs_pyproj_error():
|
|
t = T.copy()
|
|
t.crs = 4326
|
|
with pytest.raises(
|
|
ImportError, match="The 'pyproj' package is required for to_crs"
|
|
):
|
|
t.to_crs(3857)
|
|
|
|
|
|
@pytest.mark.skipif(HAS_PYPROJ, reason="pyproj installed")
|
|
def test_estimate_utm_crs_pyproj_error():
|
|
with pytest.raises(
|
|
ImportError, match="The 'pyproj' package is required for estimate_utm_crs"
|
|
):
|
|
T.estimate_utm_crs()
|
|
|
|
|
|
class TestEstimateUtmCrs:
|
|
def setup_method(self):
|
|
self.esb = shapely.geometry.Point(-73.9847, 40.7484)
|
|
self.sol = shapely.geometry.Point(-74.0446, 40.6893)
|
|
self.landmarks = from_shapely([self.esb, self.sol], crs="epsg:4326")
|
|
|
|
def test_estimate_utm_crs__geographic(self):
|
|
pyproj = pytest.importorskip("pyproj")
|
|
|
|
assert self.landmarks.estimate_utm_crs() == pyproj.CRS("EPSG:32618")
|
|
assert self.landmarks.estimate_utm_crs("NAD83") == pyproj.CRS("EPSG:26918")
|
|
|
|
def test_estimate_utm_crs__projected(self):
|
|
pyproj = pytest.importorskip("pyproj")
|
|
|
|
assert self.landmarks.to_crs("EPSG:3857").estimate_utm_crs() == pyproj.CRS(
|
|
"EPSG:32618"
|
|
)
|
|
|
|
def test_estimate_utm_crs__antimeridian(self):
|
|
pyproj = pytest.importorskip("pyproj")
|
|
|
|
antimeridian = from_shapely(
|
|
[
|
|
shapely.geometry.Point(1722483.900174921, 5228058.6143420935),
|
|
shapely.geometry.Point(4624385.494808555, 8692574.544944234),
|
|
],
|
|
crs="EPSG:3851",
|
|
)
|
|
assert antimeridian.estimate_utm_crs() == pyproj.CRS("EPSG:32760")
|
|
|
|
def test_estimate_utm_crs__out_of_bounds(self):
|
|
pytest.importorskip("pyproj")
|
|
|
|
with pytest.raises(RuntimeError, match="Unable to determine UTM CRS"):
|
|
from_shapely(
|
|
[shapely.geometry.Polygon([(0, 90), (1, 90), (2, 90)])], crs="EPSG:4326"
|
|
).estimate_utm_crs()
|
|
|
|
def test_estimate_utm_crs__missing_crs(self):
|
|
pytest.importorskip("pyproj")
|
|
|
|
with pytest.raises(RuntimeError, match="crs must be set"):
|
|
from_shapely(
|
|
[shapely.geometry.Polygon([(0, 90), (1, 90), (2, 90)])]
|
|
).estimate_utm_crs()
|