Repository: geopandas/geopandas

Issue:
BUG: explore() with all-empty geometries
All empty points:

```python
import geopandas
from shapely import Point

gdf = geopandas.GeoDataFrame(geometry=[Point(), Point()])
gdf.explore()
```

gives
```
File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/geopandas/geodataframe.py:2205](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/geopandas/geodataframe.py#line=2204), in GeoDataFrame.explore(self, *args, **kwargs)
   2203 @doc(_explore)
   2204 def explore(self, *args, **kwargs):
-> 2205     return _explore(self, *args, **kwargs)

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/geopandas/explore.py:383](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/geopandas/explore.py#line=382), in _explore(df, column, cmap, color, m, tiles, attr, tooltip, popup, highlight, categorical, legend, scheme, k, vmin, vmax, width, height, categories, classification_kwds, control_scale, marker_type, marker_kwds, style_kwds, highlight_kwds, missing_kwds, tooltip_kwds, popup_kwds, legend_kwds, map_kwds, **kwargs)
    380             map_kwds["max_zoom"] = tiles.get("max_zoom", 18)
    381         tiles = tiles.build_url(scale_factor="{r}")
--> 383 m = folium.Map(
    384     location=location,
    385     control_scale=control_scale,
    386     tiles=tiles,
    387     attr=attr,
    388     width=width,
    389     height=height,
    390     **map_kwds,
    391 )
    393 # fit bounds to get a proper zoom level
    394 if fit:

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/folium.py:274](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/folium.py#line=273), in Map.__init__(self, location, width, height, left, top, position, tiles, attr, min_zoom, max_zoom, zoom_start, min_lat, max_lat, min_lon, max_lon, max_bounds, crs, control_scale, prefer_canvas, no_touch, disable_3d, png_enabled, zoom_control, font_size, **kwargs)
    272     zoom_start = 1
    273 else:
--> 274     self.location = validate_location(location)
    276 Figure().add_child(self)
    278 # Map Size Parameters.

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/utilities.py:101](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/utilities.py#line=100), in validate_location(location)
     96         raise ValueError(
     97             "Location should consist of two numerical values, "
     98             f"but {coord!r} of type {type(coord)} is not convertible to float."
     99         )
    100     if math.isnan(float(coord)):
--> 101         raise ValueError("Location values cannot contain NaNs.")
    102 return [float(x) for x in coords]

ValueError: Location values cannot contain NaNs.
```

And then specifically when adding the empties to an existing map and with a named index level, there is another obscure error:

```python
gdf1 = geopandas.GeoDataFrame(geometry=[Point(0, 0), Point(1, 1)])
gdf2 = geopandas.GeoDataFrame(geometry=[Point(), Point()])

m = gdf1.rename_axis(index="index_name").explore()
gdf2.rename_axis(index="index_name").explore(m=m, color="red")
```

gives

```
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/IPython/core/formatters.py:347](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/IPython/core/formatters.py#line=346), in BaseFormatter.__call__(self, obj)
    345     method = get_real_method(obj, self.print_method)
    346     if method is not None:
--> 347         return method()
    348     return None
    349 else:

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/folium.py:321](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/folium.py#line=320), in Map._repr_html_(self, **kwargs)
    319     self._parent = None
    320 else:
--> 321     out = self._parent._repr_html_(**kwargs)
    322 return out

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/branca/element.py:369](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/branca/element.py#line=368), in Figure._repr_html_(self, **kwargs)
    367 def _repr_html_(self, **kwargs):
    368     """Displays the Figure in a Jupyter notebook."""
--> 369     html = escape(self.render(**kwargs))
    370     if self.height is None:
    371         iframe = (
    372             '<div style="width:{width};">'
    373             '<div style="position:relative;width:100%;height:0;padding-bottom:{ratio};">'  # noqa
   (...)
    379             "<[/div](http://localhost:8888/div)><[/div](http://localhost:8888/div)>"
    380         ).format(html=html, width=self.width, ratio=self.ratio)

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/branca/element.py:364](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/branca/element.py#line=363), in Figure.render(self, **kwargs)
    362 """Renders the HTML representation of the element."""
    363 for name, child in self._children.items():
--> 364     child.render(**kwargs)
    365 return self._template.render(this=self, kwargs=kwargs)

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/folium.py:403](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/folium.py#line=402), in Map.render(self, **kwargs)
    376 figure.header.add_child(
    377     Element(
    378         "<style>html, body {"
   (...)
    386     name="css_style",
    387 )
    389 figure.header.add_child(
    390     Element(
    391         "<style>#map {"
   (...)
    400     name="map_style",
    401 )
--> 403 super().render(**kwargs)

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/elements.py:25](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/elements.py#line=24), in JSCSSMixin.render(self, **kwargs)
     22 for name, url in self.default_css:
     23     figure.header.add_child(CssLink(url), name=name)
---> 25 super().render(**kwargs)

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/branca/element.py:681](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/branca/element.py#line=680), in MacroElement.render(self, **kwargs)
    678     figure.script.add_child(Element(script(self, kwargs)), name=self.get_name())
    680 for name, element in self._children.items():
--> 681     element.render(**kwargs)

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/features.py:831](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/features.py#line=830), in GeoJson.render(self, **kwargs)
    829     if self.highlight:
    830         self.highlight_map = mapper.get_highlight_map(self.highlight_function)
--> 831 super().render()

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/map.py:63](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/map.py#line=62), in Layer.render(self, **kwargs)
     55 if self.show:
     56     self.add_child(
     57         ElementAddToElement(
     58             element_name=self.get_name(),
   (...)
     61         name=self.get_name() + "_add",
     62     )
---> 63 super().render(**kwargs)

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/branca/element.py:681](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/branca/element.py#line=680), in MacroElement.render(self, **kwargs)
    678     figure.script.add_child(Element(script(self, kwargs)), name=self.get_name())
    680 for name, element in self._children.items():
--> 681     element.render(**kwargs)

File [~/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/features.py:1189](http://localhost:8888/home/joris/conda/envs/geopandas-workshop/lib/python3.12/site-packages/folium/features.py#line=1188), in GeoJsonDetail.render(self, **kwargs)
   1186 keys = tuple(x for x in keys if x not in ("style", "highlight"))
   1187 for value in self.fields:
   1188     assert (
-> 1189         value in keys
   1190     ), f"The field {value} is not available in the data. Choose from: {keys}."
   1191 figure.header.add_child(
   1192     Element(
   1193         Template(
   (...)
   1212     name=self.get_name() + "tablestyle",
   1213 )
   1215 super().render()

AssertionError: The field index_name is not available in the data. Choose from: ().
```



Discussion:

Resolving patch:
diff --git a/geopandas/explore.py b/geopandas/explore.py
--- a/geopandas/explore.py
+++ b/geopandas/explore.py
@@ -1,3 +1,4 @@
+import warnings
 from packaging.version import Version
 from statistics import mean
 
@@ -341,7 +342,7 @@ def _colormap_helper(_cmap, n_resample=None, idx=None):
         # Get bounds to specify location and map extent
         bounds = gdf.total_bounds
         location = kwargs.pop("location", None)
-        if location is None:
+        if location is None and not np.isnan(bounds).all():
             x = mean([bounds[0], bounds[2]])
             y = mean([bounds[1], bounds[3]])
             location = (y, x)
@@ -394,6 +395,15 @@ def _colormap_helper(_cmap, n_resample=None, idx=None):
         if fit:
             m.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])
 
+    if gdf.is_empty.all():
+        warnings.warn(
+            "The GeoSeries you are attempting to plot is "
+            "composed of empty geometries. Nothing has been displayed.",
+            UserWarning,
+            stacklevel=3,
+        )
+        return m
+
     for map_kwd in _MAP_KWARGS:
         kwargs.pop(map_kwd, None)
 


Test patch:
diff --git a/geopandas/tests/test_explore.py b/geopandas/tests/test_explore.py
--- a/geopandas/tests/test_explore.py
+++ b/geopandas/tests/test_explore.py
@@ -1004,3 +1004,37 @@ def test_empty_geometry(self):
         df.loc[0, df.geometry.name] = shapely.Point()
         m = df.explore()
         self._fetch_map_string(m)
+
+    def test_all_empty(self):
+        with_crs = gpd.GeoDataFrame(
+            geometry=[shapely.Point(), shapely.Point()], crs=4326
+        )
+        with pytest.warns(
+            UserWarning,
+            match="The GeoSeries you are attempting to plot is composed of empty",
+        ):
+            m = with_crs.explore()
+        out_str = self._fetch_map_string(m)
+        if HAS_PYPROJ:
+            assert "center:[0.0,0.0],crs:L.CRS.EPSG3857" in out_str
+
+        no_crs = gpd.GeoDataFrame(geometry=[shapely.Point(), shapely.Point()])
+        with pytest.warns(
+            UserWarning,
+            match="The GeoSeries you are attempting to plot is composed of empty",
+        ):
+            m = no_crs.explore()
+        out_str = self._fetch_map_string(m)
+        assert "center:[0.0,0.0],crs:L.CRS.Simple" in out_str
+
+    def test_add_all_empty_named_index(self):
+        gdf1 = gpd.GeoDataFrame(geometry=[shapely.Point(0, 0), shapely.Point(1, 1)])
+        gdf2 = gpd.GeoDataFrame(geometry=[shapely.Point(), shapely.Point()])
+        m = gdf1.rename_axis(index="index_name").explore()
+        with pytest.warns(
+            UserWarning,
+            match="The GeoSeries you are attempting to plot is composed of empty",
+        ):
+            m = gdf2.rename_axis(index="index_name").explore(m=m, color="red")
+        out_str = self._fetch_map_string(m)
+        assert "center:[0.5,0.5],crs:L.CRS.Simple" in out_str