From 3f110dcc053599a8ce7629082ae41b2fe9752bfd Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Sat, 21 Sep 2019 19:36:15 +0200 Subject: [PATCH] BUG: fix geometry methods to handle single MultiGeoms output (#1134) --- geopandas/array.py | 49 +++++++++++++++++++++-------------- geopandas/tests/test_array.py | 27 +++++++++++++++++++ 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/geopandas/array.py b/geopandas/array.py index d894c9e..f9dedf7 100644 --- a/geopandas/array.py +++ b/geopandas/array.py @@ -112,8 +112,9 @@ def from_wkb(data): geom = None out.append(geom) - out = np.array(out, dtype=object) - return GeometryArray(out) + aout = np.empty(n, dtype=object) + aout[:] = out + return GeometryArray(aout) def to_wkb(geoms): @@ -146,8 +147,9 @@ def from_wkt(data): geom = None out.append(geom) - out = np.array(out, dtype=object) - return GeometryArray(out) + aout = np.empty(n, dtype=object) + aout[:] = out + return GeometryArray(aout) def to_wkt(geoms): @@ -197,8 +199,9 @@ def points_from_xy(x, y, z=None): if z is not None: z = np.asarray(z, dtype="float64") out = _points_from_xy(x, y, z) - out = np.array(out, dtype=object) - return GeometryArray(out) + aout = np.empty(len(x), dtype=object) + aout[:] = out + return GeometryArray(aout) # ----------------------------------------------------------------------------- @@ -384,14 +387,16 @@ def _affinity_method(op, left, *args, **kwargs): # affine_transform itself works (as well as translate), but rotate, scale # and skew fail (they try to unpack the bounds). # Here: consistently returning empty geom for input empty geom - data = [] + out = [] for geom in left.data: if geom is None or geom.is_empty: res = geom else: res = getattr(shapely.affinity, op)(geom, *args, **kwargs) - data.append(res) - return GeometryArray(np.array(data, dtype=object)) + out.append(res) + data = np.empty(len(left), dtype=object) + data[:] = out + return GeometryArray(data) class GeometryArray(ExtensionArray): @@ -547,8 +552,9 @@ class GeometryArray(ExtensionArray): "Only Polygon objects have interior rings. For other " "geometry types, None is returned." ) - - return np.array(inner_rings, dtype=object) + data = np.empty(len(self), dtype=object) + data[:] = inner_rings + return data def representative_point(self): # method and not a property -> can't use _unary_geo @@ -620,39 +626,44 @@ class GeometryArray(ExtensionArray): return _binary_op_float("distance", self, other) def buffer(self, distance, resolution=16, **kwargs): + data = data = np.empty(len(self), dtype=object) if isinstance(distance, np.ndarray): if len(distance) != len(self): raise ValueError( "Length of distance sequence does not match " "length of the GeoSeries" ) - data = [ + + data[:] = [ geom.buffer(dist, resolution, **kwargs) if geom is not None else None for geom, dist in zip(self.data, distance) ] - return GeometryArray(np.array(data, dtype=object)) + return GeometryArray(data) - data = [ + data[:] = [ geom.buffer(distance, resolution, **kwargs) if geom is not None else None for geom in self.data ] - return GeometryArray(np.array(data, dtype=object)) + return GeometryArray(data) def interpolate(self, distance, normalized=False): + data = data = np.empty(len(self), dtype=object) if isinstance(distance, np.ndarray): if len(distance) != len(self): raise ValueError( "Length of distance sequence does not match " "length of the GeoSeries" ) - data = [ + data[:] = [ geom.interpolate(dist, normalized=normalized) for geom, dist in zip(self.data, distance) ] - return GeometryArray(np.array(data, dtype=object)) + return GeometryArray(data) - data = [geom.interpolate(distance, normalized=normalized) for geom in self.data] - return GeometryArray(np.array(data, dtype=object)) + data[:] = [ + geom.interpolate(distance, normalized=normalized) for geom in self.data + ] + return GeometryArray(data) def simplify(self, *args, **kwargs): # method and not a property -> can't use _unary_geo diff --git a/geopandas/tests/test_array.py b/geopandas/tests/test_array.py index 808c9da..0fdfb2d 100644 --- a/geopandas/tests/test_array.py +++ b/geopandas/tests/test_array.py @@ -133,6 +133,13 @@ def test_from_wkb(): assert res[-1] is None assert res[-2] is None + # single MultiPolygon + multi_poly = shapely.geometry.MultiPolygon( + [shapely.geometry.box(0, 0, 1, 1), shapely.geometry.box(3, 3, 4, 4)] + ) + res = from_wkb([multi_poly.wkb]) + assert res[0] == multi_poly + def test_to_wkb(): P = from_shapely(points_no_missing) @@ -179,6 +186,13 @@ def test_from_wkt(string_type): assert res[-1] is None assert res[-2] is None + # single MultiPolygon + multi_poly = shapely.geometry.MultiPolygon( + [shapely.geometry.box(0, 0, 1, 1), shapely.geometry.box(3, 3, 4, 4)] + ) + res = from_wkt([f(multi_poly.wkt)]) + assert res[0] == multi_poly + def test_to_wkt(): P = from_shapely(points_no_missing) @@ -667,3 +681,16 @@ def test_raise_on_bad_sizes(): assert "lengths" in str(info.value).lower() assert "12" in str(info.value) assert "21" in str(info.value) + + +def test_buffer_single_multipolygon(): + # https://github.com/geopandas/geopandas/issues/1130 + multi_poly = shapely.geometry.MultiPolygon( + [shapely.geometry.box(0, 0, 1, 1), shapely.geometry.box(3, 3, 4, 4)] + ) + arr = from_shapely([multi_poly]) + result = arr.buffer(1) + expected = [multi_poly.buffer(1)] + equal_geometries(result, expected) + result = arr.buffer(np.array([1])) + equal_geometries(result, expected)