Repository: geopandas/geopandas

Issue:
BUG: Plotting empty geometries generates a Type Error (shapely only)
I'm not sure if this is a bug or expected behaviour. I've tested this in geopandas 0.7 and 0.8.1.

I have a process which generates some geometry but it may fail. I use that geometry for further operations so it seems prefereable to keep the failed geometries as empty geometries (e.g. Polygon()) rathern than missing (None). However when I plot the final geodataframe the plot will fail with a TypeError if the gemetries are empty but plot as expected if they are missing.

The plotting issue is easily reproducible with the [Creating a GeoDataFrame from a DataFrame with coordinates example](https://geopandas.org/gallery/create_geopandas_from_pandas.html). I would expect it to plot the same as the version with None.

```
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point

df = pd.DataFrame(
    {'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],
     'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela'],
     'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48],
     'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86]})

gdf = gpd.GeoDataFrame(
    df, geometry=gpd.points_from_xy(df.Longitude, df.Latitude))

gdf.plot(color='red') # plots okay
```
Missing geometry plots okay
```
gdf.loc[3,'geometry'] = None
gdf.plot(color='red') # plots okay
```
Empty geometry raises a TypeError
```
gdf.loc[3,'geometry'] = Point()
gdf.plot(color='red') # raises a TypeError
```


Discussion:
Thanks a lot! I can confirm that this is a bug, which is affecting shapely-backed GeoDataFrames (not pygeos). We should drop these geometries before plotting, I guess.

Thanks, that's really helpful. I will do as you suggest and drop the empty geometries before plotting.
It looks like I currently have to drop the entire row/record - the empty geometry won't plot, and the missing geometry won't allow reprojection (e.g. gdf.to_crs()). I don't suppose there is any way around that at the moment?

```
gdf.loc[3,'geometry'] = None
gdf.crs = 'epsg:4326'
gdf.to_crs(epsg=5347)
```

raises an AttributeError
For plotting, the way around is `gdf[~gdf.geometry.is_empty].plot()`. The `to_crs` is apparently another bug we need to fix :). I'll open an issue for that. The workaround here is a bit more tricky if you don't want to lose your empty rows. I would store them separately, reproject and append them back.
Thanks, you're right, I was hoping to keep the empty rows but, in the code that I am working on, I think it is important that the end user can plot and/or reproject the gdf that is created without having to know about the work arounds so, for the moment, I will just drop those rows entirely. Thanks again.
I ran into another example, and which is not pygeos only (raises the same error in both cases), when plotting an empty polygon:

```
In [1]: import geopandas
   ...: import shapely.geometry
   ...: import shapely.wkt

In [4]: geopandas.GeoSeries([shapely.geometry.box(1,1,2,2), shapely.wkt.loads("POLYGON EMPTY")]).plot()
/home/joris/miniconda3/envs/geo-dev/lib/python3.8/site-packages/descartes/patch.py:63: ShapelyDeprecationWarning: The array interface is deprecated and will no longer work in Shapely 2.0. Convert the '.coords' to a numpy array instead.
  concatenate([asarray(t.exterior)[:, :2]] +
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-15538a50518b> in <module>
----> 1 geopandas.GeoSeries([shapely.geometry.box(1,1,2,2), shapely.wkt.loads("POLYGON EMPTY")]).plot()

~/scipy/geopandas/geopandas/geoseries.py in plot(self, *args, **kwargs)
    438         there.
    439         """
--> 440         return plot_series(self, *args, **kwargs)
    441 
    442     plot.__doc__ = plot_series.__doc__

~/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
```

(this is a regression)
I am facing a problem which to some point probably relates to this one, here is the code sample to reproduce with a dummy GeoDataFrame:    

```
from shapely.geometry import Point, Polygon, MultiPoint
import geopandas as gpd

poly = Polygon([ (-1, -1), (-1, 2), (2, 2), (2, -1), (-1, -1) ])

multipoint = MultiPoint([Point(0, 0), Point(1, 1)])
point = Point(0,1)
point_ = Point(10,10)
empty_point = Point()
none_point = Point()

gdf = gpd.GeoDataFrame(geometry=[
    multipoint,
    point,
    empty_point,
    point_,
    none_point,
])
gdf['geometry'] = gdf['geometry'].apply(lambda x: x.intersection(poly) )
gdf['geometry'].loc[4] = None
```

`gdf` now looks like:

```
>>> gdf
                                        geometry
0  MULTIPOINT (0.00000 0.00000, 1.00000 1.00000)
1                        POINT (0.00000 1.00000)
2                       GEOMETRYCOLLECTION EMPTY
3                                    POINT EMPTY
4                                           None
```

When plotting with the `POINT EMPTY` geometry `gdf.iloc[:4].plot()` gives:    

```
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-267-627bff07eefc> in <module>
----> 1 gdf.iloc[:4].plot()

/usr/local/lib/python3.6/dist-packages/geopandas/geodataframe.py in plot(self, *args, **kwargs)
    919         from there.
    920         """
--> 921         return plot_dataframe(self, *args, **kwargs)
    922 
    923     plot.__doc__ = plot_dataframe.__doc__

/usr/local/lib/python3.6/dist-packages/geopandas/plotting.py in plot_dataframe(df, column, cmap, color, ax, cax, categorical, legend, scheme, k, vmin, vmax, markersize, figsize, legend_kwds, categories, classification_kwds, missing_kwds, aspect, **style_kwds)
    621             markersize=markersize,
    622             aspect=aspect,
--> 623             **style_kwds
    624         )
    625 

/usr/local/lib/python3.6/dist-packages/geopandas/plotting.py in plot_series(s, cmap, color, ax, figsize, aspect, **style_kwds)
    428         values_ = values[point_idx] if cmap else None
    429         _plot_point_collection(
--> 430             ax, points, values_, color=color, cmap=cmap, **style_kwds
    431         )
    432 

/usr/local/lib/python3.6/dist-packages/geopandas/plotting.py in _plot_point_collection(ax, geoms, values, color, cmap, vmin, vmax, marker, markersize, **kwargs)
    258         values = np.take(values, multiindex, axis=0)
    259 
--> 260     x = [p.x for p in geoms]
    261     y = [p.y for p in geoms]
    262 

/usr/local/lib/python3.6/dist-packages/geopandas/plotting.py in <listcomp>(.0)
    258         values = np.take(values, multiindex, axis=0)
    259 
--> 260     x = [p.x for p in geoms]
    261     y = [p.y for p in geoms]
    262 

/usr/local/lib/python3.6/dist-packages/shapely/geometry/point.py in x(self)
     53     def x(self):
     54         """Return x coordinate."""
---> 55         return self.coords[0][0]
     56 
     57     @property

IndexError: list index out of range
```

When plotting with the last line (`None` value) `gdf.iloc[:5].plot()` gives:    
```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-268-c23a112f8d60> in <module>
----> 1 gdf.iloc[:5].plot()

/usr/local/lib/python3.6/dist-packages/geopandas/geodataframe.py in plot(self, *args, **kwargs)
    919         from there.
    920         """
--> 921         return plot_dataframe(self, *args, **kwargs)
    922 
    923     plot.__doc__ = plot_dataframe.__doc__

/usr/local/lib/python3.6/dist-packages/geopandas/plotting.py in plot_dataframe(df, column, cmap, color, ax, cax, categorical, legend, scheme, k, vmin, vmax, markersize, figsize, legend_kwds, categories, classification_kwds, missing_kwds, aspect, **style_kwds)
    621             markersize=markersize,
    622             aspect=aspect,
--> 623             **style_kwds
    624         )
    625 

/usr/local/lib/python3.6/dist-packages/geopandas/plotting.py in plot_series(s, cmap, color, ax, figsize, aspect, **style_kwds)
    388 
    389     # decompose GeometryCollections
--> 390     geoms, multiindex = _flatten_multi_geoms(s.geometry, prefix="Geom")
    391     values = np.take(values, multiindex, axis=0) if cmap else None
    392     expl_series = geopandas.GeoSeries(geoms)

/usr/local/lib/python3.6/dist-packages/geopandas/plotting.py in _flatten_multi_geoms(geoms, prefix)
     46 
     47     for ix, geom in enumerate(geoms):
---> 48         if geom.type.startswith(prefix):
     49             for poly in geom:
     50                 components.append(poly)

AttributeError: 'NoneType' object has no attribute 'type'
```

Plotting with row index values up to `[:3]` are all OK.

Hope it can help. Don't hesitate to tell me if this would be more relevant to shapely (I'm not able to figure it out).

(system info are the same than here: https://github.com/geopandas/geopandas/issues/1779 )

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) and not geom.is_empty:
+        if geom is not None and geom.type.startswith(prefix) and not geom.is_empty:
             for poly in geom.geoms:
                 components.append(poly)
                 component_index.append(ix)
@@ -283,8 +283,8 @@ def _plot_point_collection(
     geoms, multiindex = _flatten_multi_geoms(geoms)
     # values are expanded below as kwargs["c"]
 
-    x = [p.x for p in geoms]
-    y = [p.y for p in geoms]
+    x = [p.x if not p.is_empty else None for p in geoms]
+    y = [p.y if not p.is_empty else None for p in geoms]
 
     # matplotlib 1.4 does not support c=None, and < 2.0 does not support s=None
     if values 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
@@ -308,6 +308,18 @@ def test_empty_geometry(self):
             ax = s.plot()
             assert len(ax.collections) == 1
 
+        # more complex case with GEOMETRYCOLLECTION EMPTY, POINT EMPTY and NONE
+        poly = Polygon([(-1, -1), (-1, 2), (2, 2), (2, -1), (-1, -1)])
+        point = Point(0, 1)
+        point_ = Point(10, 10)
+        empty_point = Point()
+
+        gdf = GeoDataFrame(geometry=[point, empty_point, point_])
+        gdf["geometry"] = gdf.intersection(poly)
+        gdf.loc[3] = [None]
+        ax = gdf.plot()
+        assert len(ax.collections) == 1
+
     def test_multipoints(self):
 
         # MultiPoints