This commit is contained in:
2025-01-26 19:24:23 -08:00
parent 32cd60e92b
commit d1dde0dbc6
4155 changed files with 29170 additions and 216373 deletions

View File

@@ -1,9 +1,3 @@
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "Name": "Null Geometry" }, "geometry": null },
{ "type": "Feature", "properties": { "Name": "SF to NY" }, "geometry": { "type": "LineString", "coordinates": [ [ -122.4051293283311, 37.786780113640894 ], [ -73.859832357849271, 40.487594916296196 ] ] } }
]
}
version https://git-lfs.github.com/spec/v1
oid sha256:5e87f89afda555a1b1d43d9dc12864169d6d0149a4f222be12d40a6a86ad8066
size 506

View File

@@ -13,8 +13,8 @@ def test_no_additional_imports():
# "fiona",
# "matplotlib", # matplotlib gets imported by pandas, see below
"mapclassify",
# 'rtree', # rtree actually gets imported if installed
"sqlalchemy",
"psycopg",
"psycopg2",
"geopy",
"geoalchemy2",
@@ -34,5 +34,5 @@ if mods:
blacklist
)
call = [sys.executable, "-c", code]
returncode = subprocess.run(call).returncode
returncode = subprocess.run(call, check=False).returncode
assert returncode == 0

View File

@@ -1,34 +1,30 @@
import random
import warnings
import numpy as np
import pandas as pd
from pyproj import CRS
import shapely
import shapely.affinity
import shapely.geometry
from shapely.geometry.base import CAP_STYLE, JOIN_STYLE, BaseGeometry
import shapely.wkb
import shapely.wkt
try:
from shapely import geos_version
except ImportError:
from shapely._buildcfg import geos_version
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,
_check_crs,
_crs_mismatch_warn,
)
import geopandas._compat as compat
import pytest
@@ -143,11 +139,8 @@ def test_from_wkb():
assert all(v.equals(t) for v, t in zip(res, points_no_missing))
# missing values
# TODO(pygeos) does not support empty strings, np.nan, or pd.NA
# TODO(shapely) does not support empty strings, np.nan, or pd.NA
missing_values = [None]
if not (compat.USE_SHAPELY_20 or compat.USE_PYGEOS):
missing_values.extend([b"", np.nan])
missing_values.append(pd.NA)
res = from_wkb(missing_values)
np.testing.assert_array_equal(res, np.full(len(missing_values), None))
@@ -170,6 +163,24 @@ def test_from_wkb_hex():
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)
@@ -211,13 +222,10 @@ def test_from_wkt(string_type):
assert all(v.equals_exact(t, tolerance=tol) for v, t in zip(res, points_no_missing))
# missing values
# TODO(pygeos) does not support empty strings, np.nan, or pd.NA
# TODO(shapely) does not support empty strings, np.nan, or pd.NA
missing_values = [None]
if not (compat.USE_SHAPELY_20 or compat.USE_PYGEOS):
missing_values.extend([f(""), np.nan])
missing_values.append(pd.NA)
res = from_wkb(missing_values)
res = from_wkt(missing_values)
np.testing.assert_array_equal(res, np.full(len(missing_values), None))
# single MultiPolygon
@@ -228,6 +236,24 @@ def test_from_wkt(string_type):
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)
@@ -241,22 +267,6 @@ def test_to_wkt():
assert res[0] is None
def test_data():
arr = from_shapely(points_no_missing)
with pytest.warns(DeprecationWarning):
np_arr = arr.data
assert isinstance(np_arr, np.ndarray)
if compat.USE_PYGEOS:
np_arr2 = arr.to_numpy()
assert isinstance(np_arr2[0], BaseGeometry)
np_arr3 = np.asarray(arr)
assert isinstance(np_arr3[0], BaseGeometry)
else:
assert arr.to_numpy() is np_arr
assert np.asarray(arr) is np_arr
def test_as_array():
arr = from_shapely(points_no_missing)
np_arr1 = np.asarray(arr)
@@ -281,6 +291,9 @@ def test_as_array():
("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
@@ -293,9 +306,11 @@ def test_predicates_vector_scalar(attr, args):
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
(
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
]
@@ -320,6 +335,9 @@ def test_predicates_vector_scalar(attr, args):
("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
@@ -449,17 +467,12 @@ def test_binary_geo_scalar(attr):
"is_simple",
"has_z",
# for is_ring we raise a warning about the value for Polygon changing
pytest.param(
"is_ring",
marks=[
pytest.mark.filterwarnings("ignore:is_ring:FutureWarning"),
],
),
"is_ring",
],
)
def test_unary_predicates(attr):
na_value = False
if attr == "is_simple" and geos_version < (3, 8) and not compat.USE_PYGEOS:
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
@@ -471,40 +484,17 @@ def test_unary_predicates(attr):
result = getattr(V, attr)
if attr == "is_simple" and geos_version < (3, 8):
# poly.is_simple raises an error for empty polygon for GEOS < 3.8
# with shapely, pygeos always returns False for all GEOS versions
if attr == "is_ring":
expected = [
getattr(t, attr) if t is not None and not t.is_empty else na_value
getattr(t, attr) if t is not None and t.exterior is not None else na_value
for t in vals
]
elif attr == "is_ring":
expected = [
getattr(t.exterior, attr)
if t is not None and t.exterior is not None
else na_value
for t in vals
]
# empty Linearring.is_ring gives False with Shapely < 2.0
if compat.USE_PYGEOS and not compat.SHAPELY_GE_20:
expected[-2] = True
elif (
attr == "is_closed"
and compat.USE_PYGEOS
and compat.SHAPELY_GE_182
and not compat.SHAPELY_GE_20
):
# In shapely 1.8.2, is_closed was changed to return always True for
# Polygon/MultiPolygon, while PyGEOS returns always False
expected = [False] * len(vals)
else:
expected = [getattr(t, attr) if t is not None else na_value for t in vals]
assert result.tolist() == expected
# for is_ring we raise a warning about the value for Polygon changing
@pytest.mark.filterwarnings("ignore:is_ring:FutureWarning")
def test_is_ring():
g = [
shapely.geometry.LinearRing([(0, 0), (1, 1), (1, -1)]),
@@ -514,11 +504,7 @@ def test_is_ring():
shapely.wkt.loads("POLYGON EMPTY"),
None,
]
expected = [True, False, True, True, True, False]
if not compat.USE_PYGEOS and not compat.SHAPELY_GE_20:
# empty polygon is_ring gives False with Shapely < 2.0
expected[-2] = False
expected = [True, False, True, False, False, False]
result = from_shapely(g).is_ring
assert result.tolist() == expected
@@ -561,9 +547,11 @@ def test_binary_distance():
# 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
(
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)
@@ -620,9 +608,11 @@ def test_binary_project(normalized):
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
(
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)
@@ -632,16 +622,15 @@ def test_binary_project(normalized):
@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):
if compat.USE_PYGEOS:
# TODO(pygeos) need to further investigate why this test fails
if cap_style == 1 and join_style == 3:
pytest.skip("failing TODO")
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
(
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(
@@ -676,10 +665,32 @@ def test_unary_union():
shapely.geometry.Polygon([(0, 0), (1, 0), (1, 1)]),
]
G = from_shapely(geoms)
u = G.unary_union()
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(
@@ -810,7 +821,7 @@ def test_setitem(item):
def test_equality_ops():
with pytest.raises(ValueError):
P[:5] == P[:7]
_ = P[:5] == P[:7]
a1 = from_shapely([points[1], points[2], points[3]])
a2 = from_shapely([points[1], points[0], points[3]])
@@ -833,7 +844,7 @@ def test_equality_ops():
def test_dir():
assert "contains" in dir(P)
assert "data" in dir(P)
assert "to_numpy" in dir(P)
def test_chaining():
@@ -894,6 +905,7 @@ def test_astype_multipolygon():
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
@@ -902,6 +914,7 @@ def test_check_crs():
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()
@@ -921,6 +934,14 @@ def test_crs_mismatch_warn():
_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()
@@ -948,6 +969,24 @@ def test_unique_has_crs():
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)
@@ -955,15 +994,21 @@ class TestEstimateUtmCrs:
self.landmarks = from_shapely([self.esb, self.sol], crs="epsg:4326")
def test_estimate_utm_crs__geographic(self):
assert self.landmarks.estimate_utm_crs() == CRS("EPSG:32618")
assert self.landmarks.estimate_utm_crs("NAD83") == CRS("EPSG:26918")
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):
assert self.landmarks.to_crs("EPSG:3857").estimate_utm_crs() == CRS(
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),
@@ -971,15 +1016,19 @@ class TestEstimateUtmCrs:
],
crs="EPSG:3851",
)
assert antimeridian.estimate_utm_crs() == CRS("EPSG:32760")
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)])]

View File

@@ -1,7 +1,7 @@
import pytest
from geopandas._compat import import_optional_dependency
import pytest
def test_import_optional_dependency_present():
# pandas is not optional, but we know it is present

View File

@@ -1,15 +1,19 @@
import random
import warnings
import numpy as np
import pandas as pd
import pyproj
import pytest
from shapely.geometry import Point, Polygon, LineString
from geopandas import GeoSeries, GeoDataFrame, points_from_xy, datasets, read_file
from geopandas.array import from_shapely, from_wkb, from_wkt, GeometryArray
from shapely.geometry import LineString, Point, Polygon
from geopandas import GeoDataFrame, GeoSeries, points_from_xy, read_file
from geopandas.array import GeometryArray, from_shapely, from_wkb, from_wkt
import pytest
from geopandas.testing import assert_geodataframe_equal
pyproj = pytest.importorskip("pyproj")
def _create_df(x, y=None, crs=None):
y = y or x
@@ -82,6 +86,9 @@ def test_to_crs_dimension_z():
assert result.has_z.all()
# pyproj + numpy 1.25 trigger warning for single-element array -> recommdation is to
# ignore the warning for now (https://github.com/pyproj4/pyproj/issues/1307)
@pytest.mark.filterwarnings("ignore:Conversion of an array with:DeprecationWarning")
def test_to_crs_dimension_mixed():
s = GeoSeries([Point(1, 2), LineString([(1, 2, 3), (4, 5, 6)])], crs=2056)
result = s.to_crs(epsg=4326)
@@ -150,6 +157,9 @@ def test_transform2(epsg4326, epsg26918):
assert_geodataframe_equal(df, utm, check_less_precise=True, check_crs=False)
# pyproj + numpy 1.25 trigger warning for single-element array -> recommdation is to
# ignore the warning for now (https://github.com/pyproj4/pyproj/issues/1307)
@pytest.mark.filterwarnings("ignore:Conversion of an array with:DeprecationWarning")
def test_crs_axis_order__always_xy():
df = GeoDataFrame(geometry=[Point(-1683723, 6689139)], crs="epsg:26918")
lonlat = df.to_crs("epsg:4326")
@@ -203,7 +213,7 @@ class TestGeometryArrayCRS:
assert s.values.crs == self.osgb
# manually change CRS
s.crs = 4326
s = s.set_crs(4326, allow_override=True)
assert s.crs == self.wgs
assert s.values.crs == self.wgs
@@ -252,7 +262,7 @@ class TestGeometryArrayCRS:
arr = from_shapely(self.geoms)
s = GeoSeries(arr, crs=27700)
df = GeoDataFrame(geometry=s)
df.crs = 4326
df = df.set_crs(crs="epsg:4326", allow_override=True)
assert df.crs == self.wgs
assert df.geometry.crs == self.wgs
assert df.geometry.values.crs == self.wgs
@@ -319,7 +329,11 @@ class TestGeometryArrayCRS:
df.crs = 27700
# geometry column without geometry
df = GeoDataFrame({"geometry": [Point(0, 1)]}).assign(geometry=[0])
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", "Geometry column does not contain geometry", UserWarning
)
df = GeoDataFrame({"geometry": [Point(0, 1)]}).assign(geometry=[0])
with pytest.raises(
ValueError,
match="Assigning CRS to a GeoDataFrame without an active geometry",
@@ -404,7 +418,7 @@ class TestGeometryArrayCRS:
FutureWarning, match="You are adding a column named 'geometry'"
):
df["geometry"] = scalar
df.crs = 4326
df = df.set_crs(4326)
assert df.crs == self.wgs
assert df.geometry.crs == self.wgs
assert df.geometry.values.crs == self.wgs
@@ -415,8 +429,7 @@ class TestGeometryArrayCRS:
df = GeoDataFrame()
df.crs = 4326
def test_read_file(self):
nybb_filename = datasets.get_path("nybb")
def test_read_file(self, nybb_filename):
df = read_file(nybb_filename)
assert df.crs == pyproj.CRS(2263)
assert df.geometry.crs == pyproj.CRS(2263)
@@ -728,6 +741,7 @@ class TestSetCRS:
assert non_naive.crs == "EPSG:3857"
assert result.crs == "EPSG:3857"
# raise error when no crs is passed
with pytest.raises(ValueError):
naive.set_crs(crs=None, epsg=None)
# set CRS to None
result = non_naive.set_crs(crs=None, allow_override=True)
assert result.crs is None
assert non_naive.crs == "EPSG:3857"

View File

@@ -5,8 +5,11 @@ import pytest
@pytest.mark.parametrize(
"test_dataset", ["naturalearth_lowres", "naturalearth_cities", "nybb"]
"test_dataset", ["naturalearth_lowres", "naturalearth_cities", "nybb", "foo"]
)
def test_read_paths(test_dataset):
with pytest.warns(FutureWarning, match="The geopandas.dataset module is"):
with pytest.raises(
AttributeError,
match=r"The geopandas\.dataset has been deprecated and was removed",
):
assert isinstance(read_file(get_path(test_dataset)), GeoDataFrame)

View File

@@ -10,7 +10,6 @@ def cumsum(whatever):
It computes the cumulative {operation}.
"""
...
@doc(
@@ -27,18 +26,15 @@ def cumsum(whatever):
method="cumavg",
operation="average",
)
def cumavg(whatever):
...
def cumavg(whatever): ...
@doc(cumsum, method="cummax", operation="maximum")
def cummax(whatever):
...
def cummax(whatever): ...
@doc(cummax, method="cummin", operation="minimum")
def cummin(whatever):
...
def cummin(whatever): ...
def test_docstring_formatting():

View File

@@ -5,17 +5,15 @@ import pandas as pd
import geopandas
from geopandas import GeoDataFrame, read_file
from geopandas._compat import HAS_PYPROJ, PANDAS_GE_15, PANDAS_GE_20, PANDAS_GE_30
from pandas.testing import assert_frame_equal
import pytest
from geopandas._compat import PANDAS_GE_15, PANDAS_GE_20
from geopandas.testing import assert_geodataframe_equal, geom_almost_equals
from pandas.testing import assert_frame_equal
@pytest.fixture
def nybb_polydf():
nybb_filename = geopandas.datasets.get_path("nybb")
def nybb_polydf(nybb_filename):
nybb_polydf = read_file(nybb_filename)
nybb_polydf = nybb_polydf[["geometry", "BoroName", "BoroCode"]]
nybb_polydf = nybb_polydf.rename(columns={"geometry": "myshapes"})
@@ -32,7 +30,7 @@ def merged_shapes(nybb_polydf):
manhattan_bronx = nybb_polydf.loc[3:4]
others = nybb_polydf.loc[0:2]
collapsed = [others.geometry.unary_union, manhattan_bronx.geometry.unary_union]
collapsed = [others.geometry.union_all(), manhattan_bronx.geometry.union_all()]
merged_shapes = GeoDataFrame(
{"myshapes": collapsed},
geometry="myshapes",
@@ -64,6 +62,7 @@ def test_geom_dissolve(nybb_polydf, first):
assert geom_almost_equals(test, first)
@pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not installed")
def test_dissolve_retains_existing_crs(nybb_polydf):
assert nybb_polydf.crs is not None
test = nybb_polydf.dissolve("manhattan_bronx")
@@ -71,7 +70,7 @@ def test_dissolve_retains_existing_crs(nybb_polydf):
def test_dissolve_retains_nonexisting_crs(nybb_polydf):
nybb_polydf.crs = None
nybb_polydf.geometry.array.crs = None
test = nybb_polydf.dissolve("manhattan_bronx")
assert test.crs is None
@@ -95,7 +94,7 @@ def test_mean_dissolve(nybb_polydf, first, expected_mean):
)
# for non pandas "mean", numeric only cannot be applied. Drop columns manually
test2 = nybb_polydf.drop(columns=["BoroName"]).dissolve(
"manhattan_bronx", aggfunc=np.mean
"manhattan_bronx", aggfunc="mean"
)
assert_frame_equal(expected_mean, test, check_column_type=False)
@@ -152,7 +151,7 @@ def test_dissolve_none(nybb_polydf):
test = nybb_polydf.dissolve(by=None)
expected = GeoDataFrame(
{
nybb_polydf.geometry.name: [nybb_polydf.geometry.unary_union],
nybb_polydf.geometry.name: [nybb_polydf.geometry.union_all()],
"BoroName": ["Staten Island"],
"BoroCode": [5],
"manhattan_bronx": [5],
@@ -167,7 +166,7 @@ def test_dissolve_none_mean(nybb_polydf):
test = nybb_polydf.dissolve(aggfunc="mean", numeric_only=True)
expected = GeoDataFrame(
{
nybb_polydf.geometry.name: [nybb_polydf.geometry.unary_union],
nybb_polydf.geometry.name: [nybb_polydf.geometry.union_all()],
"BoroCode": [3.0],
"manhattan_bronx": [5.4],
},
@@ -261,6 +260,7 @@ def test_dissolve_categorical():
# when observed=False we get an additional observation
# that wasn't in the original data
none_val = "GEOMETRYCOLLECTION EMPTY" if PANDAS_GE_30 else None
expected_gdf_observed_false = geopandas.GeoDataFrame(
{
"cat": pd.Categorical(["a", "a", "b", "b"]),
@@ -268,7 +268,7 @@ def test_dissolve_categorical():
"geometry": geopandas.array.from_wkt(
[
"MULTIPOINT (0 0, 1 1)",
None,
none_val,
"POINT (2 2)",
"POINT (3 3)",
]
@@ -348,3 +348,25 @@ def test_dissolve_multi_agg(nybb_polydf, merged_shapes):
)
assert_geodataframe_equal(test, merged_shapes)
assert len(record) == 0
def test_coverage_dissolve(nybb_polydf):
manhattan_bronx = nybb_polydf.loc[3:4]
others = nybb_polydf.loc[0:2]
collapsed = [
others.geometry.union_all(method="coverage"),
manhattan_bronx.geometry.union_all(method="coverage"),
]
merged_shapes = GeoDataFrame(
{"myshapes": collapsed},
geometry="myshapes",
index=pd.Index([5, 6], name="manhattan_bronx"),
crs=nybb_polydf.crs,
)
merged_shapes["BoroName"] = ["Staten Island", "Manhattan"]
merged_shapes["BoroCode"] = [5, 1]
test = nybb_polydf.dissolve("manhattan_bronx", method="coverage")
assert_frame_equal(merged_shapes, test, check_column_type=False)

View File

@@ -1,8 +1,15 @@
import geopandas as gpd
import uuid
from packaging.version import Version
import numpy as np
import pandas as pd
import shapely
import geopandas as gpd
from geopandas._compat import HAS_PYPROJ
import pytest
from packaging.version import Version
folium = pytest.importorskip("folium")
branca = pytest.importorskip("branca")
@@ -10,26 +17,34 @@ matplotlib = pytest.importorskip("matplotlib")
mapclassify = pytest.importorskip("mapclassify")
geodatasets = pytest.importorskip("geodatasets")
from matplotlib import cm
from matplotlib import colors
from branca.colormap import StepColormap
from matplotlib import cm, colors
BRANCA_05 = Version(branca.__version__) > Version("0.4.2")
FOLIUM_G_014 = Version(folium.__version__) > Version("0.14.0")
class TestExplore:
def setup_method(self):
self.nybb = gpd.read_file(gpd.datasets.get_path("nybb"))
self.world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
self.cities = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))
self.chicago = gpd.read_file(geodatasets.get_path("geoda.chicago_commpop"))
self.world["range"] = range(len(self.world))
self.missing = self.world.copy()
np.random.seed(42)
self.missing.loc[np.random.choice(self.missing.index, 40), "continent"] = np.nan
self.missing.loc[np.random.choice(self.missing.index, 40), "pop_est"] = np.nan
@pytest.fixture(scope="class")
def _setup_class_test_explore(
nybb_filename, naturalearth_lowres, naturalearth_cities, request
):
request.cls.nybb = gpd.read_file(nybb_filename)
request.cls.world = gpd.read_file(naturalearth_lowres)
request.cls.cities = gpd.read_file(naturalearth_cities)
request.cls.chicago = gpd.read_file(geodatasets.get_path("geoda.chicago_commpop"))
request.cls.world["range"] = range(len(request.cls.world))
request.cls.missing = request.cls.world.copy()
np.random.seed(42)
request.cls.missing.loc[
np.random.choice(request.cls.missing.index, 40), "continent"
] = np.nan
request.cls.missing.loc[
np.random.choice(request.cls.missing.index, 40), "pop_est"
] = np.nan
@pytest.mark.usefixtures("_setup_class_test_explore")
class TestExplore:
def _fetch_map_string(self, m):
out = m._parent.render()
out_str = "".join(out.split())
@@ -46,6 +61,7 @@ class TestExplore:
"""Make sure default choropleth pass"""
self.world.explore(column="pop_est")
@pytest.mark.skipif(not HAS_PYPROJ, reason="requires pyproj")
def test_map_settings_default(self):
"""Check default map settings"""
m = self.world.explore()
@@ -64,6 +80,7 @@ class TestExplore:
assert m.global_switches.disable_3d is False
assert "openstreetmap" in m.to_dict()["children"].keys()
@pytest.mark.skipif(not HAS_PYPROJ, reason="requires pyproj")
def test_map_settings_custom(self):
"""Check custom map settings"""
m = self.nybb.explore(
@@ -284,6 +301,42 @@ class TestExplore:
assert '"__folium_color":"#9edae5","bool":true' in out2_str
assert '"__folium_color":"#1f77b4","bool":false' in out2_str
def test_datetime(self):
df = self.nybb.copy().head(2)
date1 = pd.Timestamp(2022, 1, 1, 1, 22, 0, 0)
date2 = pd.Timestamp(2025, 1, 1, 1, 22, 0, 0)
df["datetime"] = [date1, date2]
m1 = df.explore("datetime")
out1_str = self._fetch_map_string(m1)
assert '"__folium_color":"#9edae5","datetime":"2025-01-0101:22:00"' in out1_str
assert '"__folium_color":"#1f77b4","datetime":"2022-01-0101:22:00"' in out1_str
df2 = df.set_index("datetime")
m2 = df2.explore()
out2_str = self._fetch_map_string(m2)
assert '"datetime":"2025-01-0101:22:00"' in out2_str
assert '"datetime":"2022-01-0101:22:00"' in out2_str
def test_non_json_serialisable(self):
df = self.nybb.copy().head(2)
u1 = "12345678-1234-5678-1234-567812345678"
uuid1 = uuid.UUID(u1)
u2 = "12345678-1234-5678-1234-567812345679"
uuid2 = uuid.UUID(u2)
df["object"] = [uuid1, uuid2]
m1 = df.explore("object")
out1_str = self._fetch_map_string(m1)
assert f'"__folium_color":"#9edae5","object":"{u2}"' in out1_str
assert f'"__folium_color":"#1f77b4","object":"{u1}"' in out1_str
df2 = df.set_index("object")
m2 = df2.explore()
out2_str = self._fetch_map_string(m2)
assert f'"object":"{u2}"' in out2_str
assert f'"object":"{u1}"' in out2_str
def test_string(self):
df = self.nybb.copy()
df["string"] = pd.array([1, 2, 3, 4, 5], dtype="string")
@@ -333,7 +386,7 @@ class TestExplore:
def test_no_crs(self):
"""Naive geometry get no tiles"""
df = self.world.copy()
df.crs = None
df.geometry.array.crs = None
m = df.explore()
assert "openstreetmap" not in m.to_dict()["children"].keys()
@@ -351,12 +404,12 @@ class TestExplore:
m = self.world.explore(
style_kwds={
"style_function": lambda x: {
"fillColor": "red"
if x["properties"]["gdp_md_est"] < 10**6
else "green",
"color": "black"
if x["properties"]["gdp_md_est"] < 10**6
else "white",
"fillColor": (
"red" if x["properties"]["gdp_md_est"] < 10**6 else "green"
),
"color": (
"black" if x["properties"]["gdp_md_est"] < 10**6 else "white"
),
}
}
)
@@ -683,6 +736,7 @@ class TestExplore:
== "tickValues([140.0,'','','',559086084.0,'','','',1118172028.0,'','',''])"
)
@pytest.mark.skipif(not HAS_PYPROJ, reason="requires pyproj")
def test_xyzservices_providers(self):
xyzservices = pytest.importorskip("xyzservices")
@@ -697,8 +751,9 @@ class TestExplore:
'attribution":"\\u0026copy;\\u003cahref=\\"https://www.openstreetmap.org'
in out_str
)
assert '"maxNativeZoom":20,"maxZoom":20,"minZoom":0' in out_str
assert '"maxZoom":20,"minZoom":0' in out_str
@pytest.mark.skipif(not HAS_PYPROJ, reason="requires pyproj")
def test_xyzservices_query_name(self):
pytest.importorskip("xyzservices")
@@ -713,8 +768,9 @@ class TestExplore:
'attribution":"\\u0026copy;\\u003cahref=\\"https://www.openstreetmap.org'
in out_str
)
assert '"maxNativeZoom":20,"maxZoom":20,"minZoom":0' in out_str
assert '"maxZoom":20,"minZoom":0' in out_str
@pytest.mark.skipif(not HAS_PYPROJ, reason="requires pyproj")
def test_xyzservices_providers_min_zoom_override(self):
xyzservices = pytest.importorskip("xyzservices")
@@ -723,8 +779,9 @@ class TestExplore:
)
out_str = self._fetch_map_string(m)
assert '"maxNativeZoom":20,"maxZoom":20,"minZoom":3' in out_str
assert '"maxZoom":20,"minZoom":3' in out_str
@pytest.mark.skipif(not HAS_PYPROJ, reason="requires pyproj")
def test_xyzservices_providers_max_zoom_override(self):
xyzservices = pytest.importorskip("xyzservices")
@@ -733,8 +790,9 @@ class TestExplore:
)
out_str = self._fetch_map_string(m)
assert '"maxNativeZoom":12,"maxZoom":12,"minZoom":0' in out_str
assert '"maxZoom":12,"minZoom":0' in out_str
@pytest.mark.skipif(not HAS_PYPROJ, reason="requires pyproj")
def test_xyzservices_providers_both_zooms_override(self):
xyzservices = pytest.importorskip("xyzservices")
@@ -745,7 +803,7 @@ class TestExplore:
)
out_str = self._fetch_map_string(m)
assert '"maxNativeZoom":12,"maxZoom":12,"minZoom":3' in out_str
assert '"maxZoom":12,"minZoom":3' in out_str
def test_linearrings(self):
rings = self.nybb.explode(index_parts=True).exterior
@@ -940,3 +998,51 @@ class TestExplore:
"zoom_control": False,
}
)
def test_none_geometry(self):
# None and empty geoms are dropped prior plotting
df = self.nybb.copy()
df.loc[0, df.geometry.name] = None
m = df.explore()
self._fetch_map_string(m)
def test_empty_geometry(self):
# None and empty geoms are dropped prior plotting
df = self.nybb.copy()
df.loc[0, df.geometry.name] = shapely.Point()
m = df.explore()
self._fetch_map_string(m)
def test_all_empty(self):
with_crs = gpd.GeoDataFrame(
geometry=[shapely.Point(), shapely.Point()], crs=4326
)
with pytest.warns(
UserWarning,
match="The GeoSeries you are attempting to plot is composed of empty",
):
m = with_crs.explore()
out_str = self._fetch_map_string(m)
if HAS_PYPROJ:
assert "center:[0.0,0.0],crs:L.CRS.EPSG3857" in out_str
no_crs = gpd.GeoDataFrame(geometry=[shapely.Point(), shapely.Point()])
with pytest.warns(
UserWarning,
match="The GeoSeries you are attempting to plot is composed of empty",
):
m = no_crs.explore()
out_str = self._fetch_map_string(m)
assert "center:[0.0,0.0],crs:L.CRS.Simple" in out_str
def test_add_all_empty_named_index(self):
gdf1 = gpd.GeoDataFrame(geometry=[shapely.Point(0, 0), shapely.Point(1, 1)])
gdf2 = gpd.GeoDataFrame(geometry=[shapely.Point(), shapely.Point()])
m = gdf1.rename_axis(index="index_name").explore()
with pytest.warns(
UserWarning,
match="The GeoSeries you are attempting to plot is composed of empty",
):
m = gdf2.rename_axis(index="index_name").explore(m=m, color="red")
out_str = self._fetch_map_string(m)
assert "center:[0.5,0.5],crs:L.CRS.Simple" in out_str

View File

@@ -13,21 +13,22 @@ A set of fixtures are defined to provide data for the tests (the fixtures
expected to be available to pytest by the inherited pandas tests).
"""
import itertools
import operator
import numpy as np
from numpy.testing import assert_array_equal
import pandas as pd
from pandas.testing import assert_series_equal
from pandas.tests.extension import base as extension_tests
import shapely.geometry
from shapely.geometry import Point
from geopandas._compat import PANDAS_GE_15, PANDAS_GE_21, PANDAS_GE_22
from geopandas.array import GeometryArray, GeometryDtype, from_shapely
from geopandas._compat import ignore_shapely2_warnings, SHAPELY_GE_20, PANDAS_GE_15
import pytest
from pandas.testing import assert_frame_equal, assert_series_equal
# -----------------------------------------------------------------------------
# Compat with extension tests in older pandas versions
@@ -36,9 +37,6 @@ import pytest
not_yet_implemented = pytest.mark.skip(reason="Not yet implemented")
no_minmax = pytest.mark.skip(reason="Min/max not supported")
requires_shapely2 = pytest.mark.skipif(
not SHAPELY_GE_20, reason="Requires hashable geometries"
)
# -----------------------------------------------------------------------------
@@ -54,8 +52,7 @@ def dtype():
def make_data():
a = np.empty(100, dtype=object)
with ignore_shapely2_warnings():
a[:] = [shapely.geometry.Point(i, i) for i in range(100)]
a[:] = [shapely.geometry.Point(i, i) for i in range(100)]
ga = from_shapely(a)
return ga
@@ -316,20 +313,6 @@ class TestDtype(extension_tests.BaseDtypeTests):
class TestInterface(extension_tests.BaseInterfaceTests):
def test_array_interface(self, data):
# we are overriding this base test because the creation of `expected`
# potentially doesn't work for shapely geometries
# TODO can be removed with Shapely 2.0
result = np.array(data)
assert result[0] == data[0]
result = np.array(data, dtype=object)
# expected = np.array(list(data), dtype=object)
expected = np.empty(len(data), dtype=object)
with ignore_shapely2_warnings():
expected[:] = list(data)
assert_array_equal(result, expected)
def test_contains(self, data, data_missing):
# overridden due to the inconsistency between
# GeometryDtype.na_value = np.nan
@@ -352,7 +335,74 @@ class TestConstructors(extension_tests.BaseConstructorsTests):
class TestReshaping(extension_tests.BaseReshapingTests):
pass
# NOTE: this test is copied from pandas/tests/extension/base/reshaping.py
# because starting with pandas 3.0 the assert_frame_equal is strict regarding
# the exact missing value (None vs NaN)
# Our `result` uses None, but the way the `expected` is created results in
# NaNs (and specifying to use None as fill value in unstack also does not
# help)
# -> the only change compared to the upstream test is marked
@pytest.mark.parametrize(
"index",
[
# Two levels, uniform.
pd.MultiIndex.from_product(([["A", "B"], ["a", "b"]]), names=["a", "b"]),
# non-uniform
pd.MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "b")]),
# three levels, non-uniform
pd.MultiIndex.from_product([("A", "B"), ("a", "b", "c"), (0, 1, 2)]),
pd.MultiIndex.from_tuples(
[
("A", "a", 1),
("A", "b", 0),
("A", "a", 0),
("B", "a", 0),
("B", "c", 1),
]
),
],
)
@pytest.mark.parametrize("obj", ["series", "frame"])
def test_unstack(self, data, index, obj):
data = data[: len(index)]
if obj == "series":
ser = pd.Series(data, index=index)
else:
ser = pd.DataFrame({"A": data, "B": data}, index=index)
n = index.nlevels
levels = list(range(n))
# [0, 1, 2]
# [(0,), (1,), (2,), (0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
combinations = itertools.chain.from_iterable(
itertools.permutations(levels, i) for i in range(1, n)
)
for level in combinations:
result = ser.unstack(level=level)
assert all(
isinstance(result[col].array, type(data)) for col in result.columns
)
if obj == "series":
# We should get the same result with to_frame+unstack+droplevel
df = ser.to_frame()
alt = df.unstack(level=level).droplevel(0, axis=1)
assert_frame_equal(result, alt)
obj_ser = ser.astype(object)
expected = obj_ser.unstack(level=level, fill_value=data.dtype.na_value)
if obj == "series":
assert (expected.dtypes == object).all()
# <------------ next line is added
expected[expected.isna()] = None
# ------------->
result = result.astype(object)
assert_frame_equal(result, expected)
class TestGetitem(extension_tests.BaseGetitemTests):
@@ -403,24 +453,38 @@ class TestMissing(extension_tests.BaseMissingTests):
# `geopandas\tests\test_pandas_methods.py::test_fillna_scalar`
# and `geopandas\tests\test_pandas_methods.py::test_fillna_series`.
@pytest.mark.skip("fillna method not supported")
@pytest.mark.skipif(
not PANDAS_GE_21, reason="fillna method not supported with older pandas"
)
def test_fillna_limit_pad(self, data_missing):
pass
super().test_fillna_limit_pad(data_missing)
@pytest.mark.skip("fillna method not supported")
@pytest.mark.skipif(
not PANDAS_GE_21, reason="fillna method not supported with older pandas"
)
def test_fillna_limit_backfill(self, data_missing):
pass
super().test_fillna_limit_backfill(data_missing)
@pytest.mark.skip("fillna method not supported")
def test_fillna_series_method(self, data_missing, method):
pass
@pytest.mark.skipif(
not PANDAS_GE_21, reason="fillna method not supported with older pandas"
)
def test_fillna_series_method(self, data_missing, fillna_method):
super().test_fillna_series_method(data_missing, fillna_method)
@pytest.mark.skip("fillna method not supported")
@pytest.mark.skipif(
not PANDAS_GE_21, reason="fillna method not supported with older pandas"
)
def test_fillna_no_op_returns_copy(self, data):
pass
super().test_fillna_no_op_returns_copy(data)
class TestReduce(extension_tests.BaseNoReduceTests):
if PANDAS_GE_22:
from pandas.tests.extension.base import BaseReduceTests
else:
from pandas.tests.extension.base import BaseNoReduceTests as BaseReduceTests
class TestReduce(BaseReduceTests):
@pytest.mark.skip("boolean reduce (any/all) tested in test_pandas_methods")
def test_reduce_series_boolean(self):
pass
@@ -506,7 +570,6 @@ class TestMethods(extension_tests.BaseMethodsTests):
def test_value_counts_with_normalize(self, data):
pass
@requires_shapely2
@pytest.mark.parametrize("ascending", [True, False])
def test_sort_values_frame(self, data_for_sorting, ascending):
super().test_sort_values_frame(data_for_sorting, ascending)
@@ -555,16 +618,13 @@ class TestCasting(extension_tests.BaseCastingTests):
class TestGroupby(extension_tests.BaseGroupbyTests):
@requires_shapely2
@pytest.mark.parametrize("as_index", [True, False])
def test_groupby_extension_agg(self, as_index, data_for_grouping):
super().test_groupby_extension_agg(as_index, data_for_grouping)
@requires_shapely2
def test_groupby_extension_transform(self, data_for_grouping):
super().test_groupby_extension_transform(data_for_grouping)
@requires_shapely2
@pytest.mark.parametrize(
"op",
[

View File

@@ -3,13 +3,14 @@ import pandas as pd
from shapely.geometry import Point
from geopandas import GeoDataFrame, GeoSeries
from geopandas._compat import HAS_PYPROJ
from geopandas.tools import geocode, reverse_geocode
from geopandas.tools.geocoding import _prepare_geocode_result
import pytest
from geopandas.testing import assert_geodataframe_equal
from geopandas.tests.util import assert_geoseries_equal, mock
from pandas.testing import assert_series_equal
from geopandas.testing import assert_geodataframe_equal
import pytest
geopy = pytest.importorskip("geopy")
@@ -71,7 +72,8 @@ def test_prepare_result():
df = _prepare_geocode_result(d)
assert type(df) is GeoDataFrame
assert df.crs == "EPSG:4326"
if HAS_PYPROJ:
assert df.crs == "EPSG:4326"
assert len(df) == 2
assert "address" in df
@@ -93,18 +95,15 @@ def test_prepare_result_none():
df = _prepare_geocode_result(d)
assert type(df) is GeoDataFrame
assert df.crs == "EPSG:4326"
if HAS_PYPROJ:
assert df.crs == "EPSG:4326"
assert len(df) == 2
assert "address" in df
row = df.loc["b"]
# The shapely.geometry.Point() is actually a GeometryCollection, and thus
# gets converted to that in conversion to pygeos. When converting back
# on access, you now get a GeometryCollection object instead of Point,
# which has no coords
# see https://github.com/Toblerity/Shapely/issues/742/#issuecomment-545296708
# TODO we should probably replace this with a missing value instead of point?
# assert len(row["geometry"].coords) == 0
assert len(row["geometry"].coords) == 0
assert row["geometry"].is_empty
assert row["address"] is None

View File

@@ -6,23 +6,17 @@ import tempfile
import numpy as np
import pandas as pd
from pyproj import CRS
from pyproj.exceptions import CRSError
from shapely.geometry import Point, Polygon
from shapely.geometry import Point, Polygon, box
import geopandas
import geopandas._compat as compat
from geopandas import GeoDataFrame, GeoSeries, points_from_xy, read_file
from geopandas.array import GeometryArray, GeometryDtype, from_shapely
from geopandas._compat import ignore_shapely2_warnings
import pytest
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
from geopandas.tests.util import PACKAGE_DIR, validate_boro_df
from pandas.testing import assert_frame_equal, assert_index_equal, assert_series_equal
import pytest
TEST_NEAREST = compat.USE_SHAPELY_20 or (compat.PYGEOS_GE_010 and compat.USE_PYGEOS)
@pytest.fixture
@@ -51,12 +45,13 @@ def how(request):
return request.param
@pytest.mark.usefixtures("_setup_class_nybb_filename")
class TestDataFrame:
def setup_method(self):
N = 10
nybb_filename = geopandas.datasets.get_path("nybb")
self.df = read_file(nybb_filename)
# self.nybb_filename attached via _setup_class_nybb_filename
self.df = read_file(self.nybb_filename)
# TODO re-write instance variables to be fixtures
self.tempdir = tempfile.mkdtemp()
self.crs = "epsg:4326"
self.df2 = GeoDataFrame(
@@ -75,9 +70,13 @@ class TestDataFrame:
def test_df_init(self):
assert type(self.df2) is GeoDataFrame
assert self.df2.crs == self.crs
if compat.HAS_PYPROJ:
assert self.df2.crs == self.crs
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="Requires pyproj")
def test_different_geo_colname(self):
from pyproj.exceptions import CRSError
data = {
"A": range(5),
"B": range(-5, 0),
@@ -198,10 +197,11 @@ class TestDataFrame:
assert_geoseries_equal(df.geometry, new_geom)
assert_geoseries_equal(df["geometry"], new_geom)
# new crs
gs = new_geom.to_crs(crs="epsg:3857")
df.geometry = gs
assert df.crs == "epsg:3857"
if compat.HAS_PYPROJ:
# new crs
gs = new_geom.to_crs(crs="epsg:3857")
df.geometry = gs
assert df.crs == "epsg:3857"
def test_geometry_property_errors(self):
with pytest.raises(AttributeError):
@@ -266,6 +266,10 @@ class TestDataFrame:
with pytest.raises(ValueError):
self.df.set_geometry(self.df)
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="Requires pyproj")
def test_set_geometry_crs(self):
geom = GeoSeries([Point(x, y) for x, y in zip(range(5), range(5))])
# new crs - setting should default to GeoSeries' crs
gs = GeoSeries(geom, crs="epsg:3857")
new_df = self.df.set_geometry(gs)
@@ -292,7 +296,8 @@ class TestDataFrame:
assert_geoseries_equal(df2.geometry, g_simplified)
# If True, drops column and renames to geometry
df3 = self.df.set_geometry("simplified_geometry", drop=True)
with pytest.warns(FutureWarning):
df3 = self.df.set_geometry("simplified_geometry", drop=True)
assert "simplified_geometry" not in df3
assert_geoseries_equal(df3.geometry, g_simplified)
@@ -368,6 +373,35 @@ class TestDataFrame:
with pytest.raises(AttributeError, match=msg_geo_col_missing):
df.geometry
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="Requires pyproj")
def test_override_existing_crs_warning(self):
with pytest.warns(
DeprecationWarning,
match="Overriding the CRS of a GeoSeries that already has CRS",
):
self.df.geometry.crs = "epsg:2100"
with pytest.warns(
DeprecationWarning,
match="Overriding the CRS of a GeoDataFrame that already has CRS",
):
self.df.crs = "epsg:4326"
def test_active_geometry_name(self):
# default single active called "geometry"
assert self.df.active_geometry_name == "geometry"
# one GeoSeries, not active
no_active = GeoDataFrame({"foo": self.df.BoroName, "bar": self.df.geometry})
assert no_active.active_geometry_name is None
assert no_active.set_geometry("bar").active_geometry_name == "bar"
# multiple, none active
multiple = GeoDataFrame({"foo": self.df.geometry, "bar": self.df.geometry})
assert multiple.active_geometry_name is None
assert multiple.set_geometry("foo").active_geometry_name == "foo"
assert multiple.set_geometry("bar").active_geometry_name == "bar"
def test_align(self):
df = self.df2
@@ -379,14 +413,14 @@ class TestDataFrame:
assert_geodataframe_equal(res1, df)
assert_geodataframe_equal(res2, df)
# assert crs is / is not preserved on mixed dataframes
df_nocrs = df.copy()
df_nocrs.crs = None
res1, res2 = df.align(df_nocrs)
assert_geodataframe_equal(res1, df)
assert res1.crs is not None
assert_geodataframe_equal(res2, df_nocrs)
assert res2.crs is None
if compat.HAS_PYPROJ:
# assert crs is / is not preserved on mixed dataframes
df_nocrs = df.copy().set_crs(None, allow_override=True)
res1, res2 = df.align(df_nocrs)
assert_geodataframe_equal(res1, df)
assert res1.crs is not None
assert_geodataframe_equal(res2, df_nocrs)
assert res2.crs is None
# mixed GeoDataFrame / DataFrame
df_nogeom = pd.DataFrame(df.drop("geometry", axis=1))
@@ -407,15 +441,14 @@ class TestDataFrame:
assert_geodataframe_equal(res1, exp1)
assert_geodataframe_equal(res2, exp2)
df2_nocrs = df2.copy()
df2_nocrs.crs = None
exp2_nocrs = exp2.copy()
exp2_nocrs.crs = None
res1, res2 = df1.align(df2_nocrs)
assert_geodataframe_equal(res1, exp1)
assert res1.crs is not None
assert_geodataframe_equal(res2, exp2_nocrs)
assert res2.crs is None
if compat.HAS_PYPROJ:
df2_nocrs = df2.copy().set_crs(None, allow_override=True)
exp2_nocrs = exp2.copy().set_crs(None, allow_override=True)
res1, res2 = df1.align(df2_nocrs)
assert_geodataframe_equal(res1, exp1)
assert res1.crs is not None
assert_geodataframe_equal(res2, exp2_nocrs)
assert res2.crs is None
df2_nogeom = pd.DataFrame(df2.drop("geometry", axis=1))
exp2_nogeom = pd.DataFrame(exp2.drop("geometry", axis=1))
@@ -424,6 +457,7 @@ class TestDataFrame:
assert type(res2) == pd.DataFrame
assert_frame_equal(res2, exp2_nogeom)
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="Requires pyproj")
def test_to_json(self):
text = self.df.to_json(to_wgs84=True)
data = json.loads(text)
@@ -443,7 +477,7 @@ class TestDataFrame:
assert coord == [970217.0223999023, 145643.33221435547]
def test_to_json_no_crs(self):
self.df.crs = None
self.df.geometry.array.crs = None
with pytest.raises(ValueError, match="CRS is not set"):
self.df.to_json(to_wgs84=True)
@@ -586,23 +620,28 @@ class TestDataFrame:
def test_from_dict(self):
data = {"A": [1], "geometry": [Point(0.0, 0.0)]}
df = GeoDataFrame.from_dict(data, crs=3857)
assert df.crs == "epsg:3857"
if compat.HAS_PYPROJ:
assert df.crs == "epsg:3857"
else:
assert df.crs is None
assert df._geometry_column_name == "geometry"
data = {"B": [1], "location": [Point(0.0, 0.0)]}
df = GeoDataFrame.from_dict(data, geometry="location")
assert df._geometry_column_name == "location"
def test_from_features(self):
def test_from_features(self, nybb_filename):
fiona = pytest.importorskip("fiona")
nybb_filename = geopandas.datasets.get_path("nybb")
with fiona.open(nybb_filename) as f:
features = list(f)
crs = f.crs_wkt
df = GeoDataFrame.from_features(features, crs=crs)
validate_boro_df(df, case_sensitive=True)
assert df.crs == crs
if compat.HAS_PYPROJ:
assert df.crs == crs
else:
assert df.crs is None
def test_from_features_unaligned_properties(self):
p1 = Point(1, 1)
@@ -781,7 +820,8 @@ class TestDataFrame:
assert gf.geometry.name == "location"
assert "geometry" not in gf
gf2 = df.set_geometry("location", crs=self.df.crs, drop=True)
with pytest.warns(FutureWarning):
gf2 = df.set_geometry("location", crs=self.df.crs, drop=True)
assert isinstance(df, pd.DataFrame)
assert isinstance(gf2, GeoDataFrame)
assert gf2.geometry.name == "geometry"
@@ -833,40 +873,40 @@ class TestDataFrame:
df.loc[0, "BoroName"] = np.nan
# when containing missing values
# null: output the missing entries as JSON null
result = list(df.iterfeatures(na="null"))[0]["properties"]
result = next(iter(df.iterfeatures(na="null")))["properties"]
assert result["BoroName"] is None
# drop: remove the property from the feature.
result = list(df.iterfeatures(na="drop"))[0]["properties"]
result = next(iter(df.iterfeatures(na="drop")))["properties"]
assert "BoroName" not in result.keys()
# keep: output the missing entries as NaN
result = list(df.iterfeatures(na="keep"))[0]["properties"]
result = next(iter(df.iterfeatures(na="keep")))["properties"]
assert np.isnan(result["BoroName"])
# test for checking that the (non-null) features are python scalars and
# not numpy scalars
assert type(df.loc[0, "Shape_Leng"]) is np.float64
# null
result = list(df.iterfeatures(na="null"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
result = next(iter(df.iterfeatures(na="null")))
assert isinstance(result["properties"]["Shape_Leng"], float)
# drop
result = list(df.iterfeatures(na="drop"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
result = next(iter(df.iterfeatures(na="drop")))
assert isinstance(result["properties"]["Shape_Leng"], float)
# keep
result = list(df.iterfeatures(na="keep"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
result = next(iter(df.iterfeatures(na="keep")))
assert isinstance(result["properties"]["Shape_Leng"], float)
# when only having numerical columns
df_only_numerical_cols = df[["Shape_Leng", "Shape_Area", "geometry"]]
assert type(df_only_numerical_cols.loc[0, "Shape_Leng"]) is np.float64
# null
result = list(df_only_numerical_cols.iterfeatures(na="null"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
result = next(iter(df_only_numerical_cols.iterfeatures(na="null")))
assert isinstance(result["properties"]["Shape_Leng"], float)
# drop
result = list(df_only_numerical_cols.iterfeatures(na="drop"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
result = next(iter(df_only_numerical_cols.iterfeatures(na="drop")))
assert isinstance(result["properties"]["Shape_Leng"], float)
# keep
result = list(df_only_numerical_cols.iterfeatures(na="keep"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
result = next(iter(df_only_numerical_cols.iterfeatures(na="keep")))
assert isinstance(result["properties"]["Shape_Leng"], float)
with pytest.raises(
ValueError, match="GeoDataFrame cannot contain duplicated column names."
@@ -888,25 +928,25 @@ class TestDataFrame:
)
# null
expected = {"non-scalar": [1, 2], "test_col": None}
result = list(df.iterfeatures(na="null"))[0].get("properties")
result = next(iter(df.iterfeatures(na="null"))).get("properties")
assert expected == result
# drop
expected = {"non-scalar": [1, 2]}
result = list(df.iterfeatures(na="drop"))[0].get("properties")
result = next(iter(df.iterfeatures(na="drop"))).get("properties")
assert expected == result
# keep
expected = {"non-scalar": [1, 2], "test_col": None}
result = list(df.iterfeatures(na="keep"))[0].get("properties")
result = next(iter(df.iterfeatures(na="keep"))).get("properties")
assert expected == result
def test_geodataframe_geojson_no_bbox(self):
geo = self.df._to_geo(na="null", show_bbox=False)
geo = self.df.to_geo_dict(na="null", show_bbox=False)
assert "bbox" not in geo.keys()
for feature in geo["features"]:
assert "bbox" not in feature.keys()
def test_geodataframe_geojson_bbox(self):
geo = self.df._to_geo(na="null", show_bbox=True)
geo = self.df.to_geo_dict(na="null", show_bbox=True)
assert "bbox" in geo.keys()
assert len(geo["bbox"]) == 4
assert isinstance(geo["bbox"], tuple)
@@ -927,29 +967,31 @@ class TestDataFrame:
assert self.df.crs == unpickled.crs
def test_estimate_utm_crs(self):
assert self.df.estimate_utm_crs() == CRS("EPSG:32618")
assert self.df.estimate_utm_crs("NAD83") == CRS("EPSG:26918")
pyproj = pytest.importorskip("pyproj")
assert self.df.estimate_utm_crs() == pyproj.CRS("EPSG:32618")
assert self.df.estimate_utm_crs("NAD83") == pyproj.CRS("EPSG:26918")
def test_to_wkb(self):
wkbs0 = [
(
( # POINT (0 0)
b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
), # POINT (0 0)
(
),
( # POINT (1 1)
b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?"
), # POINT (1 1)
),
]
wkbs1 = [
(
( # POINT (2 2)
b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00@\x00\x00\x00\x00\x00\x00\x00@"
), # POINT (2 2)
(
),
( # POINT (3 3)
b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x08@\x00\x00\x00\x00\x00\x00\x08@"
), # POINT (3 3)
),
]
gs0 = GeoSeries.from_wkb(wkbs0)
gs1 = GeoSeries.from_wkb(wkbs1)
@@ -970,40 +1012,51 @@ class TestDataFrame:
@pytest.mark.parametrize("how", ["left", "inner", "right"])
@pytest.mark.parametrize("predicate", ["intersects", "within", "contains"])
@pytest.mark.skipif(
not (compat.USE_PYGEOS or compat.USE_SHAPELY_20 or compat.HAS_RTREE),
reason="sjoin needs `rtree` or `pygeos` dependency",
)
def test_sjoin(self, how, predicate):
def test_sjoin(self, how, predicate, naturalearth_cities, naturalearth_lowres):
"""
Basic test for availability of the GeoDataFrame method. Other
sjoin tests are located in /tools/tests/test_sjoin.py
"""
left = read_file(geopandas.datasets.get_path("naturalearth_cities"))
right = read_file(geopandas.datasets.get_path("naturalearth_lowres"))
left = read_file(naturalearth_cities)
right = read_file(naturalearth_lowres)
expected = geopandas.sjoin(left, right, how=how, predicate=predicate)
result = left.sjoin(right, how=how, predicate=predicate)
assert_geodataframe_equal(result, expected)
@pytest.mark.parametrize("how", ["left", "inner", "right"])
@pytest.mark.parametrize("distance", [0, 3])
@pytest.mark.skipif(
not compat.GEOS_GE_310,
reason="`dwithin` requires GEOS 3.10",
)
def test_sjoin_dwithin(self, how, distance):
"""
Basic test for predicate='dwithin' availability of the GeoDataFrame method.
Other sjoin tests are located in /tools/tests/test_sjoin.py
"""
left = GeoDataFrame(geometry=points_from_xy([0, 1, 2], [0, 1, 1]))
right = GeoDataFrame(geometry=[box(0, 0, 1, 1)])
expected = geopandas.sjoin(
left, right, how=how, predicate="dwithin", distance=distance
)
result = left.sjoin(right, how=how, predicate="dwithin", distance=distance)
assert_geodataframe_equal(result, expected)
@pytest.mark.parametrize("how", ["left", "inner", "right"])
@pytest.mark.parametrize("max_distance", [None, 1])
@pytest.mark.parametrize("distance_col", [None, "distance"])
@pytest.mark.skipif(
not TEST_NEAREST,
reason=(
"PyGEOS >= 0.10.0"
" must be installed and activated via the geopandas.compat module to"
" test sjoin_nearest"
),
)
def test_sjoin_nearest(self, how, max_distance, distance_col):
@pytest.mark.filterwarnings("ignore:Geometry is in a geographic CRS:UserWarning")
def test_sjoin_nearest(
self, how, max_distance, distance_col, naturalearth_cities, naturalearth_lowres
):
"""
Basic test for availability of the GeoDataFrame method. Other
sjoin tests are located in /tools/tests/test_sjoin.py
"""
left = read_file(geopandas.datasets.get_path("naturalearth_cities"))
right = read_file(geopandas.datasets.get_path("naturalearth_lowres"))
left = read_file(naturalearth_cities)
right = read_file(naturalearth_lowres)
expected = geopandas.sjoin_nearest(
left, right, how=how, max_distance=max_distance, distance_col=distance_col
@@ -1013,21 +1066,42 @@ class TestDataFrame:
)
assert_geodataframe_equal(result, expected)
@pytest.mark.skip_no_sindex
def test_clip(self):
def test_clip(self, naturalearth_cities, naturalearth_lowres):
"""
Basic test for availability of the GeoDataFrame method. Other
clip tests are located in /tools/tests/test_clip.py
"""
left = read_file(geopandas.datasets.get_path("naturalearth_cities"))
world = read_file(geopandas.datasets.get_path("naturalearth_lowres"))
left = read_file(naturalearth_cities)
world = read_file(naturalearth_lowres)
south_america = world[world["continent"] == "South America"]
expected = geopandas.clip(left, south_america)
result = left.clip(south_america)
assert_geodataframe_equal(result, expected)
@pytest.mark.skip_no_sindex
def test_clip_sorting(self, naturalearth_cities, naturalearth_lowres):
"""
Test sorting of geodataframe when clipping.
"""
cities = read_file(naturalearth_cities)
world = read_file(naturalearth_lowres)
south_america = world[world["continent"] == "South America"]
unsorted_clipped_cities = geopandas.clip(cities, south_america, sort=False)
sorted_clipped_cities = geopandas.clip(cities, south_america, sort=True)
expected_sorted_index = pd.Index(
[55, 59, 62, 88, 101, 114, 122, 169, 181, 189, 210, 230, 236, 238, 239]
)
assert not (
sorted(unsorted_clipped_cities.index) == unsorted_clipped_cities.index
).all()
assert (
sorted(sorted_clipped_cities.index) == sorted_clipped_cities.index
).all()
assert_index_equal(expected_sorted_index, sorted_clipped_cities.index)
def test_overlay(self, dfs, how):
"""
Basic test for availability of the GeoDataFrame method. Other
@@ -1138,8 +1212,7 @@ class TestConstructor:
"B": np.arange(3.0),
"geometry": [Point(x, x) for x in range(3)],
}
with ignore_shapely2_warnings():
a = np.array([data["A"], data["B"], data["geometry"]], dtype=object).T
a = np.array([data["A"], data["B"], data["geometry"]], dtype=object).T
df = GeoDataFrame(a, columns=["A", "B", "geometry"])
check_geodataframe(df)
@@ -1154,8 +1227,7 @@ class TestConstructor:
"geometry": [Point(x, x) for x in range(3)],
}
gpdf = GeoDataFrame(data)
with ignore_shapely2_warnings():
pddf = pd.DataFrame(data)
pddf = pd.DataFrame(data)
check_geodataframe(gpdf)
assert type(pddf) == pd.DataFrame
@@ -1184,8 +1256,7 @@ class TestConstructor:
gpdf = GeoDataFrame(data, geometry="other_geom")
check_geodataframe(gpdf, "other_geom")
with ignore_shapely2_warnings():
pddf = pd.DataFrame(data)
pddf = pd.DataFrame(data)
for df in [gpdf, pddf]:
res = GeoDataFrame(df, geometry="other_geom")
@@ -1240,7 +1311,7 @@ class TestConstructor:
geometry="geometry",
)
check_geodataframe(gdf)
gdf.columns == ["geometry", "a"]
assert list(gdf.columns) == ["geometry", "a"]
# with non-default index
gdf = GeoDataFrame(
@@ -1250,27 +1321,24 @@ class TestConstructor:
geometry="geometry",
)
check_geodataframe(gdf)
gdf.columns == ["geometry", "a"]
assert list(gdf.columns) == ["geometry", "a"]
@pytest.mark.xfail
def test_preserve_series_name(self):
def test_do_not_preserve_series_name_in_constructor(self):
# GH3337
# GeoDataFrame(... geometry=...) should always create geom col "geometry"
geoms = [Point(1, 1), Point(2, 2), Point(3, 3)]
gs = GeoSeries(geoms)
gdf = GeoDataFrame({"a": [1, 2, 3]}, geometry=gs)
check_geodataframe(gdf, geometry_column="geometry")
geoms = [Point(1, 1), Point(2, 2), Point(3, 3)]
# still get "geometry", even with custom geoseries name
gs = GeoSeries(geoms, name="my_geom")
gdf = GeoDataFrame({"a": [1, 2, 3]}, geometry=gs)
check_geodataframe(gdf, geometry_column="my_geom")
check_geodataframe(gdf, geometry_column="geometry")
def test_overwrite_geometry(self):
# GH602
data = pd.DataFrame({"geometry": [1, 2, 3], "col1": [4, 5, 6]})
with ignore_shapely2_warnings():
geoms = pd.Series([Point(i, i) for i in range(3)])
geoms = pd.Series([Point(i, i) for i in range(3)])
# passed geometry kwarg should overwrite geometry column in data
res = GeoDataFrame(data, geometry=geoms)
assert_geoseries_equal(res.geometry, GeoSeries(geoms))
@@ -1329,7 +1397,8 @@ class TestConstructor:
):
gdf5["geometry"] = "foo"
assert gdf5._geometry_column_name is None
gdf3 = gdf.copy().assign(geometry=geo_col)
with pytest.warns(FutureWarning, match=match):
gdf3 = gdf.copy().assign(geometry=geo_col)
assert gdf3._geometry_column_name == "geometry"
# Check that adding a GeoSeries to a column called "geometry" to a
@@ -1355,8 +1424,9 @@ class TestConstructor:
y_col = df["location", "y"]
gdf = GeoDataFrame(df, crs=crs, geometry=points_from_xy(x_col, y_col))
assert gdf.crs == crs
assert gdf.geometry.crs == crs
if compat.HAS_PYPROJ:
assert gdf.crs == crs
assert gdf.geometry.crs == crs
assert gdf.geometry.dtype == "geometry"
assert gdf._geometry_column_name == "geometry"
assert gdf.geometry.name == "geometry"
@@ -1378,8 +1448,9 @@ class TestConstructor:
y_col = df["foo", "location", "y"]
gdf = GeoDataFrame(df, crs=crs, geometry=points_from_xy(x_col, y_col))
assert gdf.crs == crs
assert gdf.geometry.crs == crs
if compat.HAS_PYPROJ:
assert gdf.crs == crs
assert gdf.geometry.crs == crs
assert gdf.geometry.dtype == "geometry"
assert gdf._geometry_column_name == "geometry"
assert gdf.geometry.name == "geometry"
@@ -1400,17 +1471,18 @@ class TestConstructor:
df["geometry"] = GeoSeries.from_xy(x_col, y_col)
df2 = df.copy()
gdf = df.set_geometry("geometry", crs=crs)
assert gdf.crs == crs
if compat.HAS_PYPROJ:
assert gdf.crs == crs
assert gdf._geometry_column_name == "geometry"
assert gdf.geometry.name == "geometry"
# test again setting with tuple col name
gdf = df2.set_geometry(("geometry", "", ""), crs=crs)
assert gdf.crs == crs
if compat.HAS_PYPROJ:
assert gdf.crs == crs
assert gdf._geometry_column_name == ("geometry", "", "")
assert gdf.geometry.name == ("geometry", "", "")
def test_assign_cols_using_index(self):
nybb_filename = geopandas.datasets.get_path("nybb")
def test_assign_cols_using_index(self, nybb_filename):
df = read_file(nybb_filename)
other_df = pd.DataFrame({"foo": range(5), "bar": range(5)})
expected = pd.concat([df, other_df], axis=1)
@@ -1418,6 +1490,7 @@ class TestConstructor:
assert_geodataframe_equal(df, expected)
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_geodataframe_crs():
gdf = GeoDataFrame(columns=["geometry"])
gdf.crs = "IGNF:ETRS89UTM28"
@@ -1436,6 +1509,7 @@ def test_geodataframe_nocrs_json():
assert "crs" not in gdf_geojson
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_geodataframe_crs_json():
gdf = GeoDataFrame(columns=["geometry"])
gdf.crs = 25833
@@ -1449,6 +1523,7 @@ def test_geodataframe_crs_json():
assert "crs" not in gdf_geointerface
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
@pytest.mark.parametrize(
"crs",
["+proj=cea +lon_0=0 +lat_ts=45 +x_0=0 +y_0=0 +ellps=WGS84 +units=m", "IGNF:WGS84"],
@@ -1472,3 +1547,71 @@ def test_geodataframe_crs_colname():
assert gdf.crs is None
assert gdf["crs"].iloc[0] == 1
assert getattr(gdf, "crs") is None
@pytest.mark.parametrize("geo_col_name", ["geometry", "polygons"])
def test_set_geometry_supply_colname(dfs, geo_col_name):
df, _ = dfs
if geo_col_name != "geometry":
df = df.rename_geometry(geo_col_name)
df["centroid"] = df.geometry.centroid
res = df.set_geometry("centroid")
assert res.active_geometry_name == "centroid"
assert geo_col_name in res.columns
# Test that drop=False explicitly warns
deprecated = "The `drop` keyword argument is deprecated"
with pytest.warns(FutureWarning, match=deprecated):
res2 = df.set_geometry("centroid", drop=False)
assert_geodataframe_equal(res, res2)
with pytest.warns(FutureWarning, match=deprecated):
res3 = df.set_geometry("centroid", drop=True)
# drop=True should preserve previous geometry col name (keep old behaviour)
assert res3.active_geometry_name == geo_col_name
assert "centroid" not in res3.columns
# Test that alternative suggested without using drop=True is equivalent
assert_geodataframe_equal(
res3,
df.set_geometry("centroid")
.drop(columns=geo_col_name)
.rename_geometry(geo_col_name),
)
@pytest.mark.parametrize("geo_col_name", ["geometry", "polygons"])
def test_set_geometry_supply_arraylike(dfs, geo_col_name):
df, _ = dfs
if geo_col_name != "geometry":
df = df.rename_geometry(geo_col_name)
centroids = df.geometry.centroid
res = df.set_geometry(centroids)
assert res.active_geometry_name == geo_col_name
# drop should do nothing if the column already exists
match_str = (
"The `drop` keyword argument is deprecated and has no effect when "
"`col` is an array-like value"
)
with pytest.warns(
FutureWarning,
match=match_str,
):
res2 = df.set_geometry(centroids, drop=True)
assert res2.active_geometry_name == geo_col_name
centroids = centroids.rename("centroids")
res3 = df.set_geometry(centroids)
# Should preserve the geoseries name
# (and old geometry column should be kept)
assert res3.active_geometry_name == "centroids"
assert geo_col_name in res3.columns
# Drop should not remove previous active geometry colname for arraylike inputs
with pytest.warns(
FutureWarning,
match=match_str,
):
res4 = df.set_geometry(centroids, drop=True)
assert res4.active_geometry_name == "centroids"
assert geo_col_name in res4.columns

View File

@@ -1,17 +1,13 @@
import json
import os
import random
import re
import shutil
import tempfile
import warnings
import numpy as np
from numpy.testing import assert_array_equal
import pandas as pd
from pandas.testing import assert_index_equal
from pyproj import CRS
from shapely.geometry import (
GeometryCollection,
LineString,
@@ -23,14 +19,15 @@ from shapely.geometry import (
)
from shapely.geometry.base import BaseGeometry
from geopandas import GeoSeries, GeoDataFrame, read_file, datasets, clip
from geopandas._compat import ignore_shapely2_warnings
import geopandas._compat as compat
from geopandas import GeoDataFrame, GeoSeries, clip, read_file
from geopandas.array import GeometryArray, GeometryDtype
from geopandas.testing import assert_geoseries_equal, geom_almost_equals
from geopandas.tests.util import geom_equals
from pandas.testing import assert_series_equal
import pytest
from geopandas.testing import assert_geoseries_equal, geom_almost_equals
from geopandas.tests.util import geom_equals
from numpy.testing import assert_array_equal
from pandas.testing import assert_index_equal, assert_series_equal
class TestSeries:
@@ -41,8 +38,7 @@ class TestSeries:
self.sq = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
self.g1 = GeoSeries([self.t1, self.sq])
self.g2 = GeoSeries([self.sq, self.t1])
self.g3 = GeoSeries([self.t1, self.t2])
self.g3.crs = "epsg:4326"
self.g3 = GeoSeries([self.t1, self.t2], crs="epsg:4326")
self.g4 = GeoSeries([self.t2, self.t1])
self.na = GeoSeries([self.t1, self.t2, Polygon()])
self.na_none = GeoSeries([self.t1, self.t2, None])
@@ -56,6 +52,9 @@ class TestSeries:
self.l1 = LineString([(0, 0), (0, 1), (1, 1)])
self.l2 = LineString([(0, 0), (1, 0), (1, 1), (0, 1)])
self.g5 = GeoSeries([self.l1, self.l2])
self.esb3857 = Point(-8235939.130493107, 4975301.253789809)
self.sol3857 = Point(-8242607.167991625, 4966620.938285081)
self.landmarks3857 = GeoSeries([self.esb3857, self.sol3857], crs="epsg:3857")
def teardown_method(self):
shutil.rmtree(self.tempdir)
@@ -82,18 +81,16 @@ class TestSeries:
assert a1["B"].equals(a2["B"])
assert a1["C"] is None
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_align_crs(self):
a1 = self.a1
a1.crs = "epsg:4326"
a2 = self.a2
a2.crs = "epsg:31370"
a1 = self.a1.set_crs("epsg:4326")
a2 = self.a2.set_crs("epsg:31370")
res1, res2 = a1.align(a2)
assert res1.crs == "epsg:4326"
assert res2.crs == "epsg:31370"
a2.crs = None
res1, res2 = a1.align(a2)
res1, res2 = a1.align(a2.set_crs(None, allow_override=True))
assert res1.crs == "epsg:4326"
assert res2.crs is None
@@ -110,11 +107,11 @@ class TestSeries:
# Test that warning is issued when operating on non-aligned series
# _series_op
with pytest.warns(UserWarning, match="The indices .+ different"):
with pytest.warns(UserWarning, match="The indices .+ not equal"):
self.a1.contains(self.a2)
# _geo_op
with pytest.warns(UserWarning, match="The indices .+ different"):
with pytest.warns(UserWarning, match="The indices .+ not equal"):
self.a1.union(self.a2)
def test_no_warning_if_aligned(self):
@@ -136,8 +133,7 @@ class TestSeries:
assert_array_equal(self.g1.geom_equals(self.sq), [False, True])
def test_geom_equals_align(self):
with pytest.warns(UserWarning, match="The indices .+ different"):
a = self.a1.geom_equals(self.a2, align=True)
a = self.a1.geom_equals(self.a2, align=True)
exp = pd.Series([False, True, False], index=["A", "B", "C"])
assert_series_equal(a, exp)
@@ -145,27 +141,39 @@ class TestSeries:
exp = pd.Series([False, False], index=["A", "B"])
assert_series_equal(a, exp)
@pytest.mark.filterwarnings(r"ignore:The 'geom_almost_equals\(\)':FutureWarning")
def test_geom_almost_equals(self):
# TODO: test decimal parameter
with pytest.warns(FutureWarning, match=re.escape("The 'geom_almost_equals()'")):
assert np.all(self.g1.geom_almost_equals(self.g1))
assert_array_equal(self.g1.geom_almost_equals(self.sq), [False, True])
assert_array_equal(
self.a1.geom_almost_equals(self.a2, align=True), [False, True, False]
assert np.all(self.g1.geom_almost_equals(self.g1))
assert_array_equal(self.g1.geom_almost_equals(self.sq), [False, True])
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
"The indices of the left and right GeoSeries' are not equal",
UserWarning,
)
assert_array_equal(
self.a1.geom_almost_equals(self.a2, align=False), [False, False]
self.a1.geom_almost_equals(self.a2, align=True),
[False, True, False],
)
assert_array_equal(
self.a1.geom_almost_equals(self.a2, align=False), [False, False]
)
def test_geom_equals_exact(self):
# TODO: test tolerance parameter
assert np.all(self.g1.geom_equals_exact(self.g1, 0.001))
assert_array_equal(self.g1.geom_equals_exact(self.sq, 0.001), [False, True])
assert_array_equal(
self.a1.geom_equals_exact(self.a2, 0.001, align=True), [False, True, False]
)
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
"The indices of the left and right GeoSeries' are not equal",
UserWarning,
)
assert_array_equal(
self.a1.geom_equals_exact(self.a2, 0.001, align=True),
[False, True, False],
)
assert_array_equal(
self.a1.geom_equals_exact(self.a2, 0.001, align=False), [False, False]
)
@@ -190,15 +198,69 @@ class TestSeries:
Test whether GeoSeries.to_json works and returns an actual json file.
"""
json_str = self.g3.to_json()
json.loads(json_str)
data = json.loads(json_str)
assert "id" in data["features"][0].keys()
assert "bbox" in data["features"][0].keys()
# TODO : verify the output is a valid GeoJSON.
def test_to_json_drop_id(self):
"""
Test whether GeoSeries.to_json works when drop_id is True.
"""
json_str = self.g3.to_json(drop_id=True)
data = json.loads(json_str)
assert "id" not in data["features"][0].keys()
def test_to_json_no_bbox(self):
"""
Test whether GeoSeries.to_json works when show_bbox is False.
"""
json_str = self.g3.to_json(show_bbox=False)
data = json.loads(json_str)
assert "bbox" not in data["features"][0].keys()
def test_to_json_no_bbox_drop_id(self):
"""
Test whether GeoSeries.to_json works when show_bbox is False
and drop_id is True.
"""
json_str = self.g3.to_json(show_bbox=False, drop_id=True)
data = json.loads(json_str)
assert "id" not in data["features"][0].keys()
assert "bbox" not in data["features"][0].keys()
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="Requires pyproj")
def test_to_json_wgs84(self):
"""
Test whether the wgs84 conversion works as intended.
"""
text = self.landmarks3857.to_json(to_wgs84=True)
data = json.loads(text)
assert data["type"] == "FeatureCollection"
assert "id" in data["features"][0].keys()
coord1 = data["features"][0]["geometry"]["coordinates"]
coord2 = data["features"][1]["geometry"]["coordinates"]
np.testing.assert_allclose(coord1, self.esb.coords[0])
np.testing.assert_allclose(coord2, self.sol.coords[0])
def test_to_json_wgs84_false(self):
"""
Ensure no conversion to wgs84
"""
text = self.landmarks3857.to_json()
data = json.loads(text)
coord1 = data["features"][0]["geometry"]["coordinates"]
coord2 = data["features"][1]["geometry"]["coordinates"]
assert coord1 == [-8235939.130493107, 4975301.253789809]
assert coord2 == [-8242607.167991625, 4966620.938285081]
def test_representative_point(self):
assert np.all(self.g1.contains(self.g1.representative_point()))
assert np.all(self.g2.contains(self.g2.representative_point()))
assert np.all(self.g3.contains(self.g3.representative_point()))
assert np.all(self.g4.contains(self.g4.representative_point()))
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_transform(self):
utm18n = self.landmarks.to_crs(epsg=26918)
lonlat = utm18n.to_crs(epsg=4326)
@@ -209,20 +271,24 @@ class TestSeries:
self.landmarks.to_crs(crs=None, epsg=None)
def test_estimate_utm_crs__geographic(self):
assert self.landmarks.estimate_utm_crs() == CRS("EPSG:32618")
assert self.landmarks.estimate_utm_crs("NAD83") == CRS("EPSG:26918")
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):
assert self.landmarks.to_crs("EPSG:3857").estimate_utm_crs() == CRS(
pyproj = pytest.importorskip("pyproj")
assert self.landmarks.to_crs("EPSG:3857").estimate_utm_crs() == pyproj.CRS(
"EPSG:32618"
)
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_estimate_utm_crs__out_of_bounds(self):
with pytest.raises(RuntimeError, match="Unable to determine UTM CRS"):
GeoSeries(
[Polygon([(0, 90), (1, 90), (2, 90)])], crs="EPSG:4326"
).estimate_utm_crs()
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_estimate_utm_crs__missing_crs(self):
with pytest.raises(RuntimeError, match="crs must be set"):
GeoSeries([Polygon([(0, 90), (1, 90), (2, 90)])]).estimate_utm_crs()
@@ -258,6 +324,7 @@ class TestSeries:
assert self.g1.__geo_interface__["type"] == "FeatureCollection"
assert len(self.g1.__geo_interface__["features"]) == self.g1.shape[0]
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_proj4strings(self):
# As string
reprojected = self.g3.to_crs("+proj=utm +zone=30")
@@ -270,8 +337,7 @@ class TestSeries:
assert geom_almost_equals(self.g3, reprojected_back)
# Set to equivalent string, convert, compare to original
copy = self.g3.copy()
copy.crs = "epsg:4326"
copy = self.g3.copy().set_crs("epsg:4326", allow_override=True)
reprojected = copy.to_crs({"proj": "utm", "zone": "30"})
reprojected_back = reprojected.to_crs(epsg=4326)
assert geom_almost_equals(self.g3, reprojected_back)
@@ -284,6 +350,23 @@ class TestSeries:
def test_from_wkb(self):
assert_geoseries_equal(self.g1, GeoSeries.from_wkb([self.t1.wkb, self.sq.wkb]))
def test_from_wkb_on_invalid(self):
# 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):
GeoSeries.from_wkb([invalid_wkb_hex], on_invalid="raise")
with pytest.warns(Warning, match=message):
res = GeoSeries.from_wkb([invalid_wkb_hex], on_invalid="warn")
assert res[0] is None
with warnings.catch_warnings():
warnings.simplefilter("error")
res = GeoSeries.from_wkb([invalid_wkb_hex], on_invalid="ignore")
assert res[0] is None
def test_from_wkb_series(self):
s = pd.Series([self.t1.wkb, self.sq.wkb], index=[1, 2])
expected = self.g1.copy()
@@ -299,6 +382,23 @@ class TestSeries:
def test_from_wkt(self):
assert_geoseries_equal(self.g1, GeoSeries.from_wkt([self.t1.wkt, self.sq.wkt]))
def test_from_wkt_on_invalid(self):
# 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):
GeoSeries.from_wkt([invalid_wkt], on_invalid="raise")
with pytest.warns(Warning, match=message):
res = GeoSeries.from_wkt([invalid_wkt], on_invalid="warn")
assert res[0] is None
with warnings.catch_warnings():
warnings.simplefilter("error")
res = GeoSeries.from_wkt([invalid_wkt], on_invalid="ignore")
assert res[0] is None
def test_from_wkt_series(self):
s = pd.Series([self.t1.wkt, self.sq.wkt], index=[1, 2])
expected = self.g1.copy()
@@ -320,16 +420,38 @@ class TestSeries:
def test_to_wkt(self):
assert_series_equal(pd.Series([self.t1.wkt, self.sq.wkt]), self.g1.to_wkt())
@pytest.mark.skip_no_sindex
def test_clip(self):
left = read_file(datasets.get_path("naturalearth_cities"))
world = read_file(datasets.get_path("naturalearth_lowres"))
def test_clip(self, naturalearth_lowres, naturalearth_cities):
left = read_file(naturalearth_cities)
world = read_file(naturalearth_lowres)
south_america = world[world["continent"] == "South America"]
expected = clip(left.geometry, south_america)
result = left.geometry.clip(south_america)
assert_geoseries_equal(result, expected)
def test_clip_sorting(self, naturalearth_cities, naturalearth_lowres):
"""
Test sorting of geodseries when clipping.
"""
cities = read_file(naturalearth_cities)
world = read_file(naturalearth_lowres)
south_america = world[world["continent"] == "South America"]
unsorted_clipped_cities = clip(cities, south_america, sort=False)
sorted_clipped_cities = clip(cities, south_america, sort=True)
expected_sorted_index = pd.Index(
[55, 59, 62, 88, 101, 114, 122, 169, 181, 189, 210, 230, 236, 238, 239]
)
assert not (
sorted(unsorted_clipped_cities.index) == unsorted_clipped_cities.index
).all()
assert (
sorted(sorted_clipped_cities.index) == sorted_clipped_cities.index
).all()
assert_index_equal(expected_sorted_index, sorted_clipped_cities.index)
def test_from_xy_points(self):
x = self.landmarks.x.values
y = self.landmarks.y.values
@@ -375,6 +497,13 @@ class TestSeries:
expected = GeoSeries([Point(0, 2, -1), Point(3, 5, 4)])
assert_geoseries_equal(expected, GeoSeries.from_xy(x, y, z))
@pytest.mark.skipif(compat.HAS_PYPROJ, reason="pyproj installed")
def test_set_crs_pyproj_error(self):
with pytest.raises(
ImportError, match="The 'pyproj' package is required for set_crs"
):
self.g1.set_crs(3857)
@pytest.mark.filterwarnings("ignore::UserWarning")
def test_missing_values():
@@ -406,12 +535,22 @@ def test_isna_empty_geoseries():
assert_series_equal(result, pd.Series([], dtype="bool"))
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_geoseries_crs():
gs = GeoSeries()
gs.crs = "IGNF:ETRS89UTM28"
gs = GeoSeries().set_crs("IGNF:ETRS89UTM28")
assert gs.crs.to_authority() == ("IGNF", "ETRS89UTM28")
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="Requires pyproj")
def test_geoseries_override_existing_crs_warning():
gs = GeoSeries(crs="epsg:4326")
with pytest.warns(
DeprecationWarning,
match="Overriding the CRS of a GeoSeries that already has CRS",
):
gs.crs = "epsg:2100"
# -----------------------------------------------------------------------------
# # Constructor tests
# -----------------------------------------------------------------------------
@@ -513,10 +652,8 @@ class TestConstructor:
Polygon([(random.random(), random.random()) for _ in range(3)])
for _ in range(10)
]
with ignore_shapely2_warnings():
# the warning here is not suppressed by GeoPandas, as this is a pure
# pandas construction call
s = pd.Series(shapes, index=list("abcdefghij"), name="foo")
s = pd.Series(shapes, index=list("abcdefghij"), name="foo")
g = GeoSeries(s)
check_geoseries(g)
@@ -524,6 +661,37 @@ class TestConstructor:
assert s.name == g.name
assert s.index is g.index
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
def test_from_series_no_set_crs_on_construction(self):
# https://github.com/geopandas/geopandas/issues/2492
# also when passing Series[geometry], ensure we don't change crs of
# original data
gs = GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)])
s = pd.Series(gs)
result = GeoSeries(s, crs=4326)
assert s.values.crs is None
assert gs.crs is None
assert result.crs == "EPSG:4326"
def test_copy(self):
# default is to copy with CoW / pandas 3+
arr = np.array([Point(x, x) for x in range(3)], dtype=object)
result = GeoSeries(arr)
# modifying result doesn't change original array
result.loc[0] = Point(10, 10)
if compat.PANDAS_GE_30 or getattr(pd.options.mode, "copy_on_write", False):
assert arr[0] == Point(0, 0)
else:
assert arr[0] == Point(10, 10)
# avoid copy with copy=False
arr = np.array([Point(x, x) for x in range(3)], dtype=object)
result = GeoSeries(arr, copy=False)
assert result.array._data.flags.writeable
# now modifying result also updates original array
result.loc[0] = Point(10, 10)
assert arr[0] == Point(10, 10)
# GH 1216
@pytest.mark.parametrize("name", [None, "geometry", "Points"])
@pytest.mark.parametrize("crs", [None, "epsg:4326"])

View File

@@ -1,13 +1,15 @@
import warnings
import pandas as pd
import pytest
from geopandas.testing import assert_geodataframe_equal
from pandas.testing import assert_index_equal
from shapely.geometry import Point
from geopandas import GeoDataFrame, GeoSeries
from geopandas._compat import HAS_PYPROJ, PANDAS_GE_21
import pytest
from geopandas.testing import assert_geodataframe_equal
from pandas.testing import assert_index_equal
class TestMerging:
@@ -59,6 +61,7 @@ class TestMerging:
assert isinstance(res, GeoSeries)
assert isinstance(res.geometry, GeoSeries)
@pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not available")
def test_concat_axis0_crs(self):
# CRS not set for both GeoDataFrame
res = pd.concat([self.gdf, self.gdf])
@@ -100,6 +103,7 @@ class TestMerging:
[self.gdf, self.gdf.set_crs("epsg:4326"), self.gdf.set_crs("epsg:4327")]
)
@pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not available")
def test_concat_axis0_unaligned_cols(self):
# https://github.com/geopandas/geopandas/issues/2679
gdf = self.gdf.set_crs("epsg:4326").assign(
@@ -133,6 +137,40 @@ class TestMerging:
partial_none_case.iloc[0] = None
pd.concat([single_geom_col, partial_none_case])
def test_concat_axis0_crs_wkt_mismatch(self):
pyproj = pytest.importorskip("pyproj")
# https://github.com/geopandas/geopandas/issues/326#issuecomment-1727958475
wkt_template = """GEOGCRS["WGS 84",
ENSEMBLE["World Geodetic System 1984 ensemble",
MEMBER["World Geodetic System 1984 (Transit)"],
MEMBER["World Geodetic System 1984 (G730)"],
MEMBER["World Geodetic System 1984 (G873)"],
MEMBER["World Geodetic System 1984 (G1150)"],
MEMBER["World Geodetic System 1984 (G1674)"],
MEMBER["World Geodetic System 1984 (G1762)"],
MEMBER["World Geodetic System 1984 (G2139)"],
ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],
ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],
AXIS["geodetic latitude (Lat)",north,ORDER[1],
ANGLEUNIT["degree",0.0174532925199433]],
AXIS["geodetic longitude (Lon)",east,ORDER[2],
ANGLEUNIT["degree",0.0174532925199433]],
USAGE[SCOPE["Horizontal component of 3D system."],
AREA["World.{}"],BBOX[-90,-180,90,180]],ID["EPSG",4326]]"""
wkt_v1 = wkt_template.format("")
wkt_v2 = wkt_template.format(" ") # add additional whitespace
crs1 = pyproj.CRS.from_wkt(wkt_v1)
crs2 = pyproj.CRS.from_wkt(wkt_v2)
# pyproj crs __hash__ based on WKT strings means these are distinct in a
# set are but equal by equality
assert len({crs1, crs2}) == 2
assert crs1 == crs2
expected = pd.concat([self.gdf, self.gdf]).set_crs(crs1)
res = pd.concat([self.gdf.set_crs(crs1), self.gdf.set_crs(crs2)])
assert_geodataframe_equal(expected, res)
def test_concat_axis1(self):
res = pd.concat([self.gdf, self.df], axis=1)
@@ -145,10 +183,18 @@ class TestMerging:
# https://github.com/geopandas/geopandas/issues/1230
# Expect that concat should fail gracefully if duplicate column names belonging
# to geometry columns are introduced.
expected_err = (
"GeoDataFrame does not support multiple columns using the geometry"
" column name 'geometry'"
)
if PANDAS_GE_21:
# _constructor_from_mgr changes mean we now get the concat specific error
# message in this case too
expected_err = (
"Concat operation has resulted in multiple columns using the geometry "
"column name 'geometry'."
)
else:
expected_err = (
"GeoDataFrame does not support multiple columns using the geometry"
" column name 'geometry'"
)
with pytest.raises(ValueError, match=expected_err):
pd.concat([self.gdf, self.gdf], axis=1)
@@ -161,10 +207,11 @@ class TestMerging:
with pytest.raises(ValueError, match=expected_err2):
pd.concat([df2, df2], axis=1)
# Check that two geometry columns is fine, if they have different names
res3 = pd.concat([df2.set_crs("epsg:4326"), self.gdf], axis=1)
# check metadata comes from first df
self._check_metadata(res3, geometry_column_name="geom", crs="epsg:4326")
if HAS_PYPROJ:
# Check that two geometry columns is fine, if they have different names
res3 = pd.concat([df2.set_crs("epsg:4326"), self.gdf], axis=1)
# check metadata comes from first df
self._check_metadata(res3, geometry_column_name="geom", crs="epsg:4326")
@pytest.mark.filterwarnings("ignore:Accessing CRS")
def test_concat_axis1_geoseries(self):

View File

@@ -1,12 +1,15 @@
import numpy as np
import pandas as pd
import pyproj
import pytest
from shapely.geometry import Point
import numpy as np
import geopandas
from geopandas import GeoDataFrame, GeoSeries
import pytest
from geopandas.testing import assert_geodataframe_equal
pyproj = pytest.importorskip("pyproj")
crs_osgb = pyproj.CRS(27700)
crs_wgs = pyproj.CRS(4326)
@@ -144,6 +147,32 @@ def test_loc(df):
assert_object(df.loc[:, "value1"], pd.Series)
@pytest.mark.parametrize(
"geom_name",
[
"geometry",
pytest.param(
"geom",
marks=pytest.mark.xfail(
reason="pre-regression behaviour only works for geometry col geometry"
),
),
],
)
def test_loc_add_row(geom_name, nybb_filename):
# https://github.com/geopandas/geopandas/issues/3119
nybb = geopandas.read_file(nybb_filename)[["BoroCode", "geometry"]]
if geom_name != "geometry":
nybb = nybb.rename_geometry(geom_name)
# crs_orig = nybb.crs
# add a new row
nybb.loc[5] = [6, nybb.geometry.iloc[0]]
assert nybb.geometry.dtype == "geometry"
assert nybb.crs is None # TODO this should be crs_orig, regressed in #2373
def test_iloc(df):
geo_name = df.geometry.name
assert_object(df.iloc[:, 0:2], pd.DataFrame)
@@ -284,7 +313,7 @@ def test_expandim_in_groupby_aggregate_multiple_funcs():
s = GeoSeries.from_xy([0, 1, 2], [0, 1, 3])
def union(s):
return s.unary_union
return s.union_all()
def total_area(s):
return s.area.sum()
@@ -370,3 +399,13 @@ def test_constructor_sliced_in_pandas_methods(df2):
assert type(hashable_test_df.duplicated()) == pd.Series
assert type(df2.quantile(numeric_only=True)) == pd.Series
assert type(df2.memory_usage()) == pd.Series
def test_merge_preserve_geodataframe():
# https://github.com/geopandas/geopandas/issues/2932
ser = GeoSeries.from_xy([1], [1])
df = GeoDataFrame({"geo": ser})
res = df.merge(df, left_index=True, right_index=True)
assert_obj_no_active_geo_col(res, GeoDataFrame, geo_colname=None)
expected = GeoDataFrame({"geo_x": ser, "geo_y": ser})
assert_geodataframe_equal(expected, res)

View File

@@ -3,14 +3,15 @@ import os
import numpy as np
import pandas as pd
from shapely.geometry import Point, Polygon, LineString, GeometryCollection, box
from shapely import make_valid
from shapely.geometry import GeometryCollection, LineString, Point, Polygon, box
import geopandas
from geopandas import GeoDataFrame, GeoSeries, overlay, read_file
from geopandas._compat import PANDAS_GE_20
from geopandas._compat import HAS_PYPROJ, PANDAS_GE_20
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
import pytest
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
try:
from fiona.errors import DriverError
@@ -23,9 +24,6 @@ except ImportError:
DATA = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data", "overlay")
pytestmark = pytest.mark.skip_no_sindex
@pytest.fixture
def dfs(request):
s1 = GeoSeries(
@@ -83,7 +81,7 @@ def test_overlay(dfs_index, how):
expected = read_file(
os.path.join(DATA, "polys", "df1_df2-{0}.geojson".format(name))
)
expected.crs = None
expected.geometry.array.crs = None
for col in expected.columns[expected.dtypes == "int32"]:
expected[col] = expected[col].astype("int64")
return expected
@@ -115,8 +113,8 @@ def test_overlay(dfs_index, how):
@pytest.mark.filterwarnings("ignore:GeoSeries crs mismatch:UserWarning")
def test_overlay_nybb(how):
polydf = read_file(geopandas.datasets.get_path("nybb"))
def test_overlay_nybb(how, nybb_filename):
polydf = read_file(nybb_filename)
# The circles have been constructed and saved at the time the expected
# results were created (exact output of buffer algorithm can slightly
@@ -212,6 +210,10 @@ def test_overlay_nybb(how):
expected.loc[24, "geometry"] = None
result.loc[24, "geometry"] = None
# missing values get read as None in read_file for a string column, but
# are introduced as NaN by overlay
expected["BoroName"] = expected["BoroName"].fillna(np.nan)
assert_geodataframe_equal(
result,
expected,
@@ -347,6 +349,7 @@ def test_geoseries_warning(dfs):
overlay(df1, df2.geometry, how="union")
@pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not available")
def test_preserve_crs(dfs, how):
df1, df2 = dfs
result = overlay(df1, df2, how=how)
@@ -358,6 +361,7 @@ def test_preserve_crs(dfs, how):
assert result.crs == crs
@pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not available")
def test_crs_mismatch(dfs, how):
df1, df2 = dfs
df1.crs = 4326
@@ -514,6 +518,12 @@ def test_overlay_strict(how, keep_geom_type, geom_types):
expected = expected.sort_values(cols, axis=0).reset_index(drop=True)
result = result.sort_values(cols, axis=0).reset_index(drop=True)
# some columns are all-NaN in the result, but get read as object dtype
# column of None values in read_file
for col in ["col1", "col3", "col4"]:
if col in expected.columns and expected[col].isna().all():
expected[col] = expected[col].astype("float64")
assert_geodataframe_equal(
result,
expected,
@@ -693,11 +703,11 @@ def test_keep_geom_type_geometry_collection_difference():
assert_geodataframe_equal(result1, expected1)
@pytest.mark.parametrize("make_valid", [True, False])
def test_overlap_make_valid(make_valid):
@pytest.mark.parametrize("should_make_valid", [True, False])
def test_overlap_make_valid(should_make_valid):
bowtie = Polygon([(1, 1), (9, 9), (9, 1), (1, 9), (1, 1)])
assert not bowtie.is_valid
fixed_bowtie = bowtie.buffer(0)
fixed_bowtie = make_valid(bowtie)
assert fixed_bowtie.is_valid
df1 = GeoDataFrame({"col1": ["region"], "geometry": GeoSeries([box(0, 0, 10, 10)])})
@@ -705,17 +715,17 @@ def test_overlap_make_valid(make_valid):
{"col1": ["invalid", "valid"], "geometry": GeoSeries([bowtie, fixed_bowtie])}
)
if make_valid:
df_overlay_bowtie = overlay(df1, df_bowtie, make_valid=make_valid)
if should_make_valid:
df_overlay_bowtie = overlay(df1, df_bowtie, make_valid=should_make_valid)
assert df_overlay_bowtie.at[0, "geometry"].equals(fixed_bowtie)
assert df_overlay_bowtie.at[1, "geometry"].equals(fixed_bowtie)
else:
with pytest.raises(ValueError, match="1 invalid input geometries"):
overlay(df1, df_bowtie, make_valid=make_valid)
overlay(df1, df_bowtie, make_valid=should_make_valid)
def test_empty_overlay_return_non_duplicated_columns():
nybb = geopandas.read_file(geopandas.datasets.get_path("nybb"))
def test_empty_overlay_return_non_duplicated_columns(nybb_filename):
nybb = geopandas.read_file(nybb_filename)
nybb2 = nybb.copy()
nybb2.geometry = nybb2.translate(20000000)
@@ -854,7 +864,7 @@ class TestOverlayWikiExample:
def test_intersection(self):
df_result = overlay(self.layer_a, self.layer_b, how="intersection")
assert df_result.geom_equals(self.intersection).bool()
assert df_result.geom_equals(self.intersection).all()
def test_union(self):
df_result = overlay(self.layer_a, self.layer_b, how="union")

View File

@@ -1,22 +1,22 @@
import os
from packaging.version import Version
import warnings
from packaging.version import Version
import numpy as np
from numpy.testing import assert_array_equal
import pandas as pd
import shapely
from shapely.geometry import Point, GeometryCollection, LineString, LinearRing
from shapely.geometry import GeometryCollection, LinearRing, LineString, Point
import geopandas
from geopandas import GeoDataFrame, GeoSeries
import geopandas._compat as compat
from geopandas import GeoDataFrame, GeoSeries
from geopandas.array import from_shapely
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
from pandas.testing import assert_frame_equal, assert_series_equal
import pytest
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
from numpy.testing import assert_array_equal
from pandas.testing import assert_frame_equal, assert_series_equal
@pytest.fixture
@@ -41,6 +41,7 @@ def test_repr(s, df):
assert "POINT" in df._repr_html_()
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="requires GEOS>=3.9")
def test_repr_boxed_display_precision():
# geographic coordinates
p1 = Point(10.123456789, 50.123456789)
@@ -90,7 +91,7 @@ def test_repr_empty():
def test_repr_linearring():
# https://github.com/geopandas/geopandas/pull/2689
# specifically, checking internal shapely/pygeos/wkt/wkb conversions
# specifically, checking internal shapely/wkt/wkb conversions
# preserve LinearRing
s = GeoSeries([LinearRing([(0, 0), (1, 1), (1, -1)])])
assert "LINEARRING" in str(s.iloc[0]) # shapely scalar repr
@@ -304,17 +305,18 @@ def test_convert_dtypes(df):
res2 = df[["value1", "value2", "geometry"]].convert_dtypes()
assert_geodataframe_equal(expected1[["value1", "value2", "geometry"]], res2)
# Test again with crs set and custom geom col name
df2 = df.set_crs(epsg=4326).rename_geometry("points")
expected2 = GeoDataFrame(
pd.DataFrame(df2).convert_dtypes(), crs=df2.crs, geometry=df2.geometry.name
)
res3 = df2.convert_dtypes()
assert_geodataframe_equal(expected2, res3)
if compat.HAS_PYPROJ:
# Test again with crs set and custom geom col name
df2 = df.set_crs(epsg=4326).rename_geometry("points")
expected2 = GeoDataFrame(
pd.DataFrame(df2).convert_dtypes(), crs=df2.crs, geometry=df2.geometry.name
)
res3 = df2.convert_dtypes()
assert_geodataframe_equal(expected2, res3)
# Test geom last, geom_col=geometry
res4 = df2[["value1", "value2", "points"]].convert_dtypes()
assert_geodataframe_equal(expected2[["value1", "value2", "points"]], res4)
# Test geom last, geom_col=geometry
res4 = df2[["value1", "value2", "points"]].convert_dtypes()
assert_geodataframe_equal(expected2[["value1", "value2", "points"]], res4)
def test_to_csv(df):
@@ -558,10 +560,9 @@ def test_value_counts():
name = "count"
else:
name = None
with compat.ignore_shapely2_warnings():
exp = pd.Series(
[2, 1], index=pd14_compat_index([Point(0, 0), Point(1, 1)]), name=name
)
exp = pd.Series(
[2, 1], index=pd14_compat_index([Point(0, 0), Point(1, 1)]), name=name
)
assert_series_equal(res, exp)
# Check crs doesn't make a difference - note it is not kept in output index anyway
s2 = GeoSeries([Point(0, 0), Point(1, 1), Point(0, 0)], crs="EPSG:4326")
@@ -575,20 +576,17 @@ def test_value_counts():
s3 = GeoSeries([Point(0, 0), LineString([[1, 1], [2, 2]]), Point(0, 0)])
res3 = s3.value_counts()
index = pd14_compat_index([Point(0, 0), LineString([[1, 1], [2, 2]])])
with compat.ignore_shapely2_warnings():
exp3 = pd.Series([2, 1], index=index, name=name)
exp3 = pd.Series([2, 1], index=index, name=name)
assert_series_equal(res3, exp3)
# check None is handled
s4 = GeoSeries([Point(0, 0), None, Point(0, 0)])
res4 = s4.value_counts(dropna=True)
with compat.ignore_shapely2_warnings():
exp4_dropna = pd.Series([2], index=pd14_compat_index([Point(0, 0)]), name=name)
exp4_dropna = pd.Series([2], index=pd14_compat_index([Point(0, 0)]), name=name)
assert_series_equal(res4, exp4_dropna)
with compat.ignore_shapely2_warnings():
exp4_keepna = pd.Series(
[2, 1], index=pd14_compat_index([Point(0, 0), None]), name=name
)
exp4_keepna = pd.Series(
[2, 1], index=pd14_compat_index([Point(0, 0), None]), name=name
)
res4_keepna = s4.value_counts(dropna=False)
assert_series_equal(res4_keepna, exp4_keepna)
@@ -636,7 +634,7 @@ def test_groupby(df):
assert_frame_equal(res, exp)
# applying on the geometry column
res = df.groupby("value2")["geometry"].apply(lambda x: x.unary_union)
res = df.groupby("value2")["geometry"].apply(lambda x: x.union_all())
exp = GeoSeries(
[shapely.geometry.MultiPoint([(0, 0), (2, 2)]), Point(1, 1)],
@@ -646,7 +644,7 @@ def test_groupby(df):
assert_series_equal(res, exp)
# apply on geometry column not resulting in new geometry
res = df.groupby("value2")["geometry"].apply(lambda x: x.unary_union.area)
res = df.groupby("value2")["geometry"].apply(lambda x: x.union_all().area)
exp = pd.Series([0.0, 0.0], index=pd.Index([1, 2], name="value2"), name="geometry")
assert_series_equal(res, exp)
@@ -660,45 +658,60 @@ def test_groupby_groups(df):
assert_frame_equal(res, exp)
@pytest.mark.skip_no_sindex
@pytest.mark.parametrize("crs", [None, "EPSG:4326"])
def test_groupby_metadata(crs):
@pytest.mark.parametrize("geometry_name", ["geometry", "geom"])
def test_groupby_metadata(crs, geometry_name):
if crs and not compat.HAS_PYPROJ:
pytest.skip("requires pyproj")
# https://github.com/geopandas/geopandas/issues/2294
df = GeoDataFrame(
{
"geometry": [Point(0, 0), Point(1, 1), Point(0, 0)],
geometry_name: [Point(0, 0), Point(1, 1), Point(0, 0)],
"value1": np.arange(3, dtype="int64"),
"value2": np.array([1, 2, 1], dtype="int64"),
},
crs=crs,
geometry=geometry_name,
)
kwargs = {}
if compat.PANDAS_GE_22:
# pandas is deprecating that the group key is present as column in the
# dataframe passed to `func`. To suppress this warning, it introduced
# a new include_groups keyword
kwargs = dict(include_groups=False)
# dummy test asserting we can access the crs
def func(group):
assert isinstance(group, GeoDataFrame)
assert group.crs == crs
df.groupby("value2").apply(func)
df.groupby("value2").apply(func, **kwargs)
# selecting the non-group columns -> no need to pass the keyword
if (
compat.PANDAS_GE_22
or (compat.PANDAS_GE_20 and geometry_name == "geometry")
or not compat.PANDAS_GE_20
):
df.groupby("value2")[[geometry_name, "value1"]].apply(func)
else:
# https://github.com/geopandas/geopandas/pull/2966#issuecomment-1878816712
# with pandas 2.0 and 2.1 with geom col != geometry this is failing
with pytest.raises(AttributeError):
df.groupby("value2")[[geometry_name, "value1"]].apply(func)
# actual test with functionality
res = df.groupby("value2").apply(
lambda x: geopandas.sjoin(x, x[["geometry", "value1"]], how="inner")
lambda x: geopandas.sjoin(x, x[[geometry_name, "value1"]], how="inner"),
**kwargs,
)
if compat.PANDAS_GE_22:
# merge sort behaviour changed in pandas #54611
take_indices = [0, 0, 2, 2, 1]
value_right = [0, 2, 0, 2, 1]
else:
take_indices = [0, 2, 0, 2, 1]
value_right = [0, 0, 2, 2, 1]
expected = (
df.take(take_indices)
.set_index("value2", drop=False, append=True)
df.take([0, 0, 2, 2, 1])
.set_index("value2", drop=compat.PANDAS_GE_22, append=True)
.swaplevel()
.rename(columns={"value1": "value1_left"})
.assign(value1_right=value_right)
.assign(value1_right=[0, 2, 0, 2, 1])
)
assert_geodataframe_equal(res.drop(columns=["index_right"]), expected)
@@ -733,6 +746,7 @@ def test_apply_loc_len1(df):
np.testing.assert_allclose(result, expected)
@pytest.mark.skipif(compat.PANDAS_GE_30, reason="convert_dtype is removed in pandas 3")
def test_apply_convert_dtypes_keyword(s):
# ensure the convert_dtypes keyword is accepted
if not compat.PANDAS_GE_21:
@@ -754,6 +768,8 @@ def test_apply_convert_dtypes_keyword(s):
@pytest.mark.parametrize("crs", [None, "EPSG:4326"])
def test_apply_no_geometry_result(df, crs):
if crs:
if not compat.HAS_PYPROJ:
pytest.skip("requires pyproj")
df = df.set_crs(crs)
result = df.apply(lambda col: col.astype(str), axis=0)
assert type(result) is pd.DataFrame
@@ -858,3 +874,17 @@ def test_preserve_flags(df):
with pytest.raises(ValueError):
pd.concat([df, df])
def test_ufunc():
# this is calling a shapely ufunc, but we currently rely on pandas' implementation
# of `__array_ufunc__` to wrap the result back into a GeoSeries
ser = GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)])
result = shapely.buffer(ser, 2)
assert isinstance(result, GeoSeries)
# ensure the result is still writeable
# (https://github.com/geopandas/geopandas/issues/3178)
assert result.array._data.flags.writeable
result.loc[0] = Point(10, 10)
assert result.iloc[0] == Point(10, 10)

View File

@@ -1,28 +1,25 @@
import itertools
from packaging.version import Version
import warnings
from packaging.version import Version
import numpy as np
import pandas as pd
from shapely import wkt
from shapely.affinity import rotate
from shapely.geometry import (
MultiPolygon,
Polygon,
LineString,
LinearRing,
Point,
MultiPoint,
MultiLineString,
GeometryCollection,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
box,
)
from geopandas import GeoDataFrame, GeoSeries, read_file
from geopandas.datasets import get_path
import geopandas._compat as compat
from geopandas import GeoDataFrame, GeoSeries, read_file
from geopandas.plotting import GeoplotAccessor
import pytest
@@ -304,17 +301,9 @@ class TestPointPlotting:
assert len(ax.collections) == 0
def test_empty_geometry(self):
if compat.USE_PYGEOS:
s = GeoSeries([wkt.loads("POLYGON EMPTY")])
s = GeoSeries(
[Polygon([(0, 0), (1, 0), (1, 1)]), wkt.loads("POLYGON EMPTY")]
)
ax = s.plot()
assert len(ax.collections) == 1
if not compat.USE_PYGEOS:
s = GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), Polygon()])
ax = s.plot()
assert len(ax.collections) == 1
s = GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), Polygon()])
ax = s.plot()
assert len(ax.collections) == 1
# more complex case with GEOMETRYCOLLECTION EMPTY, POINT EMPTY and NONE
poly = Polygon([(-1, -1), (-1, 2), (2, 2), (2, -1), (-1, -1)])
@@ -324,7 +313,14 @@ class TestPointPlotting:
gdf = GeoDataFrame(geometry=[point, empty_point, point_])
gdf["geometry"] = gdf.intersection(poly)
gdf.loc[3] = [None]
with warnings.catch_warnings():
# loc to add row calls concat internally, warning for pandas >=2.1
warnings.filterwarnings(
"ignore",
"The behavior of DataFrame concatenation with empty",
FutureWarning,
)
gdf.loc[3] = [None]
ax = gdf.plot()
assert len(ax.collections) == 1
@@ -498,6 +494,28 @@ class TestLineStringPlotting:
)
self.df3 = GeoDataFrame({"geometry": self.linearrings, "values": values})
def test_autolim_false(self):
"""Test linestring plot preserving axes limits."""
ax = self.lines[: self.N // 2].plot()
ylim = ax.get_ylim()
self.lines.plot(ax=ax, autolim=False)
assert ax.get_ylim() == ylim
ax = self.df[: self.N // 2].plot()
ylim = ax.get_ylim()
self.df.plot(ax=ax, autolim=False)
assert ax.get_ylim() == ylim
def test_autolim_true(self):
"""Test linestring plot autoscaling axes limits."""
ax = self.lines[: self.N // 2].plot()
ylim = ax.get_ylim()
self.lines.plot(ax=ax, autolim=True)
assert ax.get_ylim() != ylim
ax = self.df[: self.N // 2].plot()
ylim = ax.get_ylim()
self.df.plot(ax=ax, autolim=True)
assert ax.get_ylim() != ylim
def test_single_color(self):
ax = self.lines.plot(color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
@@ -641,6 +659,28 @@ class TestPolygonPlotting:
df_nan = GeoDataFrame({"geometry": t3, "values": [np.nan]})
self.df3 = pd.concat([self.df, df_nan])
def test_autolim_false(self):
"""Test polygon plot preserving axes limits."""
ax = self.polys[:1].plot()
xlim = ax.get_xlim()
self.polys.plot(ax=ax, autolim=False)
assert ax.get_xlim() == xlim
ax = self.df[:1].plot()
xlim = ax.get_xlim()
self.df.plot(ax=ax, autolim=False)
assert ax.get_xlim() == xlim
def test_autolim_true(self):
"""Test polygon plot autoscaling axes limits."""
ax = self.polys[:1].plot()
xlim = ax.get_xlim()
self.polys.plot(ax=ax, autolim=True)
assert ax.get_xlim() != xlim
ax = self.df[:1].plot()
xlim = ax.get_xlim()
self.df.plot(ax=ax, autolim=True)
assert ax.get_xlim() != xlim
def test_single_color(self):
ax = self.polys.plot(color="green")
_check_colors(2, ax.collections[0].get_facecolors(), ["green"] * 2)
@@ -1070,16 +1110,20 @@ class TestNonuniformGeometryPlotting:
# )
class TestGeographicAspect:
def setup_class(self):
pth = get_path("naturalearth_lowres")
df = read_file(pth)
self.north = df.loc[df.continent == "North America"]
self.north_proj = self.north.to_crs("ESRI:102008")
bounds = self.north.total_bounds
y_coord = np.mean([bounds[1], bounds[3]])
self.exp = 1 / np.cos(y_coord * np.pi / 180)
@pytest.fixture(scope="class")
def _setup_class_geographic_aspect(naturalearth_lowres, request):
"""Attach naturalearth_lowres class attribute for unittest style setup_method"""
df = read_file(naturalearth_lowres)
request.cls.north = df.loc[df.continent == "North America"]
request.cls.north_proj = request.cls.north.to_crs("ESRI:102008")
bounds = request.cls.north.total_bounds
y_coord = np.mean([bounds[1], bounds[3]])
request.cls.exp = 1 / np.cos(y_coord * np.pi / 180)
@pytest.mark.usefixtures("_setup_class_geographic_aspect")
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
class TestGeographicAspect:
def test_auto(self):
ax = self.north.geometry.plot()
assert ax.get_aspect() == self.exp
@@ -1133,6 +1177,9 @@ class TestGeographicAspect:
assert ax3.get_aspect() == 0.5
@pytest.mark.filterwarnings(
"ignore:Numba not installed. Using slow pure python version.:UserWarning"
)
class TestMapclassifyPlotting:
@classmethod
def setup_class(cls):
@@ -1143,31 +1190,40 @@ class TestMapclassifyPlotting:
cls.mc = mapclassify
cls.classifiers = list(mapclassify.classifiers.CLASSIFIERS)
cls.classifiers.remove("UserDefined")
pth = get_path("naturalearth_lowres")
cls.df = read_file(pth)
cls.df["NEGATIVES"] = np.linspace(-10, 10, len(cls.df.index))
cls.df["low_vals"] = np.linspace(0, 0.3, cls.df.shape[0])
cls.df["mid_vals"] = np.linspace(0.3, 0.7, cls.df.shape[0])
cls.df["high_vals"] = np.linspace(0.7, 1.0, cls.df.shape[0])
cls.df.loc[cls.df.index[:20:2], "high_vals"] = np.nan
cls.nybb = read_file(get_path("nybb"))
cls.nybb["vals"] = [0.001, 0.002, 0.003, 0.004, 0.005]
def test_legend(self):
@pytest.fixture
def df(self, naturalearth_lowres):
# version of naturalearth_lowres for mapclassify plotting tests
df = read_file(naturalearth_lowres)
df["NEGATIVES"] = np.linspace(-10, 10, len(df.index))
df["low_vals"] = np.linspace(0, 0.3, df.shape[0])
df["mid_vals"] = np.linspace(0.3, 0.7, df.shape[0])
df["high_vals"] = np.linspace(0.7, 1.0, df.shape[0])
df.loc[df.index[:20:2], "high_vals"] = np.nan
return df
@pytest.fixture
def nybb(self, nybb_filename):
# version of nybb for mapclassify plotting tests
df = read_file(nybb_filename)
df["vals"] = [0.001, 0.002, 0.003, 0.004, 0.005]
return df
def test_legend(self, df):
with warnings.catch_warnings(record=True) as _: # don't print warning
# warning coming from scipy.stats
ax = self.df.plot(
ax = df.plot(
column="pop_est", scheme="QUANTILES", k=3, cmap="OrRd", legend=True
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = [
s.split("|")[0][1:-2]
for s in str(self.mc.Quantiles(self.df["pop_est"], k=3)).split("\n")[4:]
for s in str(self.mc.Quantiles(df["pop_est"], k=3)).split("\n")[4:]
]
assert labels == expected
def test_bin_labels(self):
ax = self.df.plot(
def test_bin_labels(self, df):
ax = df.plot(
column="pop_est",
scheme="QUANTILES",
k=3,
@@ -1179,9 +1235,9 @@ class TestMapclassifyPlotting:
expected = ["foo", "bar", "baz"]
assert labels == expected
def test_invalid_labels_length(self):
def test_invalid_labels_length(self, df):
with pytest.raises(ValueError):
self.df.plot(
df.plot(
column="pop_est",
scheme="QUANTILES",
k=3,
@@ -1190,16 +1246,16 @@ class TestMapclassifyPlotting:
legend_kwds={"labels": ["foo", "bar"]},
)
def test_negative_legend(self):
ax = self.df.plot(
def test_negative_legend(self, df):
ax = df.plot(
column="NEGATIVES", scheme="FISHER_JENKS", k=3, cmap="OrRd", legend=True
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = ["-10.00, -3.41", " -3.41, 3.30", " 3.30, 10.00"]
assert labels == expected
def test_fmt(self):
ax = self.df.plot(
def test_fmt(self, df):
ax = df.plot(
column="NEGATIVES",
scheme="FISHER_JENKS",
k=3,
@@ -1211,8 +1267,8 @@ class TestMapclassifyPlotting:
expected = ["-10, -3", " -3, 3", " 3, 10"]
assert labels == expected
def test_interval(self):
ax = self.df.plot(
def test_interval(self, df):
ax = df.plot(
column="NEGATIVES",
scheme="FISHER_JENKS",
k=3,
@@ -1225,17 +1281,17 @@ class TestMapclassifyPlotting:
assert labels == expected
@pytest.mark.parametrize("scheme", ["FISHER_JENKS", "FISHERJENKS"])
def test_scheme_name_compat(self, scheme):
ax = self.df.plot(column="NEGATIVES", scheme=scheme, k=3, legend=True)
def test_scheme_name_compat(self, scheme, df):
ax = df.plot(column="NEGATIVES", scheme=scheme, k=3, legend=True)
assert len(ax.get_legend().get_texts()) == 3
def test_schemes(self):
def test_schemes(self, df):
# test if all available classifiers pass
for scheme in self.classifiers:
self.df.plot(column="pop_est", scheme=scheme, legend=True)
df.plot(column="pop_est", scheme=scheme, legend=True)
def test_classification_kwds(self):
ax = self.df.plot(
def test_classification_kwds(self, df):
ax = df.plot(
column="pop_est",
scheme="percentiles",
k=3,
@@ -1246,21 +1302,19 @@ class TestMapclassifyPlotting:
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = [
s.split("|")[0][1:-2]
for s in str(self.mc.Percentiles(self.df["pop_est"], pct=[50, 100])).split(
"\n"
)[4:]
for s in str(self.mc.Percentiles(df["pop_est"], pct=[50, 100])).split("\n")[
4:
]
]
assert labels == expected
def test_invalid_scheme(self):
def test_invalid_scheme(self, df):
with pytest.raises(ValueError):
scheme = "invalid_scheme_*#&)(*#"
self.df.plot(
column="gdp_md_est", scheme=scheme, k=3, cmap="OrRd", legend=True
)
df.plot(column="gdp_md_est", scheme=scheme, k=3, cmap="OrRd", legend=True)
def test_cax_legend_passing(self):
def test_cax_legend_passing(self, df):
"""Pass a 'cax' argument to 'df.plot(.)', that is valid only if 'ax' is
passed as well (if not, a new figure is created ad hoc, and 'cax' is
ignored)
@@ -1271,15 +1325,15 @@ class TestMapclassifyPlotting:
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.1)
with pytest.raises(ValueError):
ax = self.df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax)
ax = df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax)
def test_cax_legend_height(self):
def test_cax_legend_height(self, df):
"""Pass a cax argument to 'df.plot(.)', the legend location must be
aligned with those of main plot
"""
# base case
with warnings.catch_warnings(record=True) as _: # don't print warning
ax = self.df.plot(column="pop_est", cmap="OrRd", legend=True)
ax = df.plot(column="pop_est", cmap="OrRd", legend=True)
plot_height = _get_ax(ax.get_figure(), "").get_position().height
legend_height = _get_ax(ax.get_figure(), "<colorbar>").get_position().height
assert abs(plot_height - legend_height) >= 1e-6
@@ -1290,16 +1344,14 @@ class TestMapclassifyPlotting:
divider = make_axes_locatable(ax2)
cax = divider.append_axes("right", size="5%", pad=0.1, label="fixed_colorbar")
with warnings.catch_warnings(record=True) as _:
ax2 = self.df.plot(
column="pop_est", cmap="OrRd", legend=True, cax=cax, ax=ax2
)
ax2 = df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax, ax=ax2)
plot_height = _get_ax(fig, "").get_position().height
legend_height = _get_ax(fig, "fixed_colorbar").get_position().height
assert abs(plot_height - legend_height) < 1e-6
def test_empty_bins(self):
def test_empty_bins(self, df):
bins = np.arange(1, 11) / 10
ax = self.df.plot(
ax = df.plot(
"low_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
@@ -1348,7 +1400,7 @@ class TestMapclassifyPlotting:
line.get_markerfacecolor() for line in ax.get_legend().get_lines()
] == legend_colors_exp
ax2 = self.df.plot(
ax2 = df.plot(
"mid_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
@@ -1386,7 +1438,7 @@ class TestMapclassifyPlotting:
line.get_markerfacecolor() for line in ax2.get_legend().get_lines()
] == legend_colors_exp
ax3 = self.df.plot(
ax3 = df.plot(
"high_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
@@ -1411,8 +1463,8 @@ class TestMapclassifyPlotting:
line.get_markerfacecolor() for line in ax3.get_legend().get_lines()
] == legend_colors_exp
def test_equally_formatted_bins(self):
ax = self.nybb.plot(
def test_equally_formatted_bins(self, nybb):
ax = nybb.plot(
"vals",
scheme="quantiles",
legend=True,
@@ -1427,7 +1479,7 @@ class TestMapclassifyPlotting:
]
assert labels == expected
ax2 = self.nybb.plot(
ax2 = nybb.plot(
"vals", scheme="quantiles", legend=True, legend_kwds={"fmt": "{:.3f}"}
)
labels = [t.get_text() for t in ax2.get_legend().get_texts()]
@@ -1454,9 +1506,10 @@ class TestPlotCollections:
)
def test_points(self):
from geopandas.plotting import _plot_point_collection, plot_point_collection
from matplotlib.collections import PathCollection
from geopandas.plotting import _plot_point_collection
fig, ax = plt.subplots()
coll = _plot_point_collection(ax, self.points)
assert isinstance(coll, PathCollection)
@@ -1508,10 +1561,6 @@ class TestPlotCollections:
with pytest.raises((TypeError, ValueError)):
_plot_point_collection(ax, self.points, color="not color")
# check FutureWarning
with pytest.warns(FutureWarning):
plot_point_collection(ax, self.points)
def test_points_values(self):
from geopandas.plotting import _plot_point_collection
@@ -1526,12 +1575,10 @@ class TestPlotCollections:
# _check_colors(self.N, coll.get_edgecolors(), expected_colors)
def test_linestrings(self):
from geopandas.plotting import (
_plot_linestring_collection,
plot_linestring_collection,
)
from matplotlib.collections import LineCollection
from geopandas.plotting import _plot_linestring_collection
fig, ax = plt.subplots()
coll = _plot_linestring_collection(ax, self.lines)
assert isinstance(coll, LineCollection)
@@ -1581,9 +1628,6 @@ class TestPlotCollections:
# not a color
with pytest.raises((TypeError, ValueError)):
_plot_linestring_collection(ax, self.lines, color="not color")
# check FutureWarning
with pytest.warns(FutureWarning):
plot_linestring_collection(ax, self.lines)
def test_linestrings_values(self):
from geopandas.plotting import _plot_linestring_collection
@@ -1615,9 +1659,10 @@ class TestPlotCollections:
ax.cla()
def test_polygons(self):
from geopandas.plotting import _plot_polygon_collection, plot_polygon_collection
from matplotlib.collections import PatchCollection
from geopandas.plotting import _plot_polygon_collection
fig, ax = plt.subplots()
coll = _plot_polygon_collection(ax, self.polygons)
assert isinstance(coll, PatchCollection)
@@ -1673,9 +1718,6 @@ class TestPlotCollections:
# not a color
with pytest.raises((TypeError, ValueError)):
_plot_polygon_collection(ax, self.polygons, color="not color")
# check FutureWarning
with pytest.warns(FutureWarning):
plot_polygon_collection(ax, self.polygons)
def test_polygons_values(self):
from geopandas.plotting import _plot_polygon_collection
@@ -1830,9 +1872,10 @@ def test_column_values():
def test_polygon_patch():
# test adapted from descartes by Sean Gillies
# (BSD license, https://pypi.org/project/descartes).
from geopandas.plotting import _PolygonPatch
from matplotlib.patches import PathPatch
from geopandas.plotting import _PolygonPatch
polygon = (
Point(0, 0).buffer(10.0).difference(MultiPoint([(-5, 0), (5, 0)]).buffer(3.0))
)

View File

@@ -33,11 +33,11 @@ def test_get_deps_info():
assert "fiona" in deps_info
assert "numpy" in deps_info
assert "shapely" in deps_info
assert "rtree" in deps_info
assert "pyproj" in deps_info
assert "matplotlib" in deps_info
assert "mapclassify" in deps_info
assert "geopy" in deps_info
assert "psycopg" in deps_info
assert "psycopg2" in deps_info
assert "geoalchemy2" in deps_info

View File

@@ -1,30 +1,25 @@
from math import sqrt
import numpy as np
import shapely
from shapely.geometry import (
Point,
Polygon,
MultiPolygon,
box,
GeometryCollection,
LineString,
MultiPolygon,
Point,
Polygon,
box,
)
from numpy.testing import assert_array_equal
import geopandas
from geopandas import GeoDataFrame, GeoSeries, read_file
from geopandas import _compat as compat
from geopandas import GeoDataFrame, GeoSeries, read_file, datasets
import pytest
import numpy as np
import pandas as pd
if compat.USE_SHAPELY_20:
import shapely as mod
elif compat.USE_PYGEOS:
import pygeos as mod
from numpy.testing import assert_array_equal
@pytest.mark.skip_no_sindex
class TestSeriesSindex:
def test_has_sindex(self):
"""Test the has_sindex method."""
@@ -114,7 +109,6 @@ class TestSeriesSindex:
assert sliced.sindex is not original_index
@pytest.mark.skip_no_sindex
class TestFrameSindex:
def setup_method(self):
data = {
@@ -171,15 +165,15 @@ class TestFrameSindex:
"""Selecting a subset of columns preserves the index."""
original_index = self.df.sindex
# Selecting a subset of columns preserves the index for pandas < 2.0
# with pandas 2.0, the column is now copied, losing the index (although
# with Copy-on-Write, this will again be preserved)
# with pandas 2.0, the column is now copied, losing the index. But
# with pandas >= 3.0 and Copy-on-Write this is preserved again
subset1 = self.df[["geom", "A"]]
if compat.PANDAS_GE_20 and not pd.options.mode.copy_on_write:
if compat.PANDAS_GE_20 and not compat.PANDAS_GE_30:
assert subset1.sindex is not original_index
else:
assert subset1.sindex is original_index
subset2 = self.df[["A", "geom"]]
if compat.PANDAS_GE_20 and not pd.options.mode.copy_on_write:
if compat.PANDAS_GE_20 and not compat.PANDAS_GE_30:
assert subset2.sindex is not original_index
else:
assert subset2.sindex is original_index
@@ -209,12 +203,12 @@ class TestFrameSindex:
assert old_sindex is new_sindex
# Skip to accommodate Shapely geometries being unhashable
# Skip to accommodate Shapely geometries being unhashable # TODO unskip?
@pytest.mark.skip
@pytest.mark.usefixtures("_setup_class_nybb_filename")
class TestJoinSindex:
def setup_method(self):
nybb_filename = geopandas.datasets.get_path("nybb")
self.boros = read_file(nybb_filename)
self.boros = read_file(self.nybb_filename)
def test_merge_geo(self):
# First check that we gets hits from the boros frame.
@@ -247,8 +241,7 @@ class TestJoinSindex:
assert res == ["Bronx", "Queens"]
@pytest.mark.skip_no_sindex
class TestPygeosInterface:
class TestShapelyInterface:
def setup_method(self):
data = {
"geom": [Point(x, y) for x, y in zip(range(5), range(5))]
@@ -275,15 +268,9 @@ class TestPygeosInterface:
@pytest.mark.parametrize("test_geom", ((-1, -1, -0.5), -0.5, None, Point(0, 0)))
def test_intersection_invalid_bounds_tuple(self, test_geom):
"""Tests the `intersection` method with invalid inputs."""
if compat.USE_PYGEOS:
with pytest.raises(TypeError):
# we raise a useful TypeError
self.df.sindex.intersection(test_geom)
else:
with pytest.raises((TypeError, Exception)):
# catch a general exception
# rtree raises an RTreeError which we need to catch
self.df.sindex.intersection(test_geom)
with pytest.raises(TypeError):
# we raise a useful TypeError
self.df.sindex.intersection(test_geom)
# ------------------------------ `query` tests ------------------------------ #
@pytest.mark.parametrize(
@@ -394,6 +381,108 @@ class TestPygeosInterface:
with pytest.raises(TypeError):
self.df.sindex.query("notavalidgeom")
@pytest.mark.skipif(not compat.GEOS_GE_310, reason="Requires GEOS 3.10")
@pytest.mark.parametrize(
"distance, test_geom, expected",
(
# bounds don't intersect and not within distance=0
(
0,
box(9.0, 9.0, 9.9, 9.9),
[],
),
# bounds don't intersect but is within distance=1
(
1,
box(9.0, 9.0, 9.9, 9.9),
[5],
),
# within 1-D absolute distance in both axes, but not euclidean distance
(
0.5,
Point(0.5, 0.5),
[],
),
# same as before but within euclidean distance
(
sqrt(2 * 0.5**2) + 1e-9,
Point(0.5, 0.5),
[0, 1],
),
# less than euclidean distance between points, multi-object
(
sqrt(2) - 1e-9,
[
Polygon([(0, 0), (1, 0), (1, 1)]),
Polygon([(1, 1), (2, 1), (2, 2)]),
], # multi-object test
[[0, 0, 1, 1], [0, 1, 1, 2]],
),
# more than euclidean distance between points, multi-object
(
sqrt(2) + 1e-9,
[
Polygon([(0, 0), (1, 0), (1, 1)]),
Polygon([(1, 1), (2, 1), (2, 2)]),
],
[[0, 0, 0, 1, 1, 1, 1], [0, 1, 2, 0, 1, 2, 3]],
),
# distance is array-like, broadcastable to geometry
(
[2, 10],
[Point(0.5, 0.5), Point(1, 1)],
[[0, 0, 1, 1, 1, 1, 1], [0, 1, 0, 1, 2, 3, 4]],
),
),
)
def test_query_dwithin(self, distance, test_geom, expected):
"""Tests the `query` method with predicates that require keyword arguments."""
res = self.df.sindex.query(test_geom, predicate="dwithin", distance=distance)
assert_array_equal(res, expected)
@pytest.mark.skipif(not compat.GEOS_GE_310, reason="Requires GEOS 3.10")
def test_dwithin_no_distance(self):
"""Tests the `query` method with keyword arguments that are
invalid for certain predicates."""
with pytest.raises(
ValueError, match="'distance' parameter is required for 'dwithin' predicate"
):
self.df.sindex.query(Point(0, 0), predicate="dwithin")
@pytest.mark.parametrize(
"predicate",
[
None,
"contains",
"contains_properly",
"covered_by",
"covers",
"crosses",
"intersects",
"overlaps",
"touches",
"within",
],
)
def test_query_distance_invalid(self, predicate):
"""Tests the `query` method with keyword arguments that are
invalid for certain predicates."""
msg = "'distance' parameter is only supported in combination with 'dwithin'"
with pytest.raises(ValueError, match=msg):
self.df.sindex.query(Point(0, 0), predicate=predicate, distance=0)
@pytest.mark.skipif(
compat.GEOS_GE_310, reason="Test for 'dwithin'-incompatible versions of GEOS"
)
def test_dwithin_requirements(self):
"""Tests whether a ValueError is raised when trying to use dwithin with
incompatible versions of shapely or pyGEOS
"""
with pytest.raises(
ValueError, match="predicate = 'dwithin' requires GEOS >= 3.10.0"
):
self.df.sindex.query(Point(0, 0), predicate="dwithin", distance=0)
@pytest.mark.parametrize(
"test_geom, expected_value",
[
@@ -440,13 +529,8 @@ class TestPygeosInterface:
)
expected = [0, 1, 2]
# pass through GeoSeries to have GeoPandas
# determine if it should use shapely or pygeos geometry objects
tree_df = geopandas.GeoDataFrame(geometry=tree_polys)
test_df = geopandas.GeoDataFrame(geometry=test_polys)
test_geo = test_df.geometry.values[0]
res = tree_df.sindex.query(test_geo, sort=sort)
test_geo = test_polys.values[0]
res = tree_polys.sindex.query(test_geo, sort=sort)
# asserting the same elements
assert sorted(res) == sorted(expected)
@@ -564,15 +648,12 @@ class TestPygeosInterface:
),
)
def test_query_bulk(self, predicate, test_geom, expected):
"""Tests the `query_bulk` method with valid
"""Tests the `query` method with valid
inputs and valid predicates.
"""
# pass through GeoSeries to have GeoPandas
# determine if it should use shapely or pygeos geometry objects
test_geom = geopandas.GeoSeries(
[box(*geom) for geom in test_geom], index=range(len(test_geom))
res = self.df.sindex.query(
[box(*geom) for geom in test_geom], predicate=predicate
)
res = self.df.sindex.query(test_geom, predicate=predicate)
assert_array_equal(res, expected)
@pytest.mark.parametrize(
@@ -587,16 +668,12 @@ class TestPygeosInterface:
],
)
def test_query_bulk_empty_geometry(self, test_geoms, expected_value):
"""Tests the `query_bulk` method with an empty geometry."""
# pass through GeoSeries to have GeoPandas
# determine if it should use shapely or pygeos geometry objects
# note: for this test, test_geoms (note plural) is a list already
test_geoms = geopandas.GeoSeries(test_geoms, index=range(len(test_geoms)))
"""Tests the `query` method with an empty geometries."""
res = self.df.sindex.query(test_geoms)
assert_array_equal(res, expected_value)
def test_query_bulk_empty_input_array(self):
"""Tests the `query_bulk` method with an empty input array."""
"""Tests the `query` method with an empty input array."""
test_array = np.array([], dtype=object)
expected_value = [[], []]
res = self.df.sindex.query(test_array)
@@ -604,23 +681,19 @@ class TestPygeosInterface:
def test_query_bulk_invalid_input_geometry(self):
"""
Tests the `query_bulk` method with invalid input for the `geometry` parameter.
Tests the `query` method with invalid input for the `geometry` parameter.
"""
test_array = "notanarray"
with pytest.raises(TypeError):
self.df.sindex.query(test_array)
def test_query_bulk_invalid_predicate(self):
"""Tests the `query_bulk` method with invalid predicates."""
"""Tests the `query` method with invalid predicates."""
test_geom_bounds = (-1, -1, -0.5, -0.5)
test_predicate = "test"
# pass through GeoSeries to have GeoPandas
# determine if it should use shapely or pygeos geometry objects
test_geom = geopandas.GeoSeries([box(*test_geom_bounds)], index=["0"])
with pytest.raises(ValueError):
self.df.sindex.query(test_geom.geometry, predicate=test_predicate)
self.df.sindex.query([box(*test_geom_bounds)], predicate=test_predicate)
@pytest.mark.parametrize(
"predicate, test_geom, expected",
@@ -631,11 +704,10 @@ class TestPygeosInterface:
),
)
def test_query_bulk_input_type(self, predicate, test_geom, expected):
"""Tests that query_bulk can accept a GeoSeries, GeometryArray or
"""Tests that query can accept a GeoSeries, GeometryArray or
numpy array.
"""
# pass through GeoSeries to have GeoPandas
# determine if it should use shapely or pygeos geometry objects
# pass through GeoSeries to test input type
test_geom = geopandas.GeoSeries([box(*test_geom)], index=["0"])
# test GeoSeries
@@ -667,7 +739,7 @@ class TestPygeosInterface:
),
)
def test_query_bulk_sorting(self, sort, expected):
"""Check that results from `query_bulk` don't depend
"""Check that results from `query` don't depend
on the order of geometries.
"""
# these geometries come from a reported issue:
@@ -682,12 +754,7 @@ class TestPygeosInterface:
]
)
# pass through GeoSeries to have GeoPandas
# determine if it should use shapely or pygeos geometry objects
tree_df = geopandas.GeoDataFrame(geometry=tree_polys)
test_df = geopandas.GeoDataFrame(geometry=test_polys)
res = tree_df.sindex.query(test_df.geometry, sort=sort)
res = tree_polys.sindex.query(test_polys, sort=sort)
# asserting the same elements
assert sorted(res[0]) == sorted(expected[0])
@@ -706,30 +773,6 @@ class TestPygeosInterface:
raise e
# ------------------------- `nearest` tests ------------------------- #
@pytest.mark.skipif(
compat.USE_PYGEOS or compat.USE_SHAPELY_20,
reason=("RTree supports sindex.nearest with different behaviour"),
)
def test_rtree_nearest_warns(self):
df = geopandas.GeoDataFrame({"geometry": []})
with pytest.warns(
FutureWarning, match="sindex.nearest using the rtree backend"
):
df.sindex.nearest((0, 0, 1, 1), num_results=2)
@pytest.mark.skipif(
compat.USE_SHAPELY_20 or not (compat.USE_PYGEOS and not compat.PYGEOS_GE_010),
reason=("PyGEOS < 0.10 does not support sindex.nearest"),
)
def test_pygeos_error(self):
df = geopandas.GeoDataFrame({"geometry": []})
with pytest.raises(NotImplementedError, match="requires pygeos >= 0.10"):
df.sindex.nearest(None)
@pytest.mark.skipif(
not (compat.USE_SHAPELY_20 or (compat.USE_PYGEOS and compat.PYGEOS_GE_010)),
reason=("PyGEOS >= 0.10 is required to test sindex.nearest"),
)
@pytest.mark.parametrize("return_all", [True, False])
@pytest.mark.parametrize(
"geometry,expected",
@@ -739,21 +782,17 @@ class TestPygeosInterface:
],
)
def test_nearest_single(self, geometry, expected, return_all):
geoms = mod.points(np.arange(10), np.arange(10))
geoms = shapely.points(np.arange(10), np.arange(10))
df = geopandas.GeoDataFrame({"geometry": geoms})
p = Point(geometry)
res = df.sindex.nearest(p, return_all=return_all)
assert_array_equal(res, expected)
p = mod.points(geometry)
p = shapely.points(geometry)
res = df.sindex.nearest(p, return_all=return_all)
assert_array_equal(res, expected)
@pytest.mark.skipif(
not (compat.USE_SHAPELY_20 or (compat.USE_PYGEOS and compat.PYGEOS_GE_010)),
reason=("PyGEOS >= 0.10 is required to test sindex.nearest"),
)
@pytest.mark.parametrize("return_all", [True, False])
@pytest.mark.parametrize(
"geometry,expected",
@@ -763,14 +802,14 @@ class TestPygeosInterface:
],
)
def test_nearest_multi(self, geometry, expected, return_all):
geoms = mod.points(np.arange(10), np.arange(10))
geoms = shapely.points(np.arange(10), np.arange(10))
df = geopandas.GeoDataFrame({"geometry": geoms})
ps = [Point(p) for p in geometry]
res = df.sindex.nearest(ps, return_all=return_all)
assert_array_equal(res, expected)
ps = mod.points(geometry)
ps = shapely.points(geometry)
res = df.sindex.nearest(ps, return_all=return_all)
assert_array_equal(res, expected)
@@ -783,10 +822,6 @@ class TestPygeosInterface:
res = df.sindex.nearest(ga, return_all=return_all)
assert_array_equal(res, expected)
@pytest.mark.skipif(
not (compat.USE_SHAPELY_20 or (compat.USE_PYGEOS and compat.PYGEOS_GE_010)),
reason=("PyGEOS >= 0.10 is required to test sindex.nearest"),
)
@pytest.mark.parametrize("return_all", [True, False])
@pytest.mark.parametrize(
"geometry,expected",
@@ -796,16 +831,12 @@ class TestPygeosInterface:
],
)
def test_nearest_none(self, geometry, expected, return_all):
geoms = mod.points(np.arange(10), np.arange(10))
geoms = shapely.points(np.arange(10), np.arange(10))
df = geopandas.GeoDataFrame({"geometry": geoms})
res = df.sindex.nearest(geometry, return_all=return_all)
assert_array_equal(res, expected)
@pytest.mark.skipif(
not (compat.USE_SHAPELY_20 or (compat.USE_PYGEOS and compat.PYGEOS_GE_010)),
reason=("PyGEOS >= 0.10 is required to test sindex.nearest"),
)
@pytest.mark.parametrize("return_distance", [True, False])
@pytest.mark.parametrize(
"return_all,max_distance,expected",
@@ -819,7 +850,7 @@ class TestPygeosInterface:
def test_nearest_max_distance(
self, expected, max_distance, return_all, return_distance
):
geoms = mod.points(np.arange(10), np.arange(10))
geoms = shapely.points(np.arange(10), np.arange(10))
df = geopandas.GeoDataFrame({"geometry": geoms})
ps = [Point(0.5, 0.5), Point(0, 10)]
@@ -835,12 +866,6 @@ class TestPygeosInterface:
else:
assert_array_equal(res, expected[0])
@pytest.mark.skipif(
not (compat.USE_SHAPELY_20),
reason=(
"shapely >= 2.0 is required to test sindex.nearest with parameter exclusive"
),
)
@pytest.mark.parametrize("return_distance", [True, False])
@pytest.mark.parametrize(
"return_all,max_distance,exclusive,expected",
@@ -861,7 +886,7 @@ class TestPygeosInterface:
def test_nearest_exclusive(
self, expected, max_distance, return_all, return_distance, exclusive
):
geoms = mod.points(np.arange(5), np.arange(5))
geoms = shapely.points(np.arange(5), np.arange(5))
if max_distance:
# add a non grid point
geoms = np.append(geoms, [Point(1, 2)])
@@ -882,19 +907,6 @@ class TestPygeosInterface:
else:
assert_array_equal(res, expected[0])
@pytest.mark.skipif(
compat.USE_SHAPELY_20 or not (compat.USE_PYGEOS and not compat.PYGEOS_GE_010),
reason="sindex.nearest exclusive parameter requires shapely >= 2.0",
)
def test_nearest_exclusive_unavailable(self):
from shapely.geometry import Point
geoms = [Point((x, y)) for (x, y) in zip(np.arange(5), np.arange(5))]
df = geopandas.GeoDataFrame(geometry=geoms)
with pytest.raises(NotImplementedError, match="requires shapely >= 2.0"):
df.sindex.nearest(geoms, exclusive=True)
# --------------------------- misc tests ---------------------------- #
def test_empty_tree_geometries(self):
@@ -936,23 +948,12 @@ class TestPygeosInterface:
("touches", (2, 0)),
],
)
def test_integration_natural_earth(self, predicate, expected_shape):
def test_integration_natural_earth(
self, predicate, expected_shape, naturalearth_lowres, naturalearth_cities
):
"""Tests output sizes for the naturalearth datasets."""
world = read_file(datasets.get_path("naturalearth_lowres"))
capitals = read_file(datasets.get_path("naturalearth_cities"))
world = read_file(naturalearth_lowres)
capitals = read_file(naturalearth_cities)
res = world.sindex.query(capitals.geometry, predicate)
assert res.shape == expected_shape
@pytest.mark.skipif(not compat.HAS_RTREE, reason="no rtree installed")
def test_old_spatial_index_deprecated():
t1 = Polygon([(0, 0), (1, 0), (1, 1)])
t2 = Polygon([(0, 0), (1, 1), (0, 1)])
stream = ((i, item.bounds, None) for i, item in enumerate([t1, t2]))
with pytest.warns(FutureWarning):
idx = geopandas.sindex.SpatialIndex(stream)
assert list(idx.intersection((0, 0, 1, 1))) == [0, 1]

View File

@@ -1,16 +1,17 @@
import warnings
import numpy as np
from shapely.geometry import Point, Polygon
import pandas as pd
from pandas import DataFrame, Series
from shapely.geometry import Point, Polygon
from geopandas import GeoDataFrame, GeoSeries
from geopandas._compat import HAS_PYPROJ
from geopandas.array import from_shapely
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
import pytest
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
s1 = GeoSeries(
[
@@ -46,9 +47,9 @@ df1 = GeoDataFrame({"col1": [1, 2], "geometry": s1})
df2 = GeoDataFrame({"col1": [1, 2], "geometry": s2})
s4 = s1.copy()
s4.crs = 4326
s4.array.crs = 4326
s5 = s2.copy()
s5.crs = 27700
s5.array.crs = 27700
s6 = GeoSeries(
[
@@ -102,9 +103,10 @@ def test_geodataframe():
assert_geodataframe_equal(df1, df3)
assert_geodataframe_equal(df5, df4, check_like=True)
df5.geom2.crs = 3857
with pytest.raises(AssertionError):
assert_geodataframe_equal(df5, df4, check_like=True)
if HAS_PYPROJ:
df5["geom2"] = df5.geom2.set_crs(3857, allow_override=True)
with pytest.raises(AssertionError):
assert_geodataframe_equal(df5, df4, check_like=True)
def test_equal_nans():
@@ -119,6 +121,7 @@ def test_no_crs():
assert_geodataframe_equal(df1, df2)
@pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not available")
def test_ignore_crs_mismatch():
df1 = GeoDataFrame({"col1": [1, 2], "geometry": s1.copy()}, crs="EPSG:4326")
df2 = GeoDataFrame({"col1": [1, 2], "geometry": s1}, crs="EPSG:31370")

View File

@@ -13,6 +13,15 @@ from geopandas.testing import ( # noqa: F401
HERE = os.path.abspath(os.path.dirname(__file__))
PACKAGE_DIR = os.path.dirname(os.path.dirname(HERE))
_TEST_DATA_DIR = os.path.join(PACKAGE_DIR, "geopandas", "tests", "data")
_NYBB = "zip://" + os.path.join(_TEST_DATA_DIR, "nybb_16a.zip")
_NATURALEARTH_CITIES = os.path.join(
_TEST_DATA_DIR, "naturalearth_cities", "naturalearth_cities.shp"
)
_NATURALEARTH_LOWRES = os.path.join(
_TEST_DATA_DIR, "naturalearth_lowres", "naturalearth_lowres.shp"
)
# mock not used here, but the import from here is used in other modules
try: