diff --git a/geoviews/data/geom_dict.py b/geoviews/data/geom_dict.py index 5da766d1..09b5ac03 100644 --- a/geoviews/data/geom_dict.py +++ b/geoviews/data/geom_dict.py @@ -136,7 +136,7 @@ def has_holes(cls, dataset): if isinstance(geom, Polygon) and geom.interiors: return True elif isinstance(geom, MultiPolygon): - for g in geom: + for g in geom.geoms: if isinstance(g, Polygon) and g.interiors: return True return False @@ -148,7 +148,7 @@ def holes(cls, dataset): if isinstance(geom, Polygon): return [[[geom_to_array(h) for h in geom.interiors]]] elif isinstance(geom, MultiPolygon): - return [[[geom_to_array(h) for h in g.interiors] for g in geom]] + return [[[geom_to_array(h) for h in g.interiors] for g in geom.geoms]] return [] @classmethod @@ -276,9 +276,9 @@ def iloc(cls, dataset, index): if isinstance(geom, MultiPoint): if isscalar(rows) or isinstance(rows, slice): - geom = geom[rows] + geom = geom.geoms[rows] elif isinstance(rows, (set, list)): - geom = MultiPoint([geom[r] for r in rows]) + geom = MultiPoint([geom.geoms[r] for r in rows]) data['geometry'] = geom return data diff --git a/geoviews/data/geopandas.py b/geoviews/data/geopandas.py index e711517e..6c163ae5 100644 --- a/geoviews/data/geopandas.py +++ b/geoviews/data/geopandas.py @@ -133,7 +133,7 @@ def has_holes(cls, dataset): if isinstance(geom, Polygon) and geom.interiors: return True elif isinstance(geom, MultiPolygon): - for g in geom: + for g in geom.geoms: if isinstance(g, Polygon) and g.interiors: return True return False @@ -147,7 +147,7 @@ def holes(cls, dataset): if isinstance(geom, Polygon) and geom.interiors: holes.append([[geom_to_array(h) for h in geom.interiors]]) elif isinstance(geom, MultiPolygon): - holes += [[[geom_to_array(h) for h in g.interiors] for g in geom]] + holes += [[[geom_to_array(h) for h in g.interiors] for g in geom.geoms]] else: holes.append([[]]) return holes @@ -208,7 +208,7 @@ def shape_mask(cls, dataset, selection): @classmethod def select_mask(cls, dataset, selection): - mask = np.ones(len(dataset.data), dtype=np.bool) + mask = np.ones(len(dataset.data), dtype=np.bool_) for dim, k in selection.items(): if isinstance(k, tuple): k = slice(*k) @@ -233,7 +233,7 @@ def select_mask(cls, dataset, selection): index_mask = arr == k if dataset.ndims == 1 and np.sum(index_mask) == 0: data_index = np.argmin(np.abs(arr - k)) - mask = np.zeros(len(dataset), dtype=np.bool) + mask = np.zeros(len(dataset), dtype=np.bool_) mask[data_index] = True else: mask &= index_mask @@ -427,10 +427,10 @@ def iloc(cls, dataset, index): count = 0 new_geoms, indexes = [], [] for i, geom in enumerate(geoms): - length = len(geom) + length = len(geom.geoms) if np.isscalar(rows): if count <= rows < (count+length): - new_geoms.append(geom[rows-count]) + new_geoms.append(geom.geoms[rows-count]) indexes.append(i) break elif isinstance(rows, slice): @@ -444,13 +444,13 @@ def iloc(cls, dataset, index): dataset.param.warning(".iloc step slicing currently not supported for" "the multi-tabular data format.") indexes.append(i) - new_geoms.append(geom[start:stop]) + new_geoms.append(geom.geoms[start:stop]) elif isinstance(rows, (list, set)): sub_rows = [(r-count) for r in rows if count <= r < (count+length)] if not sub_rows: continue indexes.append(i) - new_geoms.append(MultiPoint([geom[r] for r in sub_rows])) + new_geoms.append(MultiPoint([geom.geoms[r] for r in sub_rows])) count += length new = dataset.data.iloc[indexes].copy() diff --git a/geoviews/operation/projection.py b/geoviews/operation/projection.py index 97f4baab..0c465f13 100644 --- a/geoviews/operation/projection.py +++ b/geoviews/operation/projection.py @@ -275,7 +275,7 @@ def _process_element(self, element): np.isnan(edge_lengths) ) if np.any(to_mask): - mask = np.zeros(zs.shape, dtype=np.bool) + mask = np.zeros(zs.shape, dtype=np.bool_) mask[:, 1:][to_mask] = True mask[:, 2:][to_mask[:, :-1]] = True mask[:, :-1][to_mask] = True diff --git a/geoviews/tests/test_element.py b/geoviews/tests/test_element.py index 0e83d89c..de76c53e 100644 --- a/geoviews/tests/test_element.py +++ b/geoviews/tests/test_element.py @@ -23,28 +23,74 @@ def test_single_geom_conversion(self): rects = Rectangles([(0, 0, 1, 1)]) geom = rects.geom() self.assertIsInstance(geom, Polygon) - self.assertEqual(np.array(geom.array_interface_base['data']), - np.array([1, 0, 1, 1, 0, 1, 0, 0, 1, 0])) + self.assertEqual( + np.array(geom.exterior.coords), + np.array([ + [1., 0.], + [1., 1.], + [0., 1.], + [0., 0.], + [1., 0.] + ]) + ) def test_multi_geom_conversion(self): rects = Rectangles([(0, 0, 1, 1), (3, 2, 2.5, 1.5)]) geom = rects.geom() self.assertIsInstance(geom, MultiPolygon) - self.assertEqual(len(geom), 2) - self.assertEqual(np.array(geom[0].array_interface_base['data']), - np.array([1, 0, 1, 1, 0, 1, 0, 0, 1, 0])) - self.assertEqual(np.array(geom[1].array_interface_base['data']), - np.array([2.5, 2, 2.5, 1.5, 3, 1.5, 3, 2, 2.5, 2])) + self.assertEqual(len(geom.geoms), 2) + self.assertEqual( + np.array(geom.geoms[0].exterior.coords), + np.array([ + [1., 0.], + [1., 1.], + [0., 1.], + [0., 0.], + [1., 0.] + ]) + ) + self.assertEqual( + np.array(geom.geoms[1].exterior.coords), + np.array([ + [2.5, 2], + [2.5, 1.5], + [3, 1.5], + [3, 2], + [2.5, 2] + ]) + ) def test_geom_union(self): rects = Rectangles([(0, 0, 1, 1), (1, 0, 2, 1)]) geom = rects.geom(union=True) self.assertIsInstance(geom, Polygon) - array = np.array(geom.array_interface_base['data']) + array = np.array(geom.exterior.coords) try: - self.assertEqual(array, np.array([0, 0, 0, 1, 1, 1, 2, 1, 2, 0, 1, 0, 0, 0])) + self.assertEqual( + array, + np.array([ + [0, 0], + [0, 1], + [1, 1], + [2, 1], + [2, 0], + [1, 0], + [0, 0] + ]) + ) except Exception: - self.assertEqual(array, np.array([1, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 0, 1, 0])) + self.assertEqual( + array, + np.array([ + [1, 0], + [0, 0], + [0, 1], + [1, 1], + [2, 1], + [2, 0], + [1, 0] + ]) + ) class TestPath(ComparisonTestCase): @@ -56,19 +102,35 @@ def test_single_geom_conversion(self): path = Path([[(0, 0), (1, 1), (2, 0)]]) geom = path.geom() self.assertIsInstance(geom, LineString) - self.assertEqual(np.array(geom.array_interface_base['data']), - np.array([0, 0, 1, 1, 2, 0])) + self.assertEqual( + np.array(geom.coords), + np.array([ + [0, 0], + [1, 1], + [2, 0] + ]) + ) def test_multi_geom_conversion(self): path = Path([[(0, 0), (1, 1), (2, 0)], [(3, 2), (2.5, 1.5)]]) geom = path.geom() self.assertIsInstance(geom, MultiLineString) - self.assertEqual(len(geom), 2) - self.assertEqual(np.array(geom[0].array_interface_base['data']), - np.array([0, 0, 1, 1, 2, 0])) - self.assertEqual(np.array(geom[1].array_interface_base['data']), - np.array([3, 2, 2.5, 1.5])) - + self.assertEqual(len(geom.geoms), 2) + self.assertEqual( + np.array(geom.geoms[0].coords), + np.array([ + [0, 0], + [1, 1], + [2, 0] + ]) + ) + self.assertEqual( + np.array(geom.geoms[1].coords), + np.array([ + [3, 2], + [2.5, 1.5] + ]) + ) class TestPolygons(ComparisonTestCase): @@ -81,20 +143,41 @@ def test_single_geom_conversion(self): path = Polygons([[(0, 0), (1, 1), (2, 0)]]) geom = path.geom() self.assertIsInstance(geom, Polygon) - self.assertEqual(np.array(geom.array_interface_base['data']), - np.array([0, 0, 1, 1, 2, 0, 0, 0])) + self.assertEqual( + np.array(geom.exterior.coords), + np.array([ + [0, 0], + [1, 1], + [2, 0], + [0, 0] + ]) + ) def test_single_geom_with_hole_conversion(self): holes = [[((0.5, 0.2), (1, 0.8), (1.5, 0.2))]] path = Polygons([{'x': [0, 1, 2], 'y': [0, 1, 0], 'holes': holes}], ['x', 'y']) geom = path.geom() self.assertIsInstance(geom, Polygon) - self.assertEqual(np.array(geom.exterior.array_interface_base['data']), - np.array([0, 0, 1, 1, 2, 0, 0, 0])) + self.assertEqual( + np.array(geom.exterior.coords), + np.array([ + [0, 0], + [1, 1], + [2, 0], + [0, 0] + ]) + ) self.assertEqual(len(geom.interiors), 1) self.assertIsInstance(geom.interiors[0], LinearRing) - self.assertEqual(np.array(geom.interiors[0].array_interface_base['data']), - np.array([0.5, 0.2, 1, 0.8, 1.5, 0.2, 0.5, 0.2])) + self.assertEqual( + np.array(geom.interiors[0].coords), + np.array([ + [0.5, 0.2], + [1, 0.8], + [1.5, 0.2], + [0.5, 0.2] + ]) + ) def test_multi_geom_conversion(self): holes = [[((0.5, 0.2), (1, 0.8), (1.5, 0.2))]] @@ -102,16 +185,37 @@ def test_multi_geom_conversion(self): {'x': [5, 6, 7], 'y': [2, 1, 2]}], ['x', 'y']) geom = path.geom() self.assertIsInstance(geom, MultiPolygon) - self.assertEqual(len(geom), 2) - self.assertEqual(np.array(geom[0].exterior.array_interface_base['data']), - np.array([0, 0, 1, 1, 2, 0, 0, 0])) - self.assertEqual(len(geom[0].interiors), 1) - self.assertIsInstance(geom[0].interiors[0], LinearRing) - self.assertEqual(np.array(geom[0].interiors[0].array_interface_base['data']), - np.array([0.5, 0.2, 1, 0.8, 1.5, 0.2, 0.5, 0.2])) - self.assertEqual(np.array(geom[1].exterior.array_interface_base['data']), - np.array([5, 2, 6, 1, 7, 2, 5, 2])) - self.assertEqual(len(geom[1].interiors), 0) + self.assertEqual(len(geom.geoms), 2) + self.assertEqual( + np.array(geom.geoms[0].exterior.coords), + np.array([ + [0, 0], + [1, 1], + [2, 0], + [0, 0] + ]) + ) + self.assertEqual(len(geom.geoms[0].interiors), 1) + self.assertIsInstance(geom.geoms[0].interiors[0], LinearRing) + self.assertEqual( + np.array(geom.geoms[0].interiors[0].coords), + np.array([ + [0.5, 0.2], + [1, 0.8], + [1.5, 0.2], + [0.5, 0.2] + ]) + ) + self.assertEqual( + np.array(geom.geoms[1].exterior.coords), + np.array([ + [5, 2], + [6, 1], + [7, 2], + [5, 2] + ]) + ) + self.assertEqual(len(geom.geoms[1].interiors), 0) @@ -131,9 +235,9 @@ def test_multi_geom_conversion(self): points = Points([(0, 0), (1, 2.5)]) geom = points.geom() self.assertIsInstance(geom, MultiPoint) - self.assertEqual(len(geom), 2) - self.assertEqual(np.column_stack(geom[0].xy), np.array([[0, 0]])) - self.assertEqual(np.column_stack(geom[1].xy), np.array([[1, 2.5]])) + self.assertEqual(len(geom.geoms), 2) + self.assertEqual(np.column_stack(geom.geoms[0].xy), np.array([[0, 0]])) + self.assertEqual(np.column_stack(geom.geoms[1].xy), np.array([[1, 2.5]])) class TestSegments(ComparisonTestCase): @@ -146,15 +250,30 @@ def test_single_geom_conversion(self): segs = Segments([(0, 0, 1, 1)]) geom = segs.geom() self.assertIsInstance(geom, LineString) - self.assertEqual(np.array(geom.array_interface_base['data']), - np.array([0, 0, 1, 1])) + self.assertEqual( + np.array(geom.coords), + np.array([ + [0, 0], + [1, 1] + ]) + ) def test_multi_geom_conversion(self): segs = Segments([(0, 0, 1, 1), (1.5, 2, 3, 1)]) geom = segs.geom() self.assertIsInstance(geom, MultiLineString) - self.assertEqual(len(geom), 2) - self.assertEqual(np.array(geom[0].array_interface_base['data']), - np.array([0, 0, 1, 1])) - self.assertEqual(np.array(geom[1].array_interface_base['data']), - np.array([1.5, 2, 3, 1])) + self.assertEqual(len(geom.geoms), 2) + self.assertEqual( + np.array(geom.geoms[0].coords), + np.array([ + [0, 0], + [1, 1] + ]) + ) + self.assertEqual( + np.array(geom.geoms[1].coords), + np.array([[ + 1.5, 2], + [3, 1] + ]) + ) diff --git a/geoviews/util.py b/geoviews/util.py index 6de40b08..a8d8e254 100644 --- a/geoviews/util.py +++ b/geoviews/util.py @@ -1,10 +1,12 @@ from __future__ import division +from distutils.version import LooseVersion import sys import warnings import param import numpy as np +import shapely import shapely.geometry as sgeom from cartopy import crs as ccrs @@ -23,6 +25,9 @@ poly_types = (MultiPolygon, Polygon, LinearRing) +shapely_version = LooseVersion(shapely.__version__) + + def wrap_lons(lons, base, period): """ Wrap longitude values into the range between base and base+period. @@ -156,7 +161,7 @@ def geom_dict_to_array_dict(geom_dict, coord_names=['Longitude', 'Latitude']): new_dict['holes'] = [holes] elif geom.geom_type == 'MultiPolygon': outer_holes = [] - for g in geom: + for g in geom.geoms: holes = [] for interior in g.interiors: holes.append(geom_to_array(interior)) @@ -319,17 +324,28 @@ def to_ccw(geom): def geom_to_arr(geom): + """ + LineString, LinearRing and Polygon (exterior only?) + """ + # LineString and LinearRing geoms have an xy attribute try: xy = getattr(geom, 'xy', None) except NotImplementedError: xy = None - if xy is not None: return np.column_stack(xy) - if hasattr(geom, 'array_interface'): - data = geom.array_interface() - return np.array(data['data']).reshape(data['shape'])[:, :2] - arr = geom.array_interface_base['data'] + + # Polygon + # shapely 1.8.0 deprecated `array_interface` and + # unfortunately also introduced a bug in the `array_interface_base` + # property which raised an error as soon as it was called. + if shapely_version < '1.8.0': + if hasattr(geom, 'array_interface'): + data = geom.array_interface() + return np.array(data['data']).reshape(data['shape'])[:, :2] + arr = geom.array_interface_base['data'] + else: + arr = np.asarray(geom.exterior.coords) if (len(arr) % 2) != 0: arr = arr[:-1] @@ -342,24 +358,34 @@ def geom_length(geom): """ if geom.geom_type == 'Point': return 1 + # Polygon if hasattr(geom, 'exterior'): geom = geom.exterior - if not geom.geom_type.startswith('Multi') and hasattr(geom, 'array_interface_base'): - return len(geom.array_interface_base['data'])//2 + # As of shapely 1.8.0: LineString, LinearRing (and GeometryCollection?) + if shapely_version < '1.8.0': + if not geom.geom_type.startswith('Multi') and hasattr(geom, 'array_interface_base'): + return len(geom.array_interface_base['data'])//2 else: - glength = len(geom) - length = 0 - for i, g in enumerate(geom): - length += geom_length(g) - if 'Point' not in geom.geom_type and (i+1 != glength): - length += 1 + if not geom.geom_type.startswith('Multi'): + return len(geom.coords) + # MultiPolygon, MultiPoint, MultiLineString (recursively) + glength = len(geom.geoms) + length = 0 + for i, g in enumerate(geom.geoms): + length += geom_length(g) + if 'Point' not in geom.geom_type and (i+1 != glength): + length += 1 - return length + return length def geom_to_array(geom): + """ + Convert the coords of a shapely Geometry to a numpy array. + """ if geom.geom_type == 'Point': return np.array([[geom.x, geom.y]]) + # Only Polygon as of shapely 1.8.0 if hasattr(geom, 'exterior'): if geom.exterior is None: xs, ys = np.array([]), np.array([]) @@ -370,13 +396,15 @@ def geom_to_array(geom): return geom_to_arr(geom) elif geom.geom_type == 'MultiPoint': arrays = [] - for g in geom: + for g in geom.geoms: if g.geom_type == 'Point': arrays.append(np.array(g.xy).T) return np.concatenate(arrays) if arrays else np.array([]) else: + # As of shapely 1.8.0, that would leave: + # MultiLineString, MultiPolygon (and GeometryCollection?) arrays = [] - for g in geom: + for g in geom.geoms: arrays.append(geom_to_arr(g)) arrays.append(np.array([[np.nan, np.nan]])) return np.concatenate(arrays[:-1]) if arrays else np.array([])