Repository: geopandas/geopandas

Issue:
BUG: Can't write empty GeoJSON
- [x] I have checked that this issue has not already been reported.

- [x] I have confirmed this bug exists on the latest version of geopandas.

- [x] (optional) I have confirmed this bug exists on the master branch of geopandas.

---

**Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) detailing how to provide the necessary information for us to reproduce your bug.

#### Code Sample, a copy-pastable example

```python
import geopandas as gpd
gpd.GeoDataFrame(geometry=[]).to_file("test", driver="GeoJSON")
```
raises
```python
Traceback (most recent call last):

  File "/tmp/ipykernel_2102262/1921918865.py", line 1, in <module>
    gpd.GeoDataFrame(geometry=[]).to_file("test", driver="GeoJSON")

  File "/home/lukas/anaconda3/envs/tf_gpu/lib/python3.9/site-packages/geopandas/geodataframe.py", line 1086, in to_file
    _to_file(self, filename, driver, schema, index, **kwargs)

  File "/home/lukas/anaconda3/envs/tf_gpu/lib/python3.9/site-packages/geopandas/io/file.py", line 304, in _to_file
    schema = infer_schema(df)

  File "/home/lukas/anaconda3/envs/tf_gpu/lib/python3.9/site-packages/geopandas/io/file.py", line 362, in infer_schema
    raise ValueError("Cannot write empty DataFrame to file.")

ValueError: Cannot write empty DataFrame to file.
```

#### Problem description

It's perfectly valid to write an empty GeoJSON. While this may not be true for other filetypes that Geopandas supports, Geopandas shouldn't prevent you from writing an empty GeoJSON.

It was possible, until this PR: https://github.com/geopandas/geopandas/pull/663

Why is this important? When dealing with large amounts of data, it would be a lot of effort to special-case the edge case of an empty collection everywhere that it occurs.

#### Expected Output

By commenting out [these lines](https://github.com/geopandas/geopandas/blob/master/geopandas/io/file.py#L427-L428), Geopandas writes a valid GeoJSON file that looks like this:

```json
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [

]
}
```
This is the expected output.

#### Output of ``geopandas.show_versions()``

<details>

SYSTEM INFO
-----------
python     : 3.9.7 (default, Sep 16 2021, 13:09:58)  [GCC 7.5.0]
executable : /home/lukas/anaconda3/envs/tf_gpu/bin/python
machine    : Linux-5.4.0-80-generic-x86_64-with-glibc2.31

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : 3.3.0
GDAL data dir: /home/lukas/anaconda3/envs/tf_gpu/lib/python3.9/site-packages/fiona/gdal_data
PROJ       : 8.1.1
PROJ data dir: /home/lukas/anaconda3/envs/tf_gpu/lib/python3.9/site-packages/pyproj/proj_dir/share/proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 0.9.0
pandas     : 1.3.4
fiona      : 1.8.20
numpy      : 1.19.5
shapely    : 1.8.0
rtree      : 0.9.7
pyproj     : 3.2.1
matplotlib : 3.4.3
mapclassify: None
geopy      : None
psycopg2   : None
geoalchemy2: None
pyarrow    : None
pygeos     : 0.10.2

</details>



Discussion:
Thanks for moving it to an issue! 

It seems that since #663, `Fiona` enhanced its support for this situation and most of the drivers actually create a file now. 

```py
import fiona
import geopandas

drivers = fiona.supported_drivers
supported = [driver for driver, support in drivers.items() if "w" in support]

df = geopandas.GeoDataFrame(geometry=[])
for i, driver in enumerate(supported):
    try:
        df.to_file(f"file_{driver}", driver=driver)
        print(f"{driver} succeeded")
    except Exception:
        print(f"{driver} failed")

DXF failed
CSV succeeded
ESRI Shapefile succeeded
FlatGeobuf succeeded
GeoJSON succeeded
GeoJSONSeq succeeded
GPKG succeeded
GML succeeded
OGR_GMT succeeded
GPX failed
GPSTrackMaker failed
MapInfo File succeeded
DGN failed
PCIDSK succeeded
SQLite succeeded
```

I think that it could be safe to turn the error into a warning but let fiona handle it. PR for this change is welcome!

Resolving patch:
diff --git a/geopandas/io/file.py b/geopandas/io/file.py
--- a/geopandas/io/file.py
+++ b/geopandas/io/file.py
@@ -256,8 +256,6 @@ def _read_file(filename, bbox=None, mask=None, rows=None, **kwargs):
 
 
 def read_file(*args, **kwargs):
-    import warnings
-
     warnings.warn(
         "geopandas.io.file.read_file() is intended for internal "
         "use only, and will be deprecated. Use geopandas.read_file() instead.",
@@ -269,8 +267,6 @@ def read_file(*args, **kwargs):
 
 
 def to_file(*args, **kwargs):
-    import warnings
-
     warnings.warn(
         "geopandas.io.file.to_file() is intended for internal "
         "use only, and will be deprecated. Use GeoDataFrame.to_file() "
@@ -432,7 +428,12 @@ def convert_type(column, in_type):
     )
 
     if df.empty:
-        raise ValueError("Cannot write empty DataFrame to file.")
+        warnings.warn(
+            "You are attempting to write an empty DataFrame to file. "
+            "For some drivers, this operation may fail.",
+            UserWarning,
+            stacklevel=3,
+        )
 
     # Since https://github.com/Toblerity/Fiona/issues/446 resolution,
     # Fiona allows a list of geometry types


Test patch:
diff --git a/geopandas/io/tests/test_file.py b/geopandas/io/tests/test_file.py
--- a/geopandas/io/tests/test_file.py
+++ b/geopandas/io/tests/test_file.py
@@ -255,7 +255,7 @@ def test_to_file_int64(tmpdir, df_points):
 def test_to_file_empty(tmpdir):
     input_empty_df = GeoDataFrame(columns=["geometry"])
     tempfilename = os.path.join(str(tmpdir), "test.shp")
-    with pytest.raises(ValueError, match="Cannot write empty DataFrame to file."):
+    with pytest.warns(UserWarning):
         input_empty_df.to_file(tempfilename)