that's too much!
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,462 @@
|
||||
"""Tests for the clip module."""
|
||||
|
||||
|
||||
import numpy as np
|
||||
|
||||
import shapely
|
||||
from shapely.geometry import (
|
||||
Polygon,
|
||||
Point,
|
||||
LineString,
|
||||
LinearRing,
|
||||
GeometryCollection,
|
||||
MultiPoint,
|
||||
box,
|
||||
)
|
||||
|
||||
import geopandas
|
||||
from geopandas import GeoDataFrame, GeoSeries, clip
|
||||
|
||||
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
|
||||
import pytest
|
||||
|
||||
from geopandas.tools.clip import _mask_is_list_like_rectangle
|
||||
|
||||
pytestmark = pytest.mark.skip_no_sindex
|
||||
mask_variants_single_rectangle = [
|
||||
"single_rectangle_gdf",
|
||||
"single_rectangle_gdf_list_bounds",
|
||||
"single_rectangle_gdf_tuple_bounds",
|
||||
"single_rectangle_gdf_array_bounds",
|
||||
]
|
||||
mask_variants_large_rectangle = [
|
||||
"larger_single_rectangle_gdf",
|
||||
"larger_single_rectangle_gdf_bounds",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def point_gdf():
|
||||
"""Create a point GeoDataFrame."""
|
||||
pts = np.array([[2, 2], [3, 4], [9, 8], [-12, -15]])
|
||||
gdf = GeoDataFrame([Point(xy) for xy in pts], columns=["geometry"], crs="EPSG:3857")
|
||||
return gdf
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pointsoutside_nooverlap_gdf():
|
||||
"""Create a point GeoDataFrame. Its points are all outside the single
|
||||
rectangle, and its bounds are outside the single rectangle's."""
|
||||
pts = np.array([[5, 15], [15, 15], [15, 20]])
|
||||
gdf = GeoDataFrame([Point(xy) for xy in pts], columns=["geometry"], crs="EPSG:3857")
|
||||
return gdf
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pointsoutside_overlap_gdf():
|
||||
"""Create a point GeoDataFrame. Its points are all outside the single
|
||||
rectangle, and its bounds are overlapping the single rectangle's."""
|
||||
pts = np.array([[5, 15], [15, 15], [15, 5]])
|
||||
gdf = GeoDataFrame([Point(xy) for xy in pts], columns=["geometry"], crs="EPSG:3857")
|
||||
return gdf
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def single_rectangle_gdf():
|
||||
"""Create a single rectangle for clipping."""
|
||||
poly_inters = Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)])
|
||||
gdf = GeoDataFrame([1], geometry=[poly_inters], crs="EPSG:3857")
|
||||
gdf["attr2"] = "site-boundary"
|
||||
return gdf
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def single_rectangle_gdf_tuple_bounds(single_rectangle_gdf):
|
||||
"""Bounds of the created single rectangle"""
|
||||
return tuple(single_rectangle_gdf.total_bounds)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def single_rectangle_gdf_list_bounds(single_rectangle_gdf):
|
||||
"""Bounds of the created single rectangle"""
|
||||
return list(single_rectangle_gdf.total_bounds)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def single_rectangle_gdf_array_bounds(single_rectangle_gdf):
|
||||
"""Bounds of the created single rectangle"""
|
||||
return single_rectangle_gdf.total_bounds
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def larger_single_rectangle_gdf():
|
||||
"""Create a slightly larger rectangle for clipping.
|
||||
The smaller single rectangle is used to test the edge case where slivers
|
||||
are returned when you clip polygons. This fixture is larger which
|
||||
eliminates the slivers in the clip return.
|
||||
"""
|
||||
poly_inters = Polygon([(-5, -5), (-5, 15), (15, 15), (15, -5), (-5, -5)])
|
||||
gdf = GeoDataFrame([1], geometry=[poly_inters], crs="EPSG:3857")
|
||||
gdf["attr2"] = ["study area"]
|
||||
return gdf
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def larger_single_rectangle_gdf_bounds(larger_single_rectangle_gdf):
|
||||
"""Bounds of the created single rectangle"""
|
||||
return tuple(larger_single_rectangle_gdf.total_bounds)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def buffered_locations(point_gdf):
|
||||
"""Buffer points to create a multi-polygon."""
|
||||
buffered_locs = point_gdf
|
||||
buffered_locs["geometry"] = buffered_locs.buffer(4)
|
||||
buffered_locs["type"] = "plot"
|
||||
return buffered_locs
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def donut_geometry(buffered_locations, single_rectangle_gdf):
|
||||
"""Make a geometry with a hole in the middle (a donut)."""
|
||||
donut = geopandas.overlay(
|
||||
buffered_locations, single_rectangle_gdf, how="symmetric_difference"
|
||||
)
|
||||
return donut
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def two_line_gdf():
|
||||
"""Create Line Objects For Testing"""
|
||||
linea = LineString([(1, 1), (2, 2), (3, 2), (5, 3)])
|
||||
lineb = LineString([(3, 4), (5, 7), (12, 2), (10, 5), (9, 7.5)])
|
||||
gdf = GeoDataFrame([1, 2], geometry=[linea, lineb], crs="EPSG:3857")
|
||||
return gdf
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multi_poly_gdf(donut_geometry):
|
||||
"""Create a multi-polygon GeoDataFrame."""
|
||||
multi_poly = donut_geometry.unary_union
|
||||
out_df = GeoDataFrame(geometry=GeoSeries(multi_poly), crs="EPSG:3857")
|
||||
out_df["attr"] = ["pool"]
|
||||
return out_df
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multi_line(two_line_gdf):
|
||||
"""Create a multi-line GeoDataFrame.
|
||||
This GDF has one multiline and one regular line."""
|
||||
# Create a single and multi line object
|
||||
multiline_feat = two_line_gdf.unary_union
|
||||
linec = LineString([(2, 1), (3, 1), (4, 1), (5, 2)])
|
||||
out_df = GeoDataFrame(geometry=GeoSeries([multiline_feat, linec]), crs="EPSG:3857")
|
||||
out_df["attr"] = ["road", "stream"]
|
||||
return out_df
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multi_point(point_gdf):
|
||||
"""Create a multi-point GeoDataFrame."""
|
||||
multi_point = point_gdf.unary_union
|
||||
out_df = GeoDataFrame(
|
||||
geometry=GeoSeries(
|
||||
[multi_point, Point(2, 5), Point(-11, -14), Point(-10, -12)]
|
||||
),
|
||||
crs="EPSG:3857",
|
||||
)
|
||||
out_df["attr"] = ["tree", "another tree", "shrub", "berries"]
|
||||
return out_df
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mixed_gdf():
|
||||
"""Create a Mixed Polygon and LineString For Testing"""
|
||||
point = Point(2, 3)
|
||||
line = LineString([(1, 1), (2, 2), (3, 2), (5, 3), (12, 1)])
|
||||
poly = Polygon([(3, 4), (5, 2), (12, 2), (10, 5), (9, 7.5)])
|
||||
ring = LinearRing([(1, 1), (2, 2), (3, 2), (5, 3), (12, 1)])
|
||||
gdf = GeoDataFrame(
|
||||
[1, 2, 3, 4], geometry=[point, poly, line, ring], crs="EPSG:3857"
|
||||
)
|
||||
return gdf
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def geomcol_gdf():
|
||||
"""Create a Mixed Polygon and LineString For Testing"""
|
||||
point = Point(2, 3)
|
||||
poly = Polygon([(3, 4), (5, 2), (12, 2), (10, 5), (9, 7.5)])
|
||||
coll = GeometryCollection([point, poly])
|
||||
gdf = GeoDataFrame([1], geometry=[coll], crs="EPSG:3857")
|
||||
return gdf
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sliver_line():
|
||||
"""Create a line that will create a point when clipped."""
|
||||
linea = LineString([(10, 5), (13, 5), (15, 5)])
|
||||
lineb = LineString([(1, 1), (2, 2), (3, 2), (5, 3), (12, 1)])
|
||||
gdf = GeoDataFrame([1, 2], geometry=[linea, lineb], crs="EPSG:3857")
|
||||
return gdf
|
||||
|
||||
|
||||
def test_not_gdf(single_rectangle_gdf):
|
||||
"""Non-GeoDataFrame inputs raise attribute errors."""
|
||||
with pytest.raises(TypeError):
|
||||
clip((2, 3), single_rectangle_gdf)
|
||||
with pytest.raises(TypeError):
|
||||
clip(single_rectangle_gdf, "foobar")
|
||||
with pytest.raises(TypeError):
|
||||
clip(single_rectangle_gdf, (1, 2, 3))
|
||||
with pytest.raises(TypeError):
|
||||
clip(single_rectangle_gdf, (1, 2, 3, 4, 5))
|
||||
|
||||
|
||||
def test_non_overlapping_geoms():
|
||||
"""Test that a bounding box returns empty if the extents don't overlap"""
|
||||
unit_box = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
|
||||
unit_gdf = GeoDataFrame([1], geometry=[unit_box], crs="EPSG:3857")
|
||||
non_overlapping_gdf = unit_gdf.copy()
|
||||
non_overlapping_gdf = non_overlapping_gdf.geometry.apply(
|
||||
lambda x: shapely.affinity.translate(x, xoff=20)
|
||||
)
|
||||
out = clip(unit_gdf, non_overlapping_gdf)
|
||||
assert_geodataframe_equal(out, unit_gdf.iloc[:0])
|
||||
out2 = clip(unit_gdf.geometry, non_overlapping_gdf)
|
||||
assert_geoseries_equal(out2, GeoSeries(crs=unit_gdf.crs))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mask_fixture_name", mask_variants_single_rectangle)
|
||||
class TestClipWithSingleRectangleGdf:
|
||||
@pytest.fixture
|
||||
def mask(self, mask_fixture_name, request):
|
||||
return request.getfixturevalue(mask_fixture_name)
|
||||
|
||||
def test_returns_gdf(self, point_gdf, mask):
|
||||
"""Test that function returns a GeoDataFrame (or GDF-like) object."""
|
||||
out = clip(point_gdf, mask)
|
||||
assert isinstance(out, GeoDataFrame)
|
||||
|
||||
def test_returns_series(self, point_gdf, mask):
|
||||
"""Test that function returns a GeoSeries if GeoSeries is passed."""
|
||||
out = clip(point_gdf.geometry, mask)
|
||||
assert isinstance(out, GeoSeries)
|
||||
|
||||
def test_clip_points(self, point_gdf, mask):
|
||||
"""Test clipping a points GDF with a generic polygon geometry."""
|
||||
clip_pts = clip(point_gdf, mask)
|
||||
pts = np.array([[2, 2], [3, 4], [9, 8]])
|
||||
exp = GeoDataFrame(
|
||||
[Point(xy) for xy in pts], columns=["geometry"], crs="EPSG:3857"
|
||||
)
|
||||
assert_geodataframe_equal(clip_pts, exp)
|
||||
|
||||
def test_clip_points_geom_col_rename(self, point_gdf, mask):
|
||||
"""Test clipping a points GDF with a generic polygon geometry."""
|
||||
point_gdf_geom_col_rename = point_gdf.rename_geometry("geometry2")
|
||||
clip_pts = clip(point_gdf_geom_col_rename, mask)
|
||||
pts = np.array([[2, 2], [3, 4], [9, 8]])
|
||||
exp = GeoDataFrame(
|
||||
[Point(xy) for xy in pts],
|
||||
columns=["geometry2"],
|
||||
crs="EPSG:3857",
|
||||
geometry="geometry2",
|
||||
)
|
||||
assert_geodataframe_equal(clip_pts, exp)
|
||||
|
||||
def test_clip_poly(self, buffered_locations, mask):
|
||||
"""Test clipping a polygon GDF with a generic polygon geometry."""
|
||||
clipped_poly = clip(buffered_locations, mask)
|
||||
assert len(clipped_poly.geometry) == 3
|
||||
assert all(clipped_poly.geom_type == "Polygon")
|
||||
|
||||
def test_clip_poly_geom_col_rename(self, buffered_locations, mask):
|
||||
"""Test clipping a polygon GDF with a generic polygon geometry."""
|
||||
|
||||
poly_gdf_geom_col_rename = buffered_locations.rename_geometry("geometry2")
|
||||
clipped_poly = clip(poly_gdf_geom_col_rename, mask)
|
||||
assert len(clipped_poly.geometry) == 3
|
||||
assert "geometry" not in clipped_poly.keys()
|
||||
assert "geometry2" in clipped_poly.keys()
|
||||
|
||||
def test_clip_poly_series(self, buffered_locations, mask):
|
||||
"""Test clipping a polygon GDF with a generic polygon geometry."""
|
||||
clipped_poly = clip(buffered_locations.geometry, mask)
|
||||
assert len(clipped_poly) == 3
|
||||
assert all(clipped_poly.geom_type == "Polygon")
|
||||
|
||||
def test_clip_multipoly_keep_geom_type(self, multi_poly_gdf, mask):
|
||||
"""Test a multi poly object where the return includes a sliver.
|
||||
Also the bounds of the object should == the bounds of the clip object
|
||||
if they fully overlap (as they do in these fixtures)."""
|
||||
clipped = clip(multi_poly_gdf, mask, keep_geom_type=True)
|
||||
expected_bounds = (
|
||||
mask if _mask_is_list_like_rectangle(mask) else mask.total_bounds
|
||||
)
|
||||
assert np.array_equal(clipped.total_bounds, expected_bounds)
|
||||
# Assert returned data is a not geometry collection
|
||||
assert (clipped.geom_type.isin(["Polygon", "MultiPolygon"])).all()
|
||||
|
||||
def test_clip_multiline(self, multi_line, mask):
|
||||
"""Test that clipping a multiline feature with a poly returns expected
|
||||
output."""
|
||||
clipped = clip(multi_line, mask)
|
||||
assert clipped.geom_type[0] == "MultiLineString"
|
||||
|
||||
def test_clip_multipoint(self, multi_point, mask):
|
||||
"""Clipping a multipoint feature with a polygon works as expected.
|
||||
should return a geodataframe with a single multi point feature"""
|
||||
clipped = clip(multi_point, mask)
|
||||
assert clipped.geom_type[0] == "MultiPoint"
|
||||
assert hasattr(clipped, "attr")
|
||||
# All points should intersect the clip geom
|
||||
assert len(clipped) == 2
|
||||
clipped_mutltipoint = MultiPoint(
|
||||
[
|
||||
Point(2, 2),
|
||||
Point(3, 4),
|
||||
Point(9, 8),
|
||||
]
|
||||
)
|
||||
assert clipped.iloc[0].geometry.wkt == clipped_mutltipoint.wkt
|
||||
shape_for_points = (
|
||||
box(*mask) if _mask_is_list_like_rectangle(mask) else mask.unary_union
|
||||
)
|
||||
assert all(clipped.intersects(shape_for_points))
|
||||
|
||||
def test_clip_lines(self, two_line_gdf, mask):
|
||||
"""Test what happens when you give the clip_extent a line GDF."""
|
||||
clip_line = clip(two_line_gdf, mask)
|
||||
assert len(clip_line.geometry) == 2
|
||||
|
||||
def test_mixed_geom(self, mixed_gdf, mask):
|
||||
"""Test clipping a mixed GeoDataFrame"""
|
||||
clipped = clip(mixed_gdf, mask)
|
||||
assert (
|
||||
clipped.geom_type[0] == "Point"
|
||||
and clipped.geom_type[1] == "Polygon"
|
||||
and clipped.geom_type[2] == "LineString"
|
||||
)
|
||||
|
||||
def test_mixed_series(self, mixed_gdf, mask):
|
||||
"""Test clipping a mixed GeoSeries"""
|
||||
clipped = clip(mixed_gdf.geometry, mask)
|
||||
assert (
|
||||
clipped.geom_type[0] == "Point"
|
||||
and clipped.geom_type[1] == "Polygon"
|
||||
and clipped.geom_type[2] == "LineString"
|
||||
)
|
||||
|
||||
def test_clip_with_line_extra_geom(self, sliver_line, mask):
|
||||
"""When the output of a clipped line returns a geom collection,
|
||||
and keep_geom_type is True, no geometry collections should be returned."""
|
||||
clipped = clip(sliver_line, mask, keep_geom_type=True)
|
||||
assert len(clipped.geometry) == 1
|
||||
# Assert returned data is a not geometry collection
|
||||
assert not (clipped.geom_type == "GeometryCollection").any()
|
||||
|
||||
def test_clip_no_box_overlap(self, pointsoutside_nooverlap_gdf, mask):
|
||||
"""Test clip when intersection is empty and boxes do not overlap."""
|
||||
clipped = clip(pointsoutside_nooverlap_gdf, mask)
|
||||
assert len(clipped) == 0
|
||||
|
||||
def test_clip_box_overlap(self, pointsoutside_overlap_gdf, mask):
|
||||
"""Test clip when intersection is empty and boxes do overlap."""
|
||||
clipped = clip(pointsoutside_overlap_gdf, mask)
|
||||
assert len(clipped) == 0
|
||||
|
||||
def test_warning_extra_geoms_mixed(self, mixed_gdf, mask):
|
||||
"""Test the correct warnings are raised if keep_geom_type is
|
||||
called on a mixed GDF"""
|
||||
with pytest.warns(UserWarning):
|
||||
clip(mixed_gdf, mask, keep_geom_type=True)
|
||||
|
||||
def test_warning_geomcoll(self, geomcol_gdf, mask):
|
||||
"""Test the correct warnings are raised if keep_geom_type is
|
||||
called on a GDF with GeometryCollection"""
|
||||
with pytest.warns(UserWarning):
|
||||
clip(geomcol_gdf, mask, keep_geom_type=True)
|
||||
|
||||
|
||||
def test_clip_line_keep_slivers(sliver_line, single_rectangle_gdf):
|
||||
"""Test the correct output if a point is returned
|
||||
from a line only geometry type."""
|
||||
clipped = clip(sliver_line, single_rectangle_gdf)
|
||||
# Assert returned data is a geometry collection given sliver geoms
|
||||
assert "Point" == clipped.geom_type[0]
|
||||
assert "LineString" == clipped.geom_type[1]
|
||||
|
||||
|
||||
def test_clip_multipoly_keep_slivers(multi_poly_gdf, single_rectangle_gdf):
|
||||
"""Test a multi poly object where the return includes a sliver.
|
||||
Also the bounds of the object should == the bounds of the clip object
|
||||
if they fully overlap (as they do in these fixtures)."""
|
||||
clipped = clip(multi_poly_gdf, single_rectangle_gdf)
|
||||
assert np.array_equal(clipped.total_bounds, single_rectangle_gdf.total_bounds)
|
||||
# Assert returned data is a geometry collection given sliver geoms
|
||||
assert "GeometryCollection" in clipped.geom_type[0]
|
||||
|
||||
|
||||
def test_warning_crs_mismatch(point_gdf, single_rectangle_gdf):
|
||||
with pytest.warns(UserWarning, match="CRS mismatch between the CRS"):
|
||||
clip(point_gdf, single_rectangle_gdf.to_crs(4326))
|
||||
|
||||
|
||||
def test_clip_with_polygon(single_rectangle_gdf):
|
||||
"""Test clip when using a shapely object"""
|
||||
polygon = Polygon([(0, 0), (5, 12), (10, 0), (0, 0)])
|
||||
clipped = clip(single_rectangle_gdf, polygon)
|
||||
exp_poly = polygon.intersection(
|
||||
Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)])
|
||||
)
|
||||
exp = GeoDataFrame([1], geometry=[exp_poly], crs="EPSG:3857")
|
||||
exp["attr2"] = "site-boundary"
|
||||
assert_geodataframe_equal(clipped, exp)
|
||||
|
||||
|
||||
def test_clip_with_multipolygon(buffered_locations, single_rectangle_gdf):
|
||||
"""Test clipping a polygon with a multipolygon."""
|
||||
multi = buffered_locations.dissolve(by="type").reset_index()
|
||||
clipped = clip(single_rectangle_gdf, multi)
|
||||
assert clipped.geom_type[0] == "Polygon"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mask_fixture_name",
|
||||
mask_variants_large_rectangle,
|
||||
)
|
||||
def test_clip_single_multipoly_no_extra_geoms(
|
||||
buffered_locations, mask_fixture_name, request
|
||||
):
|
||||
"""When clipping a multi-polygon feature, no additional geom types
|
||||
should be returned."""
|
||||
masks = request.getfixturevalue(mask_fixture_name)
|
||||
multi = buffered_locations.dissolve(by="type").reset_index()
|
||||
clipped = clip(multi, masks)
|
||||
assert clipped.geom_type[0] == "Polygon"
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:All-NaN slice encountered")
|
||||
@pytest.mark.parametrize(
|
||||
"mask",
|
||||
[
|
||||
Polygon(),
|
||||
(np.nan,) * 4,
|
||||
(np.nan, 0, np.nan, 1),
|
||||
GeoSeries([Polygon(), Polygon()], crs="EPSG:3857"),
|
||||
GeoSeries([Polygon(), Polygon()], crs="EPSG:3857").to_frame(),
|
||||
GeoSeries([], crs="EPSG:3857"),
|
||||
GeoSeries([], crs="EPSG:3857").to_frame(),
|
||||
],
|
||||
)
|
||||
def test_clip_empty_mask(buffered_locations, mask):
|
||||
"""Test that clipping with empty mask returns an empty result."""
|
||||
clipped = clip(buffered_locations, mask)
|
||||
assert_geodataframe_equal(
|
||||
clipped,
|
||||
GeoDataFrame([], columns=["geometry", "type"], crs="EPSG:3857"),
|
||||
check_index_type=False,
|
||||
)
|
||||
clipped = clip(buffered_locations.geometry, mask)
|
||||
assert_geoseries_equal(clipped, GeoSeries([], crs="EPSG:3857"))
|
||||
@@ -0,0 +1,75 @@
|
||||
import numpy as np
|
||||
from shapely.geometry import Point
|
||||
from shapely.wkt import loads
|
||||
|
||||
import geopandas
|
||||
|
||||
import pytest
|
||||
from pandas.testing import assert_series_equal
|
||||
|
||||
|
||||
def test_hilbert_distance():
|
||||
# test the actual Hilbert Code algorithm against some hardcoded values
|
||||
geoms = geopandas.GeoSeries.from_wkt(
|
||||
[
|
||||
"POINT (0 0)",
|
||||
"POINT (1 1)",
|
||||
"POINT (1 0)",
|
||||
"POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
|
||||
]
|
||||
)
|
||||
result = geoms.hilbert_distance(total_bounds=(0, 0, 1, 1), level=2)
|
||||
assert result.tolist() == [0, 10, 15, 2]
|
||||
|
||||
result = geoms.hilbert_distance(total_bounds=(0, 0, 1, 1), level=3)
|
||||
assert result.tolist() == [0, 42, 63, 10]
|
||||
|
||||
result = geoms.hilbert_distance(total_bounds=(0, 0, 1, 1), level=16)
|
||||
assert result.tolist() == [0, 2863311530, 4294967295, 715827882]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def geoseries_points():
|
||||
p1 = Point(1, 2)
|
||||
p2 = Point(2, 3)
|
||||
p3 = Point(3, 4)
|
||||
p4 = Point(4, 1)
|
||||
return geopandas.GeoSeries([p1, p2, p3, p4])
|
||||
|
||||
|
||||
def test_hilbert_distance_level(geoseries_points):
|
||||
with pytest.raises(ValueError):
|
||||
geoseries_points.hilbert_distance(level=20)
|
||||
|
||||
|
||||
def test_specified_total_bounds(geoseries_points):
|
||||
result = geoseries_points.hilbert_distance(
|
||||
total_bounds=geoseries_points.total_bounds
|
||||
)
|
||||
expected = geoseries_points.hilbert_distance()
|
||||
assert_series_equal(result, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"empty",
|
||||
[
|
||||
None,
|
||||
loads("POLYGON EMPTY"),
|
||||
],
|
||||
)
|
||||
def test_empty(geoseries_points, empty):
|
||||
s = geoseries_points
|
||||
s.iloc[-1] = empty
|
||||
with pytest.raises(
|
||||
ValueError, match="cannot be computed on a GeoSeries with empty"
|
||||
):
|
||||
s.hilbert_distance()
|
||||
|
||||
|
||||
def test_zero_width():
|
||||
# special case of all points on the same line -> avoid warnings because
|
||||
# of division by 0 and introducing NaN
|
||||
s = geopandas.GeoSeries([Point(0, 0), Point(0, 2), Point(0, 1)])
|
||||
with np.errstate(all="raise"):
|
||||
result = s.hilbert_distance()
|
||||
assert np.array(result).argsort().tolist() == [0, 2, 1]
|
||||
@@ -0,0 +1,57 @@
|
||||
import pytest
|
||||
import numpy
|
||||
import geopandas
|
||||
import geopandas._compat as compat
|
||||
|
||||
from geopandas.tools._random import uniform
|
||||
|
||||
multipolygons = geopandas.read_file(geopandas.datasets.get_path("nybb")).geometry
|
||||
polygons = multipolygons.explode(ignore_index=True).geometry
|
||||
multilinestrings = multipolygons.boundary
|
||||
linestrings = polygons.boundary
|
||||
points = multipolygons.centroid
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not (compat.USE_PYGEOS or compat.USE_SHAPELY_20),
|
||||
reason="array input in interpolate not implemented for shapely<2",
|
||||
)
|
||||
@pytest.mark.parametrize("size", [10, 100])
|
||||
@pytest.mark.parametrize(
|
||||
"geom", [multipolygons[0], polygons[0], multilinestrings[0], linestrings[0]]
|
||||
)
|
||||
def test_uniform(geom, size):
|
||||
sample = uniform(geom, size=size, rng=1)
|
||||
sample_series = geopandas.GeoSeries(sample).explode().reset_index(drop=True)
|
||||
assert len(sample_series) == size
|
||||
sample_in_geom = sample_series.buffer(0.00000001).sindex.query(
|
||||
geom, predicate="intersects"
|
||||
)
|
||||
assert len(sample_in_geom) == size
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not (compat.USE_PYGEOS or compat.USE_SHAPELY_20),
|
||||
reason="array input in interpolate not implemented for shapely<2",
|
||||
)
|
||||
def test_uniform_unsupported():
|
||||
with pytest.warns(UserWarning, match="Sampling is not supported"):
|
||||
sample = uniform(points[0], size=10, rng=1)
|
||||
assert sample.is_empty
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not (compat.USE_PYGEOS or compat.USE_SHAPELY_20),
|
||||
reason="array input in interpolate not implemented for shapely<2",
|
||||
)
|
||||
def test_uniform_generator():
|
||||
sample = uniform(polygons[0], size=10, rng=1)
|
||||
sample2 = uniform(polygons[0], size=10, rng=1)
|
||||
assert sample.equals(sample2)
|
||||
|
||||
generator = numpy.random.default_rng(seed=1)
|
||||
gen_sample = uniform(polygons[0], size=10, rng=generator)
|
||||
gen_sample2 = uniform(polygons[0], size=10, rng=generator)
|
||||
|
||||
assert sample.equals(gen_sample)
|
||||
assert not sample.equals(gen_sample2)
|
||||
@@ -0,0 +1,960 @@
|
||||
import math
|
||||
from typing import Sequence
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import shapely
|
||||
|
||||
from shapely.geometry import Point, Polygon, GeometryCollection
|
||||
|
||||
import geopandas
|
||||
import geopandas._compat as compat
|
||||
from geopandas import GeoDataFrame, GeoSeries, read_file, sjoin, sjoin_nearest
|
||||
from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
|
||||
|
||||
from pandas.testing import assert_frame_equal, assert_series_equal
|
||||
import pytest
|
||||
|
||||
|
||||
TEST_NEAREST = compat.USE_SHAPELY_20 or (compat.PYGEOS_GE_010 and compat.USE_PYGEOS)
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skip_no_sindex
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def dfs(request):
|
||||
polys1 = GeoSeries(
|
||||
[
|
||||
Polygon([(0, 0), (5, 0), (5, 5), (0, 5)]),
|
||||
Polygon([(5, 5), (6, 5), (6, 6), (5, 6)]),
|
||||
Polygon([(6, 0), (9, 0), (9, 3), (6, 3)]),
|
||||
]
|
||||
)
|
||||
|
||||
polys2 = GeoSeries(
|
||||
[
|
||||
Polygon([(1, 1), (4, 1), (4, 4), (1, 4)]),
|
||||
Polygon([(4, 4), (7, 4), (7, 7), (4, 7)]),
|
||||
Polygon([(7, 7), (10, 7), (10, 10), (7, 10)]),
|
||||
]
|
||||
)
|
||||
|
||||
df1 = GeoDataFrame({"geometry": polys1, "df1": [0, 1, 2]})
|
||||
df2 = GeoDataFrame({"geometry": polys2, "df2": [3, 4, 5]})
|
||||
|
||||
if request.param == "string-index":
|
||||
df1.index = ["a", "b", "c"]
|
||||
df2.index = ["d", "e", "f"]
|
||||
|
||||
if request.param == "named-index":
|
||||
df1.index.name = "df1_ix"
|
||||
df2.index.name = "df2_ix"
|
||||
|
||||
if request.param == "multi-index":
|
||||
i1 = ["a", "b", "c"]
|
||||
i2 = ["d", "e", "f"]
|
||||
df1 = df1.set_index([i1, i2])
|
||||
df2 = df2.set_index([i2, i1])
|
||||
|
||||
if request.param == "named-multi-index":
|
||||
i1 = ["a", "b", "c"]
|
||||
i2 = ["d", "e", "f"]
|
||||
df1 = df1.set_index([i1, i2])
|
||||
df2 = df2.set_index([i2, i1])
|
||||
df1.index.names = ["df1_ix1", "df1_ix2"]
|
||||
df2.index.names = ["df2_ix1", "df2_ix2"]
|
||||
|
||||
# construction expected frames
|
||||
expected = {}
|
||||
|
||||
part1 = df1.copy().reset_index().rename(columns={"index": "index_left"})
|
||||
part2 = (
|
||||
df2.copy()
|
||||
.iloc[[0, 1, 1, 2]]
|
||||
.reset_index()
|
||||
.rename(columns={"index": "index_right"})
|
||||
)
|
||||
part1["_merge"] = [0, 1, 2]
|
||||
part2["_merge"] = [0, 0, 1, 3]
|
||||
exp = pd.merge(part1, part2, on="_merge", how="outer")
|
||||
expected["intersects"] = exp.drop("_merge", axis=1).copy()
|
||||
|
||||
part1 = df1.copy().reset_index().rename(columns={"index": "index_left"})
|
||||
part2 = df2.copy().reset_index().rename(columns={"index": "index_right"})
|
||||
part1["_merge"] = [0, 1, 2]
|
||||
part2["_merge"] = [0, 3, 3]
|
||||
exp = pd.merge(part1, part2, on="_merge", how="outer")
|
||||
expected["contains"] = exp.drop("_merge", axis=1).copy()
|
||||
|
||||
part1["_merge"] = [0, 1, 2]
|
||||
part2["_merge"] = [3, 1, 3]
|
||||
exp = pd.merge(part1, part2, on="_merge", how="outer")
|
||||
expected["within"] = exp.drop("_merge", axis=1).copy()
|
||||
|
||||
return [request.param, df1, df2, expected]
|
||||
|
||||
|
||||
class TestSpatialJoin:
|
||||
@pytest.mark.parametrize(
|
||||
"how, lsuffix, rsuffix, expected_cols",
|
||||
[
|
||||
("left", "left", "right", {"col_left", "col_right", "index_right"}),
|
||||
("inner", "left", "right", {"col_left", "col_right", "index_right"}),
|
||||
("right", "left", "right", {"col_left", "col_right", "index_left"}),
|
||||
("left", "lft", "rgt", {"col_lft", "col_rgt", "index_rgt"}),
|
||||
("inner", "lft", "rgt", {"col_lft", "col_rgt", "index_rgt"}),
|
||||
("right", "lft", "rgt", {"col_lft", "col_rgt", "index_lft"}),
|
||||
],
|
||||
)
|
||||
def test_suffixes(self, how: str, lsuffix: str, rsuffix: str, expected_cols):
|
||||
left = GeoDataFrame({"col": [1], "geometry": [Point(0, 0)]})
|
||||
right = GeoDataFrame({"col": [1], "geometry": [Point(0, 0)]})
|
||||
joined = sjoin(left, right, how=how, lsuffix=lsuffix, rsuffix=rsuffix)
|
||||
assert set(joined.columns) == expected_cols | {"geometry"}
|
||||
|
||||
@pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True)
|
||||
def test_crs_mismatch(self, dfs):
|
||||
index, df1, df2, expected = dfs
|
||||
df1.crs = "epsg:4326"
|
||||
with pytest.warns(UserWarning, match="CRS mismatch between the CRS"):
|
||||
sjoin(df1, df2)
|
||||
|
||||
@pytest.mark.parametrize("dfs", ["default-index"], indirect=True)
|
||||
@pytest.mark.parametrize("op", ["intersects", "contains", "within"])
|
||||
def test_deprecated_op_param(self, dfs, op):
|
||||
_, df1, df2, _ = dfs
|
||||
with pytest.warns(FutureWarning, match="`op` parameter is deprecated"):
|
||||
sjoin(df1, df2, op=op)
|
||||
|
||||
@pytest.mark.parametrize("dfs", ["default-index"], indirect=True)
|
||||
@pytest.mark.parametrize("op", ["intersects", "contains", "within"])
|
||||
@pytest.mark.parametrize("predicate", ["contains", "within"])
|
||||
def test_deprecated_op_param_nondefault_predicate(self, dfs, op, predicate):
|
||||
_, df1, df2, _ = dfs
|
||||
match = "use the `predicate` parameter instead"
|
||||
if op != predicate:
|
||||
warntype = UserWarning
|
||||
match = (
|
||||
"`predicate` will be overridden by the value of `op`" # noqa: ISC003
|
||||
+ r"(.|\s)*"
|
||||
+ match
|
||||
)
|
||||
else:
|
||||
warntype = FutureWarning
|
||||
with pytest.warns(warntype, match=match):
|
||||
sjoin(df1, df2, predicate=predicate, op=op)
|
||||
|
||||
@pytest.mark.parametrize("dfs", ["default-index"], indirect=True)
|
||||
def test_unknown_kwargs(self, dfs):
|
||||
_, df1, df2, _ = dfs
|
||||
with pytest.raises(
|
||||
TypeError,
|
||||
match=r"sjoin\(\) got an unexpected keyword argument 'extra_param'",
|
||||
):
|
||||
sjoin(df1, df2, extra_param="test")
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:The `op` parameter:FutureWarning")
|
||||
@pytest.mark.parametrize(
|
||||
"dfs",
|
||||
[
|
||||
"default-index",
|
||||
"string-index",
|
||||
"named-index",
|
||||
"multi-index",
|
||||
"named-multi-index",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.parametrize("predicate", ["intersects", "contains", "within"])
|
||||
@pytest.mark.parametrize("predicate_kw", ["predicate", "op"])
|
||||
def test_inner(self, predicate, predicate_kw, dfs):
|
||||
index, df1, df2, expected = dfs
|
||||
|
||||
res = sjoin(df1, df2, how="inner", **{predicate_kw: predicate})
|
||||
|
||||
exp = expected[predicate].dropna().copy()
|
||||
exp = exp.drop("geometry_y", axis=1).rename(columns={"geometry_x": "geometry"})
|
||||
exp[["df1", "df2"]] = exp[["df1", "df2"]].astype("int64")
|
||||
if index == "default-index":
|
||||
exp[["index_left", "index_right"]] = exp[
|
||||
["index_left", "index_right"]
|
||||
].astype("int64")
|
||||
if index == "named-index":
|
||||
exp[["df1_ix", "df2_ix"]] = exp[["df1_ix", "df2_ix"]].astype("int64")
|
||||
exp = exp.set_index("df1_ix").rename(columns={"df2_ix": "index_right"})
|
||||
if index in ["default-index", "string-index"]:
|
||||
exp = exp.set_index("index_left")
|
||||
exp.index.name = None
|
||||
if index == "multi-index":
|
||||
exp = exp.set_index(["level_0_x", "level_1_x"]).rename(
|
||||
columns={"level_0_y": "index_right0", "level_1_y": "index_right1"}
|
||||
)
|
||||
exp.index.names = df1.index.names
|
||||
if index == "named-multi-index":
|
||||
exp = exp.set_index(["df1_ix1", "df1_ix2"]).rename(
|
||||
columns={"df2_ix1": "index_right0", "df2_ix2": "index_right1"}
|
||||
)
|
||||
exp.index.names = df1.index.names
|
||||
|
||||
assert_frame_equal(res, exp)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dfs",
|
||||
[
|
||||
"default-index",
|
||||
"string-index",
|
||||
"named-index",
|
||||
"multi-index",
|
||||
"named-multi-index",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.parametrize("predicate", ["intersects", "contains", "within"])
|
||||
def test_left(self, predicate, dfs):
|
||||
index, df1, df2, expected = dfs
|
||||
|
||||
res = sjoin(df1, df2, how="left", predicate=predicate)
|
||||
|
||||
if index in ["default-index", "string-index"]:
|
||||
exp = expected[predicate].dropna(subset=["index_left"]).copy()
|
||||
elif index == "named-index":
|
||||
exp = expected[predicate].dropna(subset=["df1_ix"]).copy()
|
||||
elif index == "multi-index":
|
||||
exp = expected[predicate].dropna(subset=["level_0_x"]).copy()
|
||||
elif index == "named-multi-index":
|
||||
exp = expected[predicate].dropna(subset=["df1_ix1"]).copy()
|
||||
exp = exp.drop("geometry_y", axis=1).rename(columns={"geometry_x": "geometry"})
|
||||
exp["df1"] = exp["df1"].astype("int64")
|
||||
if index == "default-index":
|
||||
exp["index_left"] = exp["index_left"].astype("int64")
|
||||
# TODO: in result the dtype is object
|
||||
res["index_right"] = res["index_right"].astype(float)
|
||||
elif index == "named-index":
|
||||
exp[["df1_ix"]] = exp[["df1_ix"]].astype("int64")
|
||||
exp = exp.set_index("df1_ix").rename(columns={"df2_ix": "index_right"})
|
||||
if index in ["default-index", "string-index"]:
|
||||
exp = exp.set_index("index_left")
|
||||
exp.index.name = None
|
||||
if index == "multi-index":
|
||||
exp = exp.set_index(["level_0_x", "level_1_x"]).rename(
|
||||
columns={"level_0_y": "index_right0", "level_1_y": "index_right1"}
|
||||
)
|
||||
exp.index.names = df1.index.names
|
||||
if index == "named-multi-index":
|
||||
exp = exp.set_index(["df1_ix1", "df1_ix2"]).rename(
|
||||
columns={"df2_ix1": "index_right0", "df2_ix2": "index_right1"}
|
||||
)
|
||||
exp.index.names = df1.index.names
|
||||
|
||||
assert_frame_equal(res, exp)
|
||||
|
||||
def test_empty_join(self):
|
||||
# Check joins resulting in empty gdfs.
|
||||
polygons = geopandas.GeoDataFrame(
|
||||
{
|
||||
"col2": [1, 2],
|
||||
"geometry": [
|
||||
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
|
||||
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
|
||||
],
|
||||
}
|
||||
)
|
||||
not_in = geopandas.GeoDataFrame({"col1": [1], "geometry": [Point(-0.5, 0.5)]})
|
||||
empty = sjoin(not_in, polygons, how="left", predicate="intersects")
|
||||
assert empty.index_right.isnull().all()
|
||||
empty = sjoin(not_in, polygons, how="right", predicate="intersects")
|
||||
assert empty.index_left.isnull().all()
|
||||
empty = sjoin(not_in, polygons, how="inner", predicate="intersects")
|
||||
assert empty.empty
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"predicate",
|
||||
[
|
||||
"contains",
|
||||
"contains_properly",
|
||||
"covered_by",
|
||||
"covers",
|
||||
"crosses",
|
||||
"intersects",
|
||||
"touches",
|
||||
"within",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"empty",
|
||||
[
|
||||
GeoDataFrame(geometry=[GeometryCollection(), GeometryCollection()]),
|
||||
GeoDataFrame(geometry=GeoSeries()),
|
||||
],
|
||||
)
|
||||
def test_join_with_empty(self, predicate, empty):
|
||||
# Check joins with empty geometry columns/dataframes.
|
||||
polygons = geopandas.GeoDataFrame(
|
||||
{
|
||||
"col2": [1, 2],
|
||||
"geometry": [
|
||||
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
|
||||
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
|
||||
],
|
||||
}
|
||||
)
|
||||
result = sjoin(empty, polygons, how="left", predicate=predicate)
|
||||
assert result.index_right.isnull().all()
|
||||
result = sjoin(empty, polygons, how="right", predicate=predicate)
|
||||
assert result.index_left.isnull().all()
|
||||
result = sjoin(empty, polygons, how="inner", predicate=predicate)
|
||||
assert result.empty
|
||||
|
||||
@pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True)
|
||||
def test_sjoin_invalid_args(self, dfs):
|
||||
index, df1, df2, expected = dfs
|
||||
|
||||
with pytest.raises(ValueError, match="'left_df' should be GeoDataFrame"):
|
||||
sjoin(df1.geometry, df2)
|
||||
|
||||
with pytest.raises(ValueError, match="'right_df' should be GeoDataFrame"):
|
||||
sjoin(df1, df2.geometry)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dfs",
|
||||
[
|
||||
"default-index",
|
||||
"string-index",
|
||||
"named-index",
|
||||
"multi-index",
|
||||
"named-multi-index",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.parametrize("predicate", ["intersects", "contains", "within"])
|
||||
def test_right(self, predicate, dfs):
|
||||
index, df1, df2, expected = dfs
|
||||
|
||||
res = sjoin(df1, df2, how="right", predicate=predicate)
|
||||
|
||||
if index in ["default-index", "string-index"]:
|
||||
exp = expected[predicate].dropna(subset=["index_right"]).copy()
|
||||
elif index == "named-index":
|
||||
exp = expected[predicate].dropna(subset=["df2_ix"]).copy()
|
||||
elif index == "multi-index":
|
||||
exp = expected[predicate].dropna(subset=["level_0_y"]).copy()
|
||||
elif index == "named-multi-index":
|
||||
exp = expected[predicate].dropna(subset=["df2_ix1"]).copy()
|
||||
exp = exp.drop("geometry_x", axis=1).rename(columns={"geometry_y": "geometry"})
|
||||
exp["df2"] = exp["df2"].astype("int64")
|
||||
if index == "default-index":
|
||||
exp["index_right"] = exp["index_right"].astype("int64")
|
||||
res["index_left"] = res["index_left"].astype(float)
|
||||
elif index == "named-index":
|
||||
exp[["df2_ix"]] = exp[["df2_ix"]].astype("int64")
|
||||
exp = exp.set_index("df2_ix").rename(columns={"df1_ix": "index_left"})
|
||||
if index in ["default-index", "string-index"]:
|
||||
exp = exp.set_index("index_right")
|
||||
exp = exp.reindex(columns=res.columns)
|
||||
exp.index.name = None
|
||||
if index == "multi-index":
|
||||
exp = exp.set_index(["level_0_y", "level_1_y"]).rename(
|
||||
columns={"level_0_x": "index_left0", "level_1_x": "index_left1"}
|
||||
)
|
||||
exp.index.names = df2.index.names
|
||||
if index == "named-multi-index":
|
||||
exp = exp.set_index(["df2_ix1", "df2_ix2"]).rename(
|
||||
columns={"df1_ix1": "index_left0", "df1_ix2": "index_left1"}
|
||||
)
|
||||
exp.index.names = df2.index.names
|
||||
if predicate == "within":
|
||||
exp = exp.sort_index()
|
||||
|
||||
assert_frame_equal(res, exp, check_index_type=False)
|
||||
|
||||
|
||||
class TestSpatialJoinNYBB:
|
||||
def setup_method(self):
|
||||
nybb_filename = geopandas.datasets.get_path("nybb")
|
||||
self.polydf = read_file(nybb_filename)
|
||||
self.crs = self.polydf.crs
|
||||
N = 20
|
||||
b = [int(x) for x in self.polydf.total_bounds]
|
||||
self.pointdf = GeoDataFrame(
|
||||
[
|
||||
{"geometry": Point(x, y), "pointattr1": x + y, "pointattr2": x - y}
|
||||
for x, y in zip(
|
||||
range(b[0], b[2], int((b[2] - b[0]) / N)),
|
||||
range(b[1], b[3], int((b[3] - b[1]) / N)),
|
||||
)
|
||||
],
|
||||
crs=self.crs,
|
||||
)
|
||||
|
||||
def test_geometry_name(self):
|
||||
# test sjoin is working with other geometry name
|
||||
polydf_original_geom_name = self.polydf.geometry.name
|
||||
self.polydf = self.polydf.rename(columns={"geometry": "new_geom"}).set_geometry(
|
||||
"new_geom"
|
||||
)
|
||||
assert polydf_original_geom_name != self.polydf.geometry.name
|
||||
res = sjoin(self.polydf, self.pointdf, how="left")
|
||||
assert self.polydf.geometry.name == res.geometry.name
|
||||
|
||||
def test_sjoin_left(self):
|
||||
df = sjoin(self.pointdf, self.polydf, how="left")
|
||||
assert df.shape == (21, 8)
|
||||
for i, row in df.iterrows():
|
||||
assert row.geometry.geom_type == "Point"
|
||||
assert "pointattr1" in df.columns
|
||||
assert "BoroCode" in df.columns
|
||||
|
||||
def test_sjoin_right(self):
|
||||
# the inverse of left
|
||||
df = sjoin(self.pointdf, self.polydf, how="right")
|
||||
df2 = sjoin(self.polydf, self.pointdf, how="left")
|
||||
assert df.shape == (12, 8)
|
||||
assert df.shape == df2.shape
|
||||
for i, row in df.iterrows():
|
||||
assert row.geometry.geom_type == "MultiPolygon"
|
||||
for i, row in df2.iterrows():
|
||||
assert row.geometry.geom_type == "MultiPolygon"
|
||||
|
||||
def test_sjoin_inner(self):
|
||||
df = sjoin(self.pointdf, self.polydf, how="inner")
|
||||
assert df.shape == (11, 8)
|
||||
|
||||
def test_sjoin_predicate(self):
|
||||
# points within polygons
|
||||
df = sjoin(self.pointdf, self.polydf, how="left", predicate="within")
|
||||
assert df.shape == (21, 8)
|
||||
assert df.loc[1]["BoroName"] == "Staten Island"
|
||||
|
||||
# points contain polygons? never happens so we should have nulls
|
||||
df = sjoin(self.pointdf, self.polydf, how="left", predicate="contains")
|
||||
assert df.shape == (21, 8)
|
||||
assert np.isnan(df.loc[1]["Shape_Area"])
|
||||
|
||||
def test_sjoin_bad_predicate(self):
|
||||
# AttributeError: 'Point' object has no attribute 'spandex'
|
||||
with pytest.raises(ValueError):
|
||||
sjoin(self.pointdf, self.polydf, how="left", predicate="spandex")
|
||||
|
||||
def test_sjoin_duplicate_column_name(self):
|
||||
pointdf2 = self.pointdf.rename(columns={"pointattr1": "Shape_Area"})
|
||||
df = sjoin(pointdf2, self.polydf, how="left")
|
||||
assert "Shape_Area_left" in df.columns
|
||||
assert "Shape_Area_right" in df.columns
|
||||
|
||||
@pytest.mark.parametrize("how", ["left", "right", "inner"])
|
||||
def test_sjoin_named_index(self, how):
|
||||
# original index names should be unchanged
|
||||
pointdf2 = self.pointdf.copy()
|
||||
pointdf2.index.name = "pointid"
|
||||
polydf = self.polydf.copy()
|
||||
polydf.index.name = "polyid"
|
||||
|
||||
res = sjoin(pointdf2, polydf, how=how)
|
||||
assert pointdf2.index.name == "pointid"
|
||||
assert polydf.index.name == "polyid"
|
||||
|
||||
# original index name should pass through to result
|
||||
if how == "right":
|
||||
assert res.index.name == "polyid"
|
||||
else: # how == "left", how == "inner"
|
||||
assert res.index.name == "pointid"
|
||||
|
||||
def test_sjoin_values(self):
|
||||
# GH190
|
||||
self.polydf.index = [1, 3, 4, 5, 6]
|
||||
df = sjoin(self.pointdf, self.polydf, how="left")
|
||||
assert df.shape == (21, 8)
|
||||
df = sjoin(self.polydf, self.pointdf, how="left")
|
||||
assert df.shape == (12, 8)
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_no_overlapping_geometry(self):
|
||||
# Note: these tests are for correctly returning GeoDataFrame
|
||||
# when result of the join is empty
|
||||
|
||||
df_inner = sjoin(self.pointdf.iloc[17:], self.polydf, how="inner")
|
||||
df_left = sjoin(self.pointdf.iloc[17:], self.polydf, how="left")
|
||||
df_right = sjoin(self.pointdf.iloc[17:], self.polydf, how="right")
|
||||
|
||||
expected_inner_df = pd.concat(
|
||||
[
|
||||
self.pointdf.iloc[:0],
|
||||
pd.Series(name="index_right", dtype="int64"),
|
||||
self.polydf.drop("geometry", axis=1).iloc[:0],
|
||||
],
|
||||
axis=1,
|
||||
)
|
||||
|
||||
expected_inner = GeoDataFrame(expected_inner_df)
|
||||
|
||||
expected_right_df = pd.concat(
|
||||
[
|
||||
self.pointdf.drop("geometry", axis=1).iloc[:0],
|
||||
pd.concat(
|
||||
[
|
||||
pd.Series(name="index_left", dtype="int64"),
|
||||
pd.Series(name="index_right", dtype="int64"),
|
||||
],
|
||||
axis=1,
|
||||
),
|
||||
self.polydf,
|
||||
],
|
||||
axis=1,
|
||||
)
|
||||
|
||||
expected_right = GeoDataFrame(expected_right_df).set_index("index_right")
|
||||
|
||||
expected_left_df = pd.concat(
|
||||
[
|
||||
self.pointdf.iloc[17:],
|
||||
pd.Series(name="index_right", dtype="int64"),
|
||||
self.polydf.iloc[:0].drop("geometry", axis=1),
|
||||
],
|
||||
axis=1,
|
||||
)
|
||||
|
||||
expected_left = GeoDataFrame(expected_left_df)
|
||||
|
||||
assert expected_inner.equals(df_inner)
|
||||
assert expected_right.equals(df_right)
|
||||
assert expected_left.equals(df_left)
|
||||
|
||||
@pytest.mark.skip("Not implemented")
|
||||
def test_sjoin_outer(self):
|
||||
df = sjoin(self.pointdf, self.polydf, how="outer")
|
||||
assert df.shape == (21, 8)
|
||||
|
||||
def test_sjoin_empty_geometries(self):
|
||||
# https://github.com/geopandas/geopandas/issues/944
|
||||
empty = GeoDataFrame(geometry=[GeometryCollection()] * 3)
|
||||
df = sjoin(pd.concat([self.pointdf, empty]), self.polydf, how="left")
|
||||
assert df.shape == (24, 8)
|
||||
df2 = sjoin(self.pointdf, pd.concat([self.polydf, empty]), how="left")
|
||||
assert df2.shape == (21, 8)
|
||||
|
||||
@pytest.mark.parametrize("predicate", ["intersects", "within", "contains"])
|
||||
def test_sjoin_no_valid_geoms(self, predicate):
|
||||
"""Tests a completely empty GeoDataFrame."""
|
||||
empty = GeoDataFrame(geometry=[], crs=self.pointdf.crs)
|
||||
assert sjoin(self.pointdf, empty, how="inner", predicate=predicate).empty
|
||||
assert sjoin(self.pointdf, empty, how="right", predicate=predicate).empty
|
||||
assert sjoin(empty, self.pointdf, how="inner", predicate=predicate).empty
|
||||
assert sjoin(empty, self.pointdf, how="left", predicate=predicate).empty
|
||||
|
||||
def test_empty_sjoin_return_duplicated_columns(self):
|
||||
nybb = geopandas.read_file(geopandas.datasets.get_path("nybb"))
|
||||
nybb2 = nybb.copy()
|
||||
nybb2.geometry = nybb2.translate(200000) # to get non-overlapping
|
||||
|
||||
result = geopandas.sjoin(nybb, nybb2)
|
||||
|
||||
assert "BoroCode_right" in result.columns
|
||||
assert "BoroCode_left" in result.columns
|
||||
|
||||
|
||||
class TestSpatialJoinNaturalEarth:
|
||||
def setup_method(self):
|
||||
world_path = geopandas.datasets.get_path("naturalearth_lowres")
|
||||
cities_path = geopandas.datasets.get_path("naturalearth_cities")
|
||||
self.world = read_file(world_path)
|
||||
self.cities = read_file(cities_path)
|
||||
|
||||
def test_sjoin_inner(self):
|
||||
# GH637
|
||||
countries = self.world[["geometry", "name"]]
|
||||
countries = countries.rename(columns={"name": "country"})
|
||||
cities_with_country = sjoin(
|
||||
self.cities, countries, how="inner", predicate="intersects"
|
||||
)
|
||||
assert cities_with_country.shape == (213, 4)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
TEST_NEAREST,
|
||||
reason=("This test can only be run _without_ PyGEOS >= 0.10 installed"),
|
||||
)
|
||||
def test_no_nearest_all():
|
||||
df1 = geopandas.GeoDataFrame({"geometry": []})
|
||||
df2 = geopandas.GeoDataFrame({"geometry": []})
|
||||
with pytest.raises(
|
||||
NotImplementedError,
|
||||
match="Currently, only PyGEOS >= 0.10.0 or Shapely >= 2.0 supports",
|
||||
):
|
||||
sjoin_nearest(df1, df2)
|
||||
|
||||
|
||||
@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"
|
||||
),
|
||||
)
|
||||
class TestNearest:
|
||||
@pytest.mark.parametrize(
|
||||
"how_kwargs", ({}, {"how": "inner"}, {"how": "left"}, {"how": "right"})
|
||||
)
|
||||
def test_allowed_hows(self, how_kwargs):
|
||||
left = geopandas.GeoDataFrame({"geometry": []})
|
||||
right = geopandas.GeoDataFrame({"geometry": []})
|
||||
sjoin_nearest(left, right, **how_kwargs) # no error
|
||||
|
||||
@pytest.mark.parametrize("how", ("outer", "abcde"))
|
||||
def test_invalid_hows(self, how: str):
|
||||
left = geopandas.GeoDataFrame({"geometry": []})
|
||||
right = geopandas.GeoDataFrame({"geometry": []})
|
||||
with pytest.raises(ValueError, match="`how` was"):
|
||||
sjoin_nearest(left, right, how=how)
|
||||
|
||||
@pytest.mark.parametrize("distance_col", (None, "distance"))
|
||||
def test_empty_right_df_how_left(self, distance_col: str):
|
||||
# all records from left and no results from right
|
||||
left = geopandas.GeoDataFrame({"geometry": [Point(0, 0), Point(1, 1)]})
|
||||
right = geopandas.GeoDataFrame({"geometry": []})
|
||||
joined = sjoin_nearest(
|
||||
left,
|
||||
right,
|
||||
how="left",
|
||||
distance_col=distance_col,
|
||||
)
|
||||
assert_geoseries_equal(joined["geometry"], left["geometry"])
|
||||
assert joined["index_right"].isna().all()
|
||||
if distance_col is not None:
|
||||
assert joined[distance_col].isna().all()
|
||||
|
||||
@pytest.mark.parametrize("distance_col", (None, "distance"))
|
||||
def test_empty_right_df_how_right(self, distance_col: str):
|
||||
# no records in joined
|
||||
left = geopandas.GeoDataFrame({"geometry": [Point(0, 0), Point(1, 1)]})
|
||||
right = geopandas.GeoDataFrame({"geometry": []})
|
||||
joined = sjoin_nearest(
|
||||
left,
|
||||
right,
|
||||
how="right",
|
||||
distance_col=distance_col,
|
||||
)
|
||||
assert joined.empty
|
||||
if distance_col is not None:
|
||||
assert distance_col in joined
|
||||
|
||||
@pytest.mark.parametrize("how", ["inner", "left"])
|
||||
@pytest.mark.parametrize("distance_col", (None, "distance"))
|
||||
def test_empty_left_df(self, how, distance_col: str):
|
||||
right = geopandas.GeoDataFrame({"geometry": [Point(0, 0), Point(1, 1)]})
|
||||
left = geopandas.GeoDataFrame({"geometry": []})
|
||||
joined = sjoin_nearest(left, right, how=how, distance_col=distance_col)
|
||||
assert joined.empty
|
||||
if distance_col is not None:
|
||||
assert distance_col in joined
|
||||
|
||||
@pytest.mark.parametrize("distance_col", (None, "distance"))
|
||||
def test_empty_left_df_how_right(self, distance_col: str):
|
||||
right = geopandas.GeoDataFrame({"geometry": [Point(0, 0), Point(1, 1)]})
|
||||
left = geopandas.GeoDataFrame({"geometry": []})
|
||||
joined = sjoin_nearest(
|
||||
left,
|
||||
right,
|
||||
how="right",
|
||||
distance_col=distance_col,
|
||||
)
|
||||
assert_geoseries_equal(joined["geometry"], right["geometry"])
|
||||
assert joined["index_left"].isna().all()
|
||||
if distance_col is not None:
|
||||
assert joined[distance_col].isna().all()
|
||||
|
||||
@pytest.mark.parametrize("how", ["inner", "left"])
|
||||
def test_empty_join_due_to_max_distance(self, how):
|
||||
# after applying max_distance the join comes back empty
|
||||
# (as in NaN in the joined columns)
|
||||
left = geopandas.GeoDataFrame({"geometry": [Point(0, 0)]})
|
||||
right = geopandas.GeoDataFrame({"geometry": [Point(1, 1), Point(2, 2)]})
|
||||
joined = sjoin_nearest(
|
||||
left,
|
||||
right,
|
||||
how=how,
|
||||
max_distance=1,
|
||||
distance_col="distances",
|
||||
)
|
||||
expected = left.copy()
|
||||
expected["index_right"] = [np.nan]
|
||||
expected["distances"] = [np.nan]
|
||||
if how == "inner":
|
||||
expected = expected.dropna()
|
||||
expected["index_right"] = expected["index_right"].astype("int64")
|
||||
assert_geodataframe_equal(joined, expected)
|
||||
|
||||
def test_empty_join_due_to_max_distance_how_right(self):
|
||||
# after applying max_distance the join comes back empty
|
||||
# (as in NaN in the joined columns)
|
||||
left = geopandas.GeoDataFrame({"geometry": [Point(0, 0), Point(1, 1)]})
|
||||
right = geopandas.GeoDataFrame({"geometry": [Point(2, 2)]})
|
||||
joined = sjoin_nearest(
|
||||
left,
|
||||
right,
|
||||
how="right",
|
||||
max_distance=1,
|
||||
distance_col="distances",
|
||||
)
|
||||
expected = right.copy()
|
||||
expected["index_left"] = [np.nan]
|
||||
expected["distances"] = [np.nan]
|
||||
expected = expected[["index_left", "geometry", "distances"]]
|
||||
assert_geodataframe_equal(joined, expected)
|
||||
|
||||
@pytest.mark.parametrize("how", ["inner", "left"])
|
||||
def test_max_distance(self, how):
|
||||
left = geopandas.GeoDataFrame({"geometry": [Point(0, 0), Point(1, 1)]})
|
||||
right = geopandas.GeoDataFrame({"geometry": [Point(1, 1), Point(2, 2)]})
|
||||
joined = sjoin_nearest(
|
||||
left,
|
||||
right,
|
||||
how=how,
|
||||
max_distance=1,
|
||||
distance_col="distances",
|
||||
)
|
||||
expected = left.copy()
|
||||
expected["index_right"] = [np.nan, 0]
|
||||
expected["distances"] = [np.nan, 0]
|
||||
if how == "inner":
|
||||
expected = expected.dropna()
|
||||
expected["index_right"] = expected["index_right"].astype("int64")
|
||||
assert_geodataframe_equal(joined, expected)
|
||||
|
||||
def test_max_distance_how_right(self):
|
||||
left = geopandas.GeoDataFrame({"geometry": [Point(1, 1), Point(2, 2)]})
|
||||
right = geopandas.GeoDataFrame({"geometry": [Point(0, 0), Point(1, 1)]})
|
||||
joined = sjoin_nearest(
|
||||
left,
|
||||
right,
|
||||
how="right",
|
||||
max_distance=1,
|
||||
distance_col="distances",
|
||||
)
|
||||
expected = right.copy()
|
||||
expected["index_left"] = [np.nan, 0]
|
||||
expected["distances"] = [np.nan, 0]
|
||||
expected = expected[["index_left", "geometry", "distances"]]
|
||||
assert_geodataframe_equal(joined, expected)
|
||||
|
||||
@pytest.mark.parametrize("how", ["inner", "left"])
|
||||
@pytest.mark.parametrize(
|
||||
"geo_left, geo_right, expected_left, expected_right, distances",
|
||||
[
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1)],
|
||||
[0, 1],
|
||||
[0, 0],
|
||||
[math.sqrt(2), 0],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1), Point(0, 0)],
|
||||
[0, 1],
|
||||
[1, 0],
|
||||
[0, 0],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1), Point(0, 0), Point(0, 0)],
|
||||
[0, 0, 1],
|
||||
[1, 2, 0],
|
||||
[0, 0, 0],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1), Point(0, 0), Point(2, 2)],
|
||||
[0, 1],
|
||||
[1, 0],
|
||||
[0, 0],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1), Point(0.25, 1)],
|
||||
[0, 1],
|
||||
[1, 0],
|
||||
[math.sqrt(0.25**2 + 1), 0],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(-10, -10), Point(100, 100)],
|
||||
[0, 1],
|
||||
[0, 0],
|
||||
[math.sqrt(10**2 + 10**2), math.sqrt(11**2 + 11**2)],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(x, y) for x, y in zip(np.arange(10), np.arange(10))],
|
||||
[0, 1],
|
||||
[0, 1],
|
||||
[0, 0],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1), Point(0, 0)],
|
||||
[Point(1.1, 1.1), Point(0, 0)],
|
||||
[0, 1, 2],
|
||||
[1, 0, 1],
|
||||
[0, np.sqrt(0.1**2 + 0.1**2), 0],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_sjoin_nearest_left(
|
||||
self,
|
||||
geo_left,
|
||||
geo_right,
|
||||
expected_left: Sequence[int],
|
||||
expected_right: Sequence[int],
|
||||
distances: Sequence[float],
|
||||
how,
|
||||
):
|
||||
left = geopandas.GeoDataFrame({"geometry": geo_left})
|
||||
right = geopandas.GeoDataFrame({"geometry": geo_right})
|
||||
expected_gdf = left.iloc[expected_left].copy()
|
||||
expected_gdf["index_right"] = expected_right
|
||||
# without distance col
|
||||
joined = sjoin_nearest(left, right, how=how)
|
||||
# inner / left join give a different row order
|
||||
check_like = how == "inner"
|
||||
assert_geodataframe_equal(expected_gdf, joined, check_like=check_like)
|
||||
# with distance col
|
||||
expected_gdf["distance_col"] = np.array(distances, dtype=float)
|
||||
joined = sjoin_nearest(left, right, how=how, distance_col="distance_col")
|
||||
assert_geodataframe_equal(expected_gdf, joined, check_like=check_like)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"geo_left, geo_right, expected_left, expected_right, distances",
|
||||
[
|
||||
([Point(0, 0), Point(1, 1)], [Point(1, 1)], [1], [0], [0]),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1), Point(0, 0)],
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
[0, 0],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1), Point(0, 0), Point(0, 0)],
|
||||
[1, 0, 0],
|
||||
[0, 1, 2],
|
||||
[0, 0, 0],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1), Point(0, 0), Point(2, 2)],
|
||||
[1, 0, 1],
|
||||
[0, 1, 2],
|
||||
[0, 0, math.sqrt(2)],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(1, 1), Point(0.25, 1)],
|
||||
[1, 1],
|
||||
[0, 1],
|
||||
[0, 0.75],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(-10, -10), Point(100, 100)],
|
||||
[0, 1],
|
||||
[0, 1],
|
||||
[math.sqrt(10**2 + 10**2), math.sqrt(99**2 + 99**2)],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1)],
|
||||
[Point(x, y) for x, y in zip(np.arange(10), np.arange(10))],
|
||||
[0, 1] + [1] * 8,
|
||||
list(range(10)),
|
||||
[0, 0] + [np.sqrt(x**2 + x**2) for x in np.arange(1, 9)],
|
||||
),
|
||||
(
|
||||
[Point(0, 0), Point(1, 1), Point(0, 0)],
|
||||
[Point(1.1, 1.1), Point(0, 0)],
|
||||
[1, 0, 2],
|
||||
[0, 1, 1],
|
||||
[np.sqrt(0.1**2 + 0.1**2), 0, 0],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_sjoin_nearest_right(
|
||||
self,
|
||||
geo_left,
|
||||
geo_right,
|
||||
expected_left: Sequence[int],
|
||||
expected_right: Sequence[int],
|
||||
distances: Sequence[float],
|
||||
):
|
||||
left = geopandas.GeoDataFrame({"geometry": geo_left})
|
||||
right = geopandas.GeoDataFrame({"geometry": geo_right})
|
||||
expected_gdf = right.iloc[expected_right].copy()
|
||||
expected_gdf["index_left"] = expected_left
|
||||
expected_gdf = expected_gdf[["index_left", "geometry"]]
|
||||
# without distance col
|
||||
joined = sjoin_nearest(left, right, how="right")
|
||||
assert_geodataframe_equal(expected_gdf, joined)
|
||||
# with distance col
|
||||
expected_gdf["distance_col"] = np.array(distances, dtype=float)
|
||||
joined = sjoin_nearest(left, right, how="right", distance_col="distance_col")
|
||||
assert_geodataframe_equal(expected_gdf, joined)
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:Geometry is in a geographic CRS")
|
||||
def test_sjoin_nearest_inner(self):
|
||||
# check equivalency of left and inner join
|
||||
countries = read_file(geopandas.datasets.get_path("naturalearth_lowres"))
|
||||
cities = read_file(geopandas.datasets.get_path("naturalearth_cities"))
|
||||
countries = countries[["geometry", "name"]].rename(columns={"name": "country"})
|
||||
|
||||
# default: inner and left give the same result
|
||||
result1 = sjoin_nearest(cities, countries, distance_col="dist")
|
||||
assert result1.shape[0] == cities.shape[0]
|
||||
result2 = sjoin_nearest(cities, countries, distance_col="dist", how="inner")
|
||||
assert_geodataframe_equal(result2, result1)
|
||||
result3 = sjoin_nearest(cities, countries, distance_col="dist", how="left")
|
||||
assert_geodataframe_equal(result3, result1, check_like=True)
|
||||
|
||||
# with max_distance: rows that go above are dropped in case of inner
|
||||
result4 = sjoin_nearest(cities, countries, distance_col="dist", max_distance=1)
|
||||
assert_geodataframe_equal(
|
||||
result4, result1[result1["dist"] < 1], check_like=True
|
||||
)
|
||||
result5 = sjoin_nearest(
|
||||
cities, countries, distance_col="dist", max_distance=1, how="left"
|
||||
)
|
||||
assert result5.shape[0] == cities.shape[0]
|
||||
result5 = result5.dropna()
|
||||
result5["index_right"] = result5["index_right"].astype("int64")
|
||||
assert_geodataframe_equal(result5, result4, check_like=True)
|
||||
|
||||
expected_index_uncapped = (
|
||||
[1, 3, 3, 1, 2] if compat.PANDAS_GE_22 else [1, 1, 3, 3, 2]
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not (compat.USE_SHAPELY_20),
|
||||
reason=(
|
||||
"shapely >= 2.0 is required to run sjoin_nearest"
|
||||
"with parameter `exclusive` set"
|
||||
),
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"max_distance,expected", [(None, expected_index_uncapped), (1.1, [3, 3, 1, 2])]
|
||||
)
|
||||
def test_sjoin_nearest_exclusive(self, max_distance, expected):
|
||||
geoms = shapely.points(np.arange(3), np.arange(3))
|
||||
geoms = np.append(geoms, [Point(1, 2)])
|
||||
|
||||
df = geopandas.GeoDataFrame({"geometry": geoms})
|
||||
result = df.sjoin_nearest(
|
||||
df, max_distance=max_distance, distance_col="dist", exclusive=True
|
||||
)
|
||||
|
||||
assert_series_equal(
|
||||
result["index_right"].reset_index(drop=True),
|
||||
pd.Series(expected),
|
||||
check_names=False,
|
||||
)
|
||||
|
||||
if max_distance:
|
||||
assert result["dist"].max() <= max_distance
|
||||
@@ -0,0 +1,51 @@
|
||||
from shapely.geometry import LineString, MultiPoint, Point
|
||||
|
||||
from geopandas import GeoSeries
|
||||
from geopandas.tools import collect
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestTools:
|
||||
def setup_method(self):
|
||||
self.p1 = Point(0, 0)
|
||||
self.p2 = Point(1, 1)
|
||||
self.p3 = Point(2, 2)
|
||||
self.mpc = MultiPoint([self.p1, self.p2, self.p3])
|
||||
|
||||
self.mp1 = MultiPoint([self.p1, self.p2])
|
||||
self.line1 = LineString([(3, 3), (4, 4)])
|
||||
|
||||
def test_collect_single(self):
|
||||
result = collect(self.p1)
|
||||
assert self.p1.equals(result)
|
||||
|
||||
def test_collect_single_force_multi(self):
|
||||
result = collect(self.p1, multi=True)
|
||||
expected = MultiPoint([self.p1])
|
||||
assert expected.equals(result)
|
||||
|
||||
def test_collect_multi(self):
|
||||
result = collect(self.mp1)
|
||||
assert self.mp1.equals(result)
|
||||
|
||||
def test_collect_multi_force_multi(self):
|
||||
result = collect(self.mp1)
|
||||
assert self.mp1.equals(result)
|
||||
|
||||
def test_collect_list(self):
|
||||
result = collect([self.p1, self.p2, self.p3])
|
||||
assert self.mpc.equals(result)
|
||||
|
||||
def test_collect_GeoSeries(self):
|
||||
s = GeoSeries([self.p1, self.p2, self.p3])
|
||||
result = collect(s)
|
||||
assert self.mpc.equals(result)
|
||||
|
||||
def test_collect_mixed_types(self):
|
||||
with pytest.raises(ValueError):
|
||||
collect([self.p1, self.line1])
|
||||
|
||||
def test_collect_mixed_multi(self):
|
||||
with pytest.raises(ValueError):
|
||||
collect([self.mpc, self.mp1])
|
||||
Reference in New Issue
Block a user