Repository: geopandas/geopandas

Issue:
Warning when missing_kwds provided but no areas missing data
If `df` is a GeoDataFrame which has no missing data in `column`, then if I do:

```
df.plot(column=column, 
        missing_kwds={'color': 'lightgrey'},
    )
```
...I get:

```
geopandas/plotting.py:332: UserWarning: The GeoSeries you are attempting to plot is empty. Nothing has been displayed.
```

`plot()` should, instead, just not attempt to plot the empty series when there's no missing data.

Workaround is to omit `missing_kwds` if you know there will be no missing data, but if you're unsure then you have to live with the warning as best I can tell?

Discussion:

Patch:
diff --git a/ci/travis/36-minimal.yaml b/ci/travis/36-minimal.yaml
index c7b37397..3533fc12 100644
--- a/ci/travis/36-minimal.yaml
+++ b/ci/travis/36-minimal.yaml
@@ -14,6 +14,7 @@ dependencies:
   - pytest
   - pytest-cov
   - codecov
+  - fsspec
   # optional
   - rtree
   - matplotlib
diff --git a/ci/travis/36-pd025.yaml b/ci/travis/36-pd025.yaml
index 713078a5..cf5df536 100644
--- a/ci/travis/36-pd025.yaml
+++ b/ci/travis/36-pd025.yaml
@@ -12,6 +12,7 @@ dependencies:
   # testing
   - pytest
   - pytest-cov
+  - fsspec
   #- codecov
   # optional
   - rtree
diff --git a/ci/travis/37-dev.yaml b/ci/travis/37-dev.yaml
index 7dd3f16c..2b463640 100644
--- a/ci/travis/37-dev.yaml
+++ b/ci/travis/37-dev.yaml
@@ -13,6 +13,7 @@ dependencies:
   # testing
   - pytest
   - pytest-cov
+  - fsspec
   #- codecov
   # optional
   - rtree
diff --git a/doc/source/docs/user_guide/io.rst b/doc/source/docs/user_guide/io.rst
index b22b0dd6..b5826b3f 100644
--- a/doc/source/docs/user_guide/io.rst
+++ b/doc/source/docs/user_guide/io.rst
@@ -57,6 +57,14 @@ as a file handler (e.g. via built-in ``open`` function) or ``StringIO``::
     file = open(filename)
     df = geopandas.read_file(file)
 
+File-like objects from `fsspec <https://filesystem-spec.readthedocs.io/en/latest>`_
+can also be used to read data, allowing for any combination of storage backends and caching
+supported by that project::
+
+    path = "simplecache::http://download.geofabrik.de/antarctica-latest-free.shp.zip"
+    with fsspec.open(path) as file:
+        df = geopandas.read_file(file)
+
 You can also read path objects::
 
     import pathlib
diff --git a/geopandas/io/file.py b/geopandas/io/file.py
index 2a94f3f3..080935a2 100644
--- a/geopandas/io/file.py
+++ b/geopandas/io/file.py
@@ -1,6 +1,5 @@
 from distutils.version import LooseVersion
 
-import io
 import warnings
 import numpy as np
 import pandas as pd
@@ -36,6 +35,16 @@ def _is_url(url):
         return False
 
 
+def _is_zip(path):
+    """Check if a given path is a zipfile"""
+    parsed = fiona.path.ParsedPath.from_uri(path)
+    return (
+        parsed.archive.endswith(".zip")
+        if parsed.archive
+        else parsed.path.endswith(".zip")
+    )
+
+
 def _read_file(filename, bbox=None, mask=None, rows=None, **kwargs):
     """
     Returns a GeoDataFrame from a file or URL.
@@ -85,10 +94,30 @@ def _read_file(filename, bbox=None, mask=None, rows=None, **kwargs):
         req = _urlopen(filename)
         path_or_bytes = req.read()
         reader = fiona.BytesCollection
-    elif isinstance(filename, io.TextIOBase):
-        path_or_bytes = filename.read()
-        reader = fiona.open
+    elif pd.api.types.is_file_like(filename):
+        data = filename.read()
+        path_or_bytes = data.encode("utf-8") if isinstance(data, str) else data
+        reader = fiona.BytesCollection
     else:
+        # Opening a file via URL or file-like-object above automatically detects a
+        # zipped file. In order to match that behavior, attempt to add a zip scheme
+        # if missing.
+        if _is_zip(str(filename)):
+            parsed = fiona.parse_path(str(filename))
+            if isinstance(parsed, fiona.path.ParsedPath):
+                # If fiona is able to parse the path, we can safely look at the scheme
+                # and update it to have a zip scheme if necessary.
+                schemes = (parsed.scheme or "").split("+")
+                if "zip" not in schemes:
+                    parsed.scheme = "+".join(["zip"] + schemes)
+                filename = parsed.name
+            elif isinstance(parsed, fiona.path.UnparsedPath) and not str(
+                filename
+            ).startswith("/vsi"):
+                # If fiona is unable to parse the path, it might have a Windows drive
+                # scheme. Try adding zip:// to the front. If the path starts with "/vsi"
+                # it is a legacy GDAL path type, so let it pass unmodified.
+                filename = "zip://" + parsed.name
         path_or_bytes = filename
         reader = fiona.open
 
diff --git a/geopandas/plotting.py b/geopandas/plotting.py
index e82c012f..72949567 100644
--- a/geopandas/plotting.py
+++ b/geopandas/plotting.py
@@ -754,7 +754,7 @@ def plot_dataframe(
             **style_kwds
         )
 
-    if missing_kwds is not None:
+    if missing_kwds is not None and not expl_series[nan_idx].empty:
         if color:
             if "color" not in missing_kwds:
                 missing_kwds["color"] = color
diff --git a/geopandas/tools/clip.py b/geopandas/tools/clip.py
index da76e0f2..75ef3ce6 100644
--- a/geopandas/tools/clip.py
+++ b/geopandas/tools/clip.py
@@ -127,7 +127,7 @@ def clip(gdf, mask, keep_geom_type=False):
     if not isinstance(mask, (GeoDataFrame, GeoSeries, Polygon, MultiPolygon)):
         raise TypeError(
             "'mask' should be GeoDataFrame, GeoSeries or"
-            "(Multi)Polygon, got {}".format(type(gdf))
+            "(Multi)Polygon, got {}".format(type(mask))
         )
 
     if isinstance(mask, (GeoDataFrame, GeoSeries)):


Test patch:
diff --git a/ci/travis/37-latest-conda-forge.yaml b/ci/travis/37-latest-conda-forge.yaml
index 80ff961b..71f87fca 100644
--- a/ci/travis/37-latest-conda-forge.yaml
+++ b/ci/travis/37-latest-conda-forge.yaml
@@ -13,6 +13,7 @@ dependencies:
   - pytest
   - pytest-cov
   - codecov
+  - fsspec
   # optional
   - rtree
   - matplotlib
diff --git a/ci/travis/37-latest-defaults.yaml b/ci/travis/37-latest-defaults.yaml
index 5a8839a2..ad96f2c6 100644
--- a/ci/travis/37-latest-defaults.yaml
+++ b/ci/travis/37-latest-defaults.yaml
@@ -12,6 +12,7 @@ dependencies:
   # testing
   - pytest
   - pytest-cov
+  - fsspec
   #- codecov
   # optional
   - rtree
diff --git a/ci/travis/38-latest-conda-forge.yaml b/ci/travis/38-latest-conda-forge.yaml
index d19642f2..951d7a41 100644
--- a/ci/travis/38-latest-conda-forge.yaml
+++ b/ci/travis/38-latest-conda-forge.yaml
@@ -13,6 +13,7 @@ dependencies:
   - pytest
   - pytest-cov
   - codecov
+  - fsspec
   # optional
   - rtree
   - matplotlib
diff --git a/geopandas/io/tests/test_file.py b/geopandas/io/tests/test_file.py
index 236c856d..96b11a17 100644
--- a/geopandas/io/tests/test_file.py
+++ b/geopandas/io/tests/test_file.py
@@ -261,7 +261,7 @@ def test_append_file(tmpdir, df_nybb, df_null, driver, ext):
     assert "geometry" in df
     assert len(df) == (5 * 2)
     expected = pd.concat([df_nybb] * 2, ignore_index=True)
-    assert_geodataframe_equal(df, expected)
+    assert_geodataframe_equal(df, expected, check_less_precise=True)
 
     # Write layer with null geometry out to file
     tempfilename = os.path.join(str(tmpdir), "null_geom." + ext)
@@ -272,7 +272,7 @@ def test_append_file(tmpdir, df_nybb, df_null, driver, ext):
     assert "geometry" in df
     assert len(df) == (2 * 2)
     expected = pd.concat([df_null] * 2, ignore_index=True)
-    assert_geodataframe_equal(df, expected)
+    assert_geodataframe_equal(df, expected, check_less_precise=True)
 
 
 # -----------------------------------------------------------------------------
@@ -304,6 +304,16 @@ def test_read_file_remote_geojson_url():
     assert isinstance(gdf, geopandas.GeoDataFrame)
 
 
+@pytest.mark.web
+def test_read_file_remote_zipfile_url():
+    url = (
+        "https://raw.githubusercontent.com/geopandas/geopandas/"
+        "master/geopandas/datasets/nybb_16a.zip"
+    )
+    gdf = read_file(url)
+    assert isinstance(gdf, geopandas.GeoDataFrame)
+
+
 def test_read_file_textio(file_path):
     file_text_stream = open(file_path)
     file_stringio = io.StringIO(open(file_path).read())
@@ -356,6 +366,47 @@ def test_read_file_tempfile():
     temp.close()
 
 
+def test_read_binary_file_fsspec():
+    fsspec = pytest.importorskip("fsspec")
+    # Remove the zip scheme so fsspec doesn't open as a zipped file,
+    # instead we want to read as bytes and let fiona decode it.
+    path = geopandas.datasets.get_path("nybb")[6:]
+    with fsspec.open(path, "rb") as f:
+        gdf = read_file(f)
+        assert isinstance(gdf, geopandas.GeoDataFrame)
+
+
+def test_read_text_file_fsspec(file_path):
+    fsspec = pytest.importorskip("fsspec")
+    with fsspec.open(file_path, "r") as f:
+        gdf = read_file(f)
+        assert isinstance(gdf, geopandas.GeoDataFrame)
+
+
+def test_infer_zipped_file():
+    # Remove the zip scheme so that the test for a zipped file can
+    # check it and add it back.
+    path = geopandas.datasets.get_path("nybb")[6:]
+    gdf = read_file(path)
+    assert isinstance(gdf, geopandas.GeoDataFrame)
+
+    # Check that it can sucessfully add a zip scheme to a path that already has a scheme
+    gdf = read_file("file+file://" + path)
+    assert isinstance(gdf, geopandas.GeoDataFrame)
+
+    # Check that it can add a zip scheme for a path that includes a subpath
+    # within the archive.
+    gdf = read_file(path + "!nybb.shp")
+    assert isinstance(gdf, geopandas.GeoDataFrame)
+
+
+def test_allow_legacy_gdal_path():
+    # Construct a GDAL-style zip path.
+    path = "/vsizip/" + geopandas.datasets.get_path("nybb")[6:]
+    gdf = read_file(path)
+    assert isinstance(gdf, geopandas.GeoDataFrame)
+
+
 def test_read_file_filtered(df_nybb):
     full_df_shape = df_nybb.shape
     nybb_filename = geopandas.datasets.get_path("nybb")