Repository: geopandas/geopandas

Issue:
REGR: plot() no longer working if empty geometries are present
(unfortunately discovered while preparing my course last week .. ;))

It seems that plotting with empty geometries no longer works with 0.8, while this certainly worked before, at least for polygons (following code examples are using geopandas master):

```
import geopandas
from shapely.geometry import Point, Polygon
from shapely import wkt
```

Without using pygeos:

```python
In [16]: geopandas.options.use_pygeos = False

# empty "polygon" which is actually an empty GeometryCollection
In [19]: s = geopandas.GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), Polygon()])

In [20]: s.plot()
...
~/scipy/geopandas/geopandas/plotting.py in _flatten_multi_geoms(geoms, prefix)
     47     for ix, geom in enumerate(geoms):
     48         if geom.type.startswith(prefix):
---> 49             for poly in geom:
     50                 components.append(poly)
     51                 component_index.append(ix)

TypeError: 'Polygon' object is not iterable

# with a real empty polygon
In [21]: s = geopandas.GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), wkt.loads("POLYGON EMPTY")])

In [22]: s.plot()
...
~/scipy/geopandas/geopandas/plotting.py in plot_series(s, cmap, color, ax, figsize, aspect, **style_kwds)
    411 
    412         values_ = values[poly_idx] if cmap else None
--> 413         _plot_polygon_collection(
    414             ax, polys, values_, facecolor=facecolor, cmap=cmap, **style_kwds
    415         )

~/scipy/geopandas/geopandas/plotting.py in _plot_polygon_collection(ax, geoms, values, color, cmap, vmin, vmax, **kwargs)
    144     _expand_kwargs(kwargs, multiindex)
    145 
--> 146     collection = PatchCollection([PolygonPatch(poly) for poly in geoms], **kwargs)
    147 
    148     if values is not None:

~/scipy/geopandas/geopandas/plotting.py in <listcomp>(.0)
    144     _expand_kwargs(kwargs, multiindex)
    145 
--> 146     collection = PatchCollection([PolygonPatch(poly) for poly in geoms], **kwargs)
    147 
    148     if values is not None:

~/miniconda3/envs/geo-dev/lib/python3.8/site-packages/descartes/patch.py in PolygonPatch(polygon, **kwargs)
     85 
     86     """
---> 87     return PathPatch(PolygonPath(polygon), **kwargs)

~/miniconda3/envs/geo-dev/lib/python3.8/site-packages/descartes/patch.py in PolygonPath(polygon)
     60                 "A polygon or multi-polygon representation is required")
     61 
---> 62     vertices = concatenate([
     63         concatenate([asarray(t.exterior)[:, :2]] +
     64                     [asarray(r)[:, :2] for r in t.interiors])

~/miniconda3/envs/geo-dev/lib/python3.8/site-packages/descartes/patch.py in <listcomp>(.0)
     61 
     62     vertices = concatenate([
---> 63         concatenate([asarray(t.exterior)[:, :2]] +
     64                     [asarray(r)[:, :2] for r in t.interiors])
     65         for t in polygon])

~/miniconda3/envs/geo-dev/lib/python3.8/site-packages/descartes/patch.py in exterior(self)
     17     def exterior(self):
     18         return (getattr(self.context, 'exterior', None)
---> 19                 or self.context[0])
     20 
     21     @property

TypeError: 'Polygon' object is not subscriptable
```

when using pygeos, a few more cases work:

```python
In [6]: geopandas.options.use_pygeos = True

# empty GeometryCollection works
In [12]: s = geopandas.GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), Polygon()])

In [13]: s.plot()
Out[13]: <AxesSubplot:>

# but real empty polygon still fails with the same error as above
In [14]: s = geopandas.GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), wkt.loads("POLYGON EMPTY")])

In [15]: s.plot()
....
TypeError: 'Polygon' object is not subscriptable
```


Discussion:

Resolving patch:
diff --git a/geopandas/plotting.py b/geopandas/plotting.py
--- a/geopandas/plotting.py
+++ b/geopandas/plotting.py
@@ -45,7 +45,7 @@ def _flatten_multi_geoms(geoms, prefix="Multi"):
         return geoms, np.arange(len(geoms))
 
     for ix, geom in enumerate(geoms):
-        if geom.type.startswith(prefix):
+        if geom.type.startswith(prefix) and not geom.is_empty:
             for poly in geom.geoms:
                 components.append(poly)
                 component_index.append(ix)
@@ -151,7 +151,9 @@ def _plot_polygon_collection(
 
     _expand_kwargs(kwargs, multiindex)
 
-    collection = PatchCollection([PolygonPatch(poly) for poly in geoms], **kwargs)
+    collection = PatchCollection(
+        [PolygonPatch(poly) for poly in geoms if not poly.is_empty], **kwargs
+    )
 
     if values is not None:
         collection.set_array(np.asarray(values))
@@ -385,6 +387,14 @@ def plot_series(
         )
         return ax
 
+    if s.is_empty.all():
+        warnings.warn(
+            "The GeoSeries you are attempting to plot is "
+            "composed of empty geometries. Nothing has been displayed.",
+            UserWarning,
+        )
+        return ax
+
     # if cmap is specified, create range of colors based on cmap
     values = None
     if cmap is not None:


Test patch:
diff --git a/geopandas/tests/test_plotting.py b/geopandas/tests/test_plotting.py
--- a/geopandas/tests/test_plotting.py
+++ b/geopandas/tests/test_plotting.py
@@ -4,6 +4,7 @@
 import numpy as np
 import pandas as pd
 
+from shapely import wkt
 from shapely.affinity import rotate
 from shapely.geometry import (
     MultiPolygon,
@@ -19,6 +20,7 @@
 
 from geopandas import GeoDataFrame, GeoSeries, read_file
 from geopandas.datasets import get_path
+import geopandas._compat as compat
 
 import pytest
 
@@ -277,6 +279,11 @@ def test_subplots_norm(self):
         np.testing.assert_array_equal(actual_colors_orig[1], actual_colors_sub[0])
 
     def test_empty_plot(self):
+
+        s = GeoSeries([Polygon()])
+        with pytest.warns(UserWarning):
+            ax = s.plot()
+        assert len(ax.collections) == 0
         s = GeoSeries([])
         with pytest.warns(UserWarning):
             ax = s.plot()
@@ -286,6 +293,20 @@ def test_empty_plot(self):
             ax = df.plot()
         assert len(ax.collections) == 0
 
+    def test_empty_geometry(self):
+
+        if compat.USE_PYGEOS:
+            s = GeoSeries([wkt.loads("POLYGON EMPTY")])
+            s = GeoSeries(
+                [Polygon([(0, 0), (1, 0), (1, 1)]), wkt.loads("POLYGON EMPTY")]
+            )
+            ax = s.plot()
+            assert len(ax.collections) == 1
+        if not compat.USE_PYGEOS:
+            s = GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), Polygon()])
+            ax = s.plot()
+            assert len(ax.collections) == 1
+
     def test_multipoints(self):
 
         # MultiPoints