ENH: add a keyword to control alignment in binary operations and predicates (#1668)
* align keyword * tests * first set of docstrings * docstrings * docs * shapely test * fix condition * fix project test * docstring changes per review
@@ -61,7 +61,7 @@ examples/nybb_*.zip
|
||||
|
||||
doc/source/savefig
|
||||
doc/source/reference
|
||||
doc/source/docs/reference/api
|
||||
doc/source/docs/reference/api/*.rst
|
||||
|
||||
geopandas.egg-info
|
||||
geopandas/version.py
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="105.44mm" height="35.61mm" viewBox="0 0 298.88 100.95"><line x1="120.15" y1="48.9" x2="162.49" y2="48.9" style="fill:none;stroke:#1d1d1b;stroke-miterlimit:10"/><polygon points="161.03 53.89 169.67 48.9 161.03 43.91 161.03 53.89" style="fill:#1d1d1b"/><circle cx="45.9" cy="48.9" r="34.98" style="fill:#e21c84;stroke:#fff;stroke-miterlimit:10;opacity:0.4"/><circle cx="80.88" cy="48.9" r="34.98" style="fill:#0f9c5a;stroke:#fff;stroke-miterlimit:10;opacity:0.6"/><g style="opacity:0.8"><circle cx="214.68" cy="48.9" r="34.98" style="fill:none;stroke:#fec905;stroke-miterlimit:10;stroke-dasharray:1.9972946643829346,1.9972946643829346"/></g><g style="opacity:0.8"><circle cx="249.65" cy="48.9" r="34.98" style="fill:none;stroke:#fec905;stroke-miterlimit:10;stroke-dasharray:1.9972946643829346,1.9972946643829346"/></g><path d="M214.68,48.9a35,35,0,0,1,17.48-30.29,35,35,0,1,0,0,60.57A34.93,34.93,0,0,1,214.68,48.9Z" style="fill:#fec905;opacity:0.8"/></svg>
|
||||
|
After Width: | Height: | Size: 1001 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="105.44mm" height="35.61mm" viewBox="0 0 298.88 100.95"><g style="opacity:0.8"><circle cx="214.68" cy="51.28" r="34.98" transform="matrix(0.94, -0.33, 0.33, 0.94, -4.87, 73.95)" style="fill:none;stroke:#fec905;stroke-miterlimit:10;stroke-dasharray:1.9972946643829346,1.9972946643829346"/></g><g style="opacity:0.8"><circle cx="249.65" cy="51.28" r="34.98" transform="translate(74.78 236.69) rotate(-58.28)" style="fill:none;stroke:#fec905;stroke-miterlimit:10;stroke-dasharray:1.9972946643829346,1.9972946643829346"/></g><line x1="120.15" y1="51.28" x2="162.49" y2="51.28" style="fill:none;stroke:#1d1d1b;stroke-miterlimit:10"/><polygon points="161.03 56.27 169.67 51.28 161.03 46.3 161.03 56.27" style="fill:#1d1d1b"/><circle cx="45.9" cy="51.28" r="34.98" style="fill:#e21c84;stroke:#fff;stroke-miterlimit:10;opacity:0.4"/><circle cx="80.88" cy="51.28" r="34.98" style="fill:#0f9c5a;stroke:#fff;stroke-miterlimit:10;opacity:0.6"/><ellipse cx="232.16" cy="51.28" rx="17.49" ry="30.29" style="fill:#fec905;opacity:0.8"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="105.44mm" height="35.61mm" viewBox="0 0 298.88 100.95"><g style="opacity:0.8"><circle cx="214.68" cy="51.28" r="34.98" transform="matrix(0.94, -0.33, 0.33, 0.94, -4.87, 73.95)" style="fill:none;stroke:#fec905;stroke-miterlimit:10;stroke-dasharray:1.9972946643829346,1.9972946643829346"/></g><g style="opacity:0.8"><circle cx="249.65" cy="51.28" r="34.98" transform="translate(74.78 236.69) rotate(-58.28)" style="fill:none;stroke:#fec905;stroke-miterlimit:10;stroke-dasharray:1.9972946643829346,1.9972946643829346"/></g><line x1="120.15" y1="51.28" x2="162.49" y2="51.28" style="fill:none;stroke:#1d1d1b;stroke-miterlimit:10"/><polygon points="161.03 56.27 169.67 51.28 161.03 46.3 161.03 56.27" style="fill:#1d1d1b"/><circle cx="45.9" cy="51.28" r="34.98" style="fill:#e21c84;stroke:#fff;stroke-miterlimit:10;opacity:0.4"/><circle cx="80.88" cy="51.28" r="34.98" style="fill:#0f9c5a;stroke:#fff;stroke-miterlimit:10;opacity:0.6"/><g style="opacity:0.8"><path d="M214.68,51.28A35,35,0,0,1,232.16,21a35,35,0,1,0,0,60.57A35,35,0,0,1,214.68,51.28Z" style="fill:#fec905"/><path d="M249.65,16.31A34.81,34.81,0,0,0,232.16,21a35,35,0,0,1,0,60.57,35,35,0,1,0,17.49-65.26Z" style="fill:#fec905"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="105.44mm" height="35.61mm" viewBox="0 0 298.88 100.95"><g style="opacity:0.8"><circle cx="214.68" cy="50.49" r="34.98" transform="matrix(0.94, -0.33, 0.33, 0.94, -4.61, 73.91)" style="fill:none;stroke:#fec905;stroke-miterlimit:10;stroke-dasharray:1.9972946643829346,1.9972946643829346"/></g><g style="opacity:0.8"><circle cx="249.65" cy="50.49" r="34.98" transform="translate(75.45 236.31) rotate(-58.28)" style="fill:none;stroke:#fec905;stroke-miterlimit:10;stroke-dasharray:1.9972946643829346,1.9972946643829346"/></g><line x1="120.15" y1="50.49" x2="162.49" y2="50.49" style="fill:none;stroke:#1d1d1b;stroke-miterlimit:10"/><polygon points="161.03 55.48 169.67 50.49 161.03 45.5 161.03 55.48" style="fill:#1d1d1b"/><circle cx="45.9" cy="50.49" r="34.98" style="fill:#e21c84;stroke:#fff;stroke-miterlimit:10;opacity:0.4"/><circle cx="80.88" cy="50.49" r="34.98" style="fill:#0f9c5a;stroke:#fff;stroke-miterlimit:10;opacity:0.6"/><path d="M249.65,15.51a34.91,34.91,0,0,0-17.49,4.69,35,35,0,1,0,0,60.57,35,35,0,1,0,17.49-65.26Z" style="fill:#fec905;opacity:0.8"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
@@ -49,11 +49,12 @@ Binary Predicates
|
||||
.. autosummary::
|
||||
:toctree: api/
|
||||
|
||||
GeoSeries.geom_almost_equals
|
||||
GeoSeries.contains
|
||||
GeoSeries.crosses
|
||||
GeoSeries.disjoint
|
||||
GeoSeries.geom_equals
|
||||
GeoSeries.geom_almost_equals
|
||||
GeoSeries.geom_equals_exact
|
||||
GeoSeries.intersects
|
||||
GeoSeries.overlaps
|
||||
GeoSeries.touches
|
||||
|
||||
@@ -81,6 +81,7 @@ class TestGeomMethods:
|
||||
self.empty = GeoSeries([])
|
||||
self.all_none = GeoSeries([None, None])
|
||||
self.empty_poly = Polygon()
|
||||
self.g9 = GeoSeries(self.g0, index=range(1, 8))
|
||||
|
||||
# Crossed lines
|
||||
self.l3 = LineString([(0, 0), (1, 1)])
|
||||
@@ -244,15 +245,24 @@ class TestGeomMethods:
|
||||
"intersection", self.all_none, self.g1, self.empty
|
||||
)
|
||||
|
||||
assert len(self.g0.intersection(self.g9, align=True) == 8)
|
||||
assert len(self.g0.intersection(self.g9, align=False) == 7)
|
||||
|
||||
def test_union_series(self):
|
||||
self._test_binary_topological("union", self.sq, self.g1, self.g2)
|
||||
|
||||
assert len(self.g0.union(self.g9, align=True) == 8)
|
||||
assert len(self.g0.union(self.g9, align=False) == 7)
|
||||
|
||||
def test_union_polygon(self):
|
||||
self._test_binary_topological("union", self.sq, self.g1, self.t2)
|
||||
|
||||
def test_symmetric_difference_series(self):
|
||||
self._test_binary_topological("symmetric_difference", self.sq, self.g3, self.g4)
|
||||
|
||||
assert len(self.g0.symmetric_difference(self.g9, align=True) == 8)
|
||||
assert len(self.g0.symmetric_difference(self.g9, align=False) == 7)
|
||||
|
||||
def test_symmetric_difference_poly(self):
|
||||
expected = GeoSeries([GeometryCollection(), self.sq], crs=self.g3.crs)
|
||||
self._test_binary_topological(
|
||||
@@ -263,6 +273,9 @@ class TestGeomMethods:
|
||||
expected = GeoSeries([GeometryCollection(), self.t2])
|
||||
self._test_binary_topological("difference", expected, self.g1, self.g2)
|
||||
|
||||
assert len(self.g0.difference(self.g9, align=True) == 8)
|
||||
assert len(self.g0.difference(self.g9, align=False) == 7)
|
||||
|
||||
def test_difference_poly(self):
|
||||
expected = GeoSeries([self.t1, self.t1])
|
||||
self._test_binary_topological("difference", expected, self.g1, self.t2)
|
||||
@@ -341,6 +354,12 @@ class TestGeomMethods:
|
||||
expected = [True, False, True, False, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.contains(self.t1))
|
||||
|
||||
expected = [False, True, True, True, True, True, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.contains(self.g9, align=True))
|
||||
|
||||
expected = [False, False, True, False, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.contains(self.g9, align=False))
|
||||
|
||||
def test_length(self):
|
||||
expected = Series(np.array([2 + np.sqrt(2), 4]), index=self.g1.index)
|
||||
self._test_unary_real("length", expected, self.g1)
|
||||
@@ -359,10 +378,22 @@ class TestGeomMethods:
|
||||
expected = [False, True]
|
||||
assert_array_dtype_equal(expected, self.crossed_lines.crosses(self.l3))
|
||||
|
||||
expected = [False] * 8
|
||||
assert_array_dtype_equal(expected, self.g0.crosses(self.g9, align=True))
|
||||
|
||||
expected = [False] * 7
|
||||
assert_array_dtype_equal(expected, self.g0.crosses(self.g9, align=False))
|
||||
|
||||
def test_disjoint(self):
|
||||
expected = [False, False, False, False, False, True, False]
|
||||
assert_array_dtype_equal(expected, self.g0.disjoint(self.t1))
|
||||
|
||||
expected = [False] * 8
|
||||
assert_array_dtype_equal(expected, self.g0.disjoint(self.g9, align=True))
|
||||
|
||||
expected = [False, False, False, False, True, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.disjoint(self.g9, align=False))
|
||||
|
||||
def test_relate(self):
|
||||
expected = Series(
|
||||
[
|
||||
@@ -381,6 +412,36 @@ class TestGeomMethods:
|
||||
expected = Series(["FF0FFF212", None], index=self.g6.index)
|
||||
assert_array_dtype_equal(expected, self.g6.relate(self.na_none))
|
||||
|
||||
expected = Series(
|
||||
[
|
||||
None,
|
||||
"2FFF1FFF2",
|
||||
"2FFF1FFF2",
|
||||
"2FFF1FFF2",
|
||||
"2FFF1FFF2",
|
||||
"0FFFFFFF2",
|
||||
None,
|
||||
None,
|
||||
],
|
||||
index=range(8),
|
||||
)
|
||||
|
||||
assert_array_dtype_equal(expected, self.g0.relate(self.g9, align=True))
|
||||
|
||||
expected = Series(
|
||||
[
|
||||
"FF2F11212",
|
||||
"2FF11F212",
|
||||
"212FF1FF2",
|
||||
"FF2F1F212",
|
||||
"FF2FF10F2",
|
||||
None,
|
||||
None,
|
||||
],
|
||||
index=self.g0.index,
|
||||
)
|
||||
assert_array_dtype_equal(expected, self.g0.relate(self.g9, align=False))
|
||||
|
||||
def test_distance(self):
|
||||
expected = Series(
|
||||
np.array([np.sqrt((5 - 1) ** 2 + (5 - 1) ** 2), np.nan]), self.na_none.index
|
||||
@@ -390,6 +451,13 @@ class TestGeomMethods:
|
||||
expected = Series(np.array([np.sqrt(4 ** 2 + 4 ** 2), np.nan]), self.g6.index)
|
||||
assert_array_dtype_equal(expected, self.g6.distance(self.na_none))
|
||||
|
||||
expected = Series(np.array([np.nan, 0, 0, 0, 0, 0, np.nan, np.nan]), range(8))
|
||||
assert_array_dtype_equal(expected, self.g0.distance(self.g9, align=True))
|
||||
|
||||
val = self.g0.iloc[4].distance(self.g9.iloc[4])
|
||||
expected = Series(np.array([0, 0, 0, 0, val, np.nan, np.nan]), self.g0.index)
|
||||
assert_array_dtype_equal(expected, self.g0.distance(self.g9, align=False))
|
||||
|
||||
def test_distance_crs_warning(self):
|
||||
with pytest.warns(UserWarning, match="Geometry is in a geographic CRS"):
|
||||
self.g4.distance(self.p0)
|
||||
@@ -410,6 +478,12 @@ class TestGeomMethods:
|
||||
expected = [False] * 7
|
||||
assert_array_dtype_equal(expected, self.g0.intersects(self.empty_poly))
|
||||
|
||||
expected = [False, True, True, True, True, True, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.intersects(self.g9, align=True))
|
||||
|
||||
expected = [True, True, True, True, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.intersects(self.g9, align=False))
|
||||
|
||||
def test_overlaps(self):
|
||||
expected = [True, True, False, False, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.overlaps(self.inner_sq))
|
||||
@@ -417,10 +491,22 @@ class TestGeomMethods:
|
||||
expected = [False, False]
|
||||
assert_array_dtype_equal(expected, self.g4.overlaps(self.t1))
|
||||
|
||||
expected = [False] * 8
|
||||
assert_array_dtype_equal(expected, self.g0.overlaps(self.g9, align=True))
|
||||
|
||||
expected = [False] * 7
|
||||
assert_array_dtype_equal(expected, self.g0.overlaps(self.g9, align=False))
|
||||
|
||||
def test_touches(self):
|
||||
expected = [False, True, False, False, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.touches(self.t1))
|
||||
|
||||
expected = [False] * 8
|
||||
assert_array_dtype_equal(expected, self.g0.touches(self.g9, align=True))
|
||||
|
||||
expected = [True, False, False, True, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.touches(self.g9, align=False))
|
||||
|
||||
def test_within(self):
|
||||
expected = [True, False, False, False, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.within(self.t1))
|
||||
@@ -428,6 +514,12 @@ class TestGeomMethods:
|
||||
expected = [True, True, True, True, True, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.within(self.sq))
|
||||
|
||||
expected = [False, True, True, True, True, True, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.within(self.g9, align=True))
|
||||
|
||||
expected = [False, True, False, False, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.within(self.g9, align=False))
|
||||
|
||||
def test_covers_itself(self):
|
||||
# Each polygon in a Series covers itself
|
||||
res = self.g1.covers(self.g1)
|
||||
@@ -439,6 +531,12 @@ class TestGeomMethods:
|
||||
exp = Series([True, False])
|
||||
assert_series_equal(res, exp)
|
||||
|
||||
expected = [False, True, True, True, True, True, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.covers(self.g9, align=True))
|
||||
|
||||
expected = [False, False, True, False, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.covers(self.g9, align=False))
|
||||
|
||||
def test_covers_inverse(self):
|
||||
res = self.g8.covers(self.g7)
|
||||
exp = Series([False, False])
|
||||
@@ -453,6 +551,12 @@ class TestGeomMethods:
|
||||
exp = Series([True, True])
|
||||
assert_series_equal(res, exp)
|
||||
|
||||
expected = [False, True, True, True, True, True, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.covered_by(self.g9, align=True))
|
||||
|
||||
expected = [False, True, False, False, False, False, False]
|
||||
assert_array_dtype_equal(expected, self.g0.covered_by(self.g9, align=False))
|
||||
|
||||
def test_is_valid(self):
|
||||
expected = Series(np.array([True] * len(self.g1)), self.g1.index)
|
||||
self._test_unary_real("is_valid", expected, self.g1)
|
||||
@@ -572,6 +676,13 @@ class TestGeomMethods:
|
||||
expected = Series([1.0, 0.5], index=self.g5.index)
|
||||
self._test_binary_real("project", expected, self.g5, p, normalized=True)
|
||||
|
||||
s = GeoSeries([Point(2, 2), Point(0.5, 0.5)], index=[1, 2])
|
||||
expected = Series([np.nan, 2.0, np.nan])
|
||||
assert_series_equal(self.g5.project(s), expected)
|
||||
|
||||
expected = Series([2.0, 0.5], index=self.g5.index)
|
||||
assert_series_equal(self.g5.project(s, align=False), expected)
|
||||
|
||||
def test_affine_transform(self):
|
||||
# 45 degree reflection matrix
|
||||
matrix = [0, 1, 1, 0, 0, 0]
|
||||
|
||||
@@ -136,20 +136,38 @@ class TestSeries:
|
||||
|
||||
def test_geom_equals_align(self):
|
||||
with pytest.warns(UserWarning, match="The indices .+ different"):
|
||||
a = self.a1.geom_equals(self.a2)
|
||||
a = self.a1.geom_equals(self.a2, align=True)
|
||||
exp = pd.Series([False, True, False], index=["A", "B", "C"])
|
||||
assert_series_equal(a, exp)
|
||||
|
||||
a = self.a1.geom_equals(self.a2, align=False)
|
||||
exp = pd.Series([False, False], index=["A", "B"])
|
||||
assert_series_equal(a, exp)
|
||||
|
||||
def test_geom_almost_equals(self):
|
||||
# TODO: test decimal parameter
|
||||
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_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]
|
||||
)
|
||||
assert_array_equal(
|
||||
self.a1.geom_equals_exact(self.a2, 0.001, align=False), [False, False]
|
||||
)
|
||||
|
||||
def test_equal_comp_op(self):
|
||||
s = GeoSeries([Point(x, x) for x in range(3)])
|
||||
res = s == Point(1, 1)
|
||||
|
||||