We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ENH: Carry original schema and use it during to_file
Following the discussion in #1185, I have drafted a PR which saves schema during `read_file` to `gdf.schema` and then during `to_file` checks if the original schema for each column is still applicable. If so, it uses it, if not it infers new one based on the dtype as we do it now.

I am pretty sure that there will be some corner cases which are not covered here, but I wanted to have this PR opened so we can discuss the implementation. But for the cases described in #1185 and earlier in #177, this should work.

```
path = gpd.datasets.get_path('nybb')
gdf = gpd.read_file(path)
gdf.schema  # original schema from fiona

{'properties': OrderedDict([('BoroCode', 'int:4'),
              ('BoroName', 'str:32'),
              ('Shape_Leng', 'float:19.11'),
              ('Shape_Area', 'float:19.11')]),
 'geometry': 'Polygon'}
```

```
gpd.io.file.infer_schema(gdf)
{'geometry': 'MultiPolygon',

 'properties': OrderedDict([('BoroCode', 'int:4'),
              ('BoroName', 'str:32'),
              ('Shape_Leng', 'float:19.11'),
              ('Shape_Area', 'float:19.11')])}
```

On master:
```
gpd.io.file.infer_schema(gdf)

{'geometry': 'MultiPolygon',
 'properties': OrderedDict([('BoroCode', 'int'),
              ('BoroName', 'str'),
              ('Shape_Leng', 'float'),
              ('Shape_Area', 'float')])}
```

Closes #1185 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `geopandas/io/file.py`
Content:
```
1 from distutils.version import LooseVersion
2 
3 import numpy as np
4 
5 import fiona
6 
7 from geopandas import GeoDataFrame, GeoSeries
8 
9 try:
10     from fiona import Env as fiona_env
11 except ImportError:
12     from fiona import drivers as fiona_env
13 # Adapted from pandas.io.common
14 from urllib.request import urlopen as _urlopen
15 from urllib.parse import urlparse as parse_url
16 from urllib.parse import uses_relative, uses_netloc, uses_params
17 
18 _FIONA18 = LooseVersion(fiona.__version__) >= LooseVersion("1.8")
19 
20 
21 _VALID_URLS = set(uses_relative + uses_netloc + uses_params)
22 _VALID_URLS.discard("")
23 
24 
25 def _is_url(url):
26     """Check to see if *url* has a valid protocol."""
27     try:
28         return parse_url(url).scheme in _VALID_URLS
29     except Exception:
30         return False
31 
32 
33 def read_file(filename, bbox=None, **kwargs):
34     """
35     Returns a GeoDataFrame from a file or URL.
36 
37     Parameters
38     ----------
39     filename: str
40         Either the absolute or relative path to the file or URL to
41         be opened.
42     bbox : tuple | GeoDataFrame or GeoSeries, default None
43         Filter features by given bounding box, GeoSeries, or GeoDataFrame.
44         CRS mis-matches are resolved if given a GeoSeries or GeoDataFrame.
45     **kwargs:
46         Keyword args to be passed to the `open` or `BytesCollection` method
47         in the fiona library when opening the file. For more information on
48         possible keywords, type:
49         ``import fiona; help(fiona.open)``
50 
51     Examples
52     --------
53     >>> df = geopandas.read_file("nybb.shp")
54 
55     Returns
56     -------
57     geodataframe : GeoDataFrame
58     """
59     if _is_url(filename):
60         req = _urlopen(filename)
61         path_or_bytes = req.read()
62         reader = fiona.BytesCollection
63     else:
64         path_or_bytes = filename
65         reader = fiona.open
66 
67     with fiona_env():
68         with reader(path_or_bytes, **kwargs) as features:
69 
70             # In a future Fiona release the crs attribute of features will
71             # no longer be a dict. The following code will be both forward
72             # and backward compatible.
73             if hasattr(features.crs, "to_dict"):
74                 crs = features.crs.to_dict()
75             else:
76                 crs = features.crs
77 
78             if bbox is not None:
79                 if isinstance(bbox, GeoDataFrame) or isinstance(bbox, GeoSeries):
80                     bbox = tuple(bbox.to_crs(crs).total_bounds)
81                 assert len(bbox) == 4
82                 f_filt = features.filter(bbox=bbox)
83             else:
84                 f_filt = features
85 
86             columns = list(features.meta["schema"]["properties"]) + ["geometry"]
87             gdf = GeoDataFrame.from_features(f_filt, crs=crs, columns=columns)
88 
89     return gdf
90 
91 
92 def to_file(df, filename, driver="ESRI Shapefile", schema=None, **kwargs):
93     """
94     Write this GeoDataFrame to an OGR data source
95 
96     A dictionary of supported OGR providers is available via:
97     >>> import fiona
98     >>> fiona.supported_drivers
99 
100     Parameters
101     ----------
102     df : GeoDataFrame to be written
103     filename : string
104         File path or file handle to write to.
105     driver : string, default 'ESRI Shapefile'
106         The OGR format driver used to write the vector file.
107     schema : dict, default None
108         If specified, the schema dictionary is passed to Fiona to
109         better control how the file is written. If None, GeoPandas
110         will determine the schema based on each column's dtype
111 
112     The *kwargs* are passed to fiona.open and can be used to write
113     to multi-layer data, store data within archives (zip files), etc.
114     The path may specify a fiona VSI scheme.
115     """
116     if schema is None:
117         schema = infer_schema(df)
118     with fiona_env():
119         with fiona.open(
120             filename, "w", driver=driver, crs=df.crs, schema=schema, **kwargs
121         ) as colxn:
122             colxn.writerecords(df.iterfeatures())
123 
124 
125 def infer_schema(df):
126     try:
127         from collections import OrderedDict
128     except ImportError:
129         from ordereddict import OrderedDict
130 
131     def convert_type(column, in_type):
132         if in_type == object:
133             return "str"
134         if in_type.name.startswith("datetime64"):
135             # numpy datetime type regardless of frequency
136             return "datetime"
137         out_type = type(np.zeros(1, in_type).item()).__name__
138         if out_type == "long":
139             out_type = "int"
140         if not _FIONA18 and out_type == "bool":
141             raise ValueError(
142                 'column "{}" is boolean type, '.format(column)
143                 + "which is unsupported in file writing with fiona "
144                 "< 1.8. Consider casting the column to int type."
145             )
146         return out_type
147 
148     properties = OrderedDict(
149         [
150             (col, convert_type(col, _type))
151             for col, _type in zip(df.columns, df.dtypes)
152             if col != df._geometry_column_name
153         ]
154     )
155 
156     if df.empty:
157         raise ValueError("Cannot write empty DataFrame to file.")
158 
159     # Since https://github.com/Toblerity/Fiona/issues/446 resolution,
160     # Fiona allows a list of geometry types
161     geom_types = _geometry_types(df)
162 
163     schema = {"geometry": geom_types, "properties": properties}
164 
165     return schema
166 
167 
168 def _geometry_types(df):
169     """
170     Determine the geometry types in the GeoDataFrame for the schema.
171     """
172     if _FIONA18:
173         # Starting from Fiona 1.8, schema submitted to fiona to write a gdf
174         # can have mixed geometries:
175         # - 3D and 2D shapes can coexist in inferred schema
176         # - Shape and MultiShape types can (and must) coexist in inferred
177         #   schema
178         geom_types_2D = df[~df.geometry.has_z].geometry.geom_type.unique()
179         geom_types_2D = [gtype for gtype in geom_types_2D if gtype is not None]
180         geom_types_3D = df[df.geometry.has_z].geometry.geom_type.unique()
181         geom_types_3D = ["3D " + gtype for gtype in geom_types_3D if gtype is not None]
182         geom_types = geom_types_3D + geom_types_2D
183 
184     else:
185         # Before Fiona 1.8, schema submitted to write a gdf should have
186         # one single geometry type whenever possible:
187         # - 3D and 2D shapes cannot coexist in inferred schema
188         # - Shape and MultiShape can not coexist in inferred schema
189         geom_types = _geometry_types_back_compat(df)
190 
191     if len(geom_types) == 0:
192         # Default geometry type supported by Fiona
193         # (Since https://github.com/Toblerity/Fiona/issues/446 resolution)
194         return "Unknown"
195 
196     if len(geom_types) == 1:
197         geom_types = geom_types[0]
198 
199     return geom_types
200 
201 
202 def _geometry_types_back_compat(df):
203     """
204     for backward compatibility with Fiona<1.8 only
205     """
206     unique_geom_types = df.geometry.geom_type.unique()
207     unique_geom_types = [gtype for gtype in unique_geom_types if gtype is not None]
208 
209     # merge single and Multi types (eg Polygon and MultiPolygon)
210     unique_geom_types = [
211         gtype
212         for gtype in unique_geom_types
213         if not gtype.startswith("Multi") or gtype[5:] not in unique_geom_types
214     ]
215 
216     if df.geometry.has_z.any():
217         # declare all geometries as 3D geometries
218         unique_geom_types = ["3D " + type for type in unique_geom_types]
219     # by default, all geometries are 2D geometries
220 
221     return unique_geom_types
222
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/geopandas/io/file.py b/geopandas/io/file.py
--- a/geopandas/io/file.py
+++ b/geopandas/io/file.py
@@ -123,10 +123,10 @@
 
 
 def infer_schema(df):
-    try:
-        from collections import OrderedDict
-    except ImportError:
-        from ordereddict import OrderedDict
+    from collections import OrderedDict
+
+    # TODO: test pandas string type and boolean type once released
+    types = {"Int64": "int", "string": "str", "boolean": "bool"}
 
     def convert_type(column, in_type):
         if in_type == object:
@@ -134,7 +134,10 @@
         if in_type.name.startswith("datetime64"):
             # numpy datetime type regardless of frequency
             return "datetime"
-        out_type = type(np.zeros(1, in_type).item()).__name__
+        if str(in_type) in types:
+            out_type = types[str(in_type)]
+        else:
+            out_type = type(np.zeros(1, in_type).item()).__name__
         if out_type == "long":
             out_type = "int"
         if not _FIONA18 and out_type == "bool":

{"golden_diff": "diff --git a/geopandas/io/file.py b/geopandas/io/file.py\n--- a/geopandas/io/file.py\n+++ b/geopandas/io/file.py\n@@ -123,10 +123,10 @@\n \n \n def infer_schema(df):\n-    try:\n-        from collections import OrderedDict\n-    except ImportError:\n-        from ordereddict import OrderedDict\n+    from collections import OrderedDict\n+\n+    # TODO: test pandas string type and boolean type once released\n+    types = {\"Int64\": \"int\", \"string\": \"str\", \"boolean\": \"bool\"}\n \n     def convert_type(column, in_type):\n         if in_type == object:\n@@ -134,7 +134,10 @@\n         if in_type.name.startswith(\"datetime64\"):\n             # numpy datetime type regardless of frequency\n             return \"datetime\"\n-        out_type = type(np.zeros(1, in_type).item()).__name__\n+        if str(in_type) in types:\n+            out_type = types[str(in_type)]\n+        else:\n+            out_type = type(np.zeros(1, in_type).item()).__name__\n         if out_type == \"long\":\n             out_type = \"int\"\n         if not _FIONA18 and out_type == \"bool\":\n", "issue": "ENH: Carry original schema and use it during to_file\nFollowing the discussion in #1185, I have drafted a PR which saves schema during `read_file` to `gdf.schema` and then during `to_file` checks if the original schema for each column is still applicable. If so, it uses it, if not it infers new one based on the dtype as we do it now.\r\n\r\nI am pretty sure that there will be some corner cases which are not covered here, but I wanted to have this PR opened so we can discuss the implementation. But for the cases described in #1185 and earlier in #177, this should work.\r\n\r\n```\r\npath = gpd.datasets.get_path('nybb')\r\ngdf = gpd.read_file(path)\r\ngdf.schema  # original schema from fiona\r\n\r\n{'properties': OrderedDict([('BoroCode', 'int:4'),\r\n              ('BoroName', 'str:32'),\r\n              ('Shape_Leng', 'float:19.11'),\r\n              ('Shape_Area', 'float:19.11')]),\r\n 'geometry': 'Polygon'}\r\n```\r\n\r\n```\r\ngpd.io.file.infer_schema(gdf)\r\n{'geometry': 'MultiPolygon',\r\n\r\n 'properties': OrderedDict([('BoroCode', 'int:4'),\r\n              ('BoroName', 'str:32'),\r\n              ('Shape_Leng', 'float:19.11'),\r\n              ('Shape_Area', 'float:19.11')])}\r\n```\r\n\r\nOn master:\r\n```\r\ngpd.io.file.infer_schema(gdf)\r\n\r\n{'geometry': 'MultiPolygon',\r\n 'properties': OrderedDict([('BoroCode', 'int'),\r\n              ('BoroName', 'str'),\r\n              ('Shape_Leng', 'float'),\r\n              ('Shape_Area', 'float')])}\r\n```\r\n\r\nCloses #1185 \n", "before_files": [{"content": "from distutils.version import LooseVersion\n\nimport numpy as np\n\nimport fiona\n\nfrom geopandas import GeoDataFrame, GeoSeries\n\ntry:\n    from fiona import Env as fiona_env\nexcept ImportError:\n    from fiona import drivers as fiona_env\n# Adapted from pandas.io.common\nfrom urllib.request import urlopen as _urlopen\nfrom urllib.parse import urlparse as parse_url\nfrom urllib.parse import uses_relative, uses_netloc, uses_params\n\n_FIONA18 = LooseVersion(fiona.__version__) >= LooseVersion(\"1.8\")\n\n\n_VALID_URLS = set(uses_relative + uses_netloc + uses_params)\n_VALID_URLS.discard(\"\")\n\n\ndef _is_url(url):\n    \"\"\"Check to see if *url* has a valid protocol.\"\"\"\n    try:\n        return parse_url(url).scheme in _VALID_URLS\n    except Exception:\n        return False\n\n\ndef read_file(filename, bbox=None, **kwargs):\n    \"\"\"\n    Returns a GeoDataFrame from a file or URL.\n\n    Parameters\n    ----------\n    filename: str\n        Either the absolute or relative path to the file or URL to\n        be opened.\n    bbox : tuple | GeoDataFrame or GeoSeries, default None\n        Filter features by given bounding box, GeoSeries, or GeoDataFrame.\n        CRS mis-matches are resolved if given a GeoSeries or GeoDataFrame.\n    **kwargs:\n        Keyword args to be passed to the `open` or `BytesCollection` method\n        in the fiona library when opening the file. For more information on\n        possible keywords, type:\n        ``import fiona; help(fiona.open)``\n\n    Examples\n    --------\n    >>> df = geopandas.read_file(\"nybb.shp\")\n\n    Returns\n    -------\n    geodataframe : GeoDataFrame\n    \"\"\"\n    if _is_url(filename):\n        req = _urlopen(filename)\n        path_or_bytes = req.read()\n        reader = fiona.BytesCollection\n    else:\n        path_or_bytes = filename\n        reader = fiona.open\n\n    with fiona_env():\n        with reader(path_or_bytes, **kwargs) as features:\n\n            # In a future Fiona release the crs attribute of features will\n            # no longer be a dict. The following code will be both forward\n            # and backward compatible.\n            if hasattr(features.crs, \"to_dict\"):\n                crs = features.crs.to_dict()\n            else:\n                crs = features.crs\n\n            if bbox is not None:\n                if isinstance(bbox, GeoDataFrame) or isinstance(bbox, GeoSeries):\n                    bbox = tuple(bbox.to_crs(crs).total_bounds)\n                assert len(bbox) == 4\n                f_filt = features.filter(bbox=bbox)\n            else:\n                f_filt = features\n\n            columns = list(features.meta[\"schema\"][\"properties\"]) + [\"geometry\"]\n            gdf = GeoDataFrame.from_features(f_filt, crs=crs, columns=columns)\n\n    return gdf\n\n\ndef to_file(df, filename, driver=\"ESRI Shapefile\", schema=None, **kwargs):\n    \"\"\"\n    Write this GeoDataFrame to an OGR data source\n\n    A dictionary of supported OGR providers is available via:\n    >>> import fiona\n    >>> fiona.supported_drivers\n\n    Parameters\n    ----------\n    df : GeoDataFrame to be written\n    filename : string\n        File path or file handle to write to.\n    driver : string, default 'ESRI Shapefile'\n        The OGR format driver used to write the vector file.\n    schema : dict, default None\n        If specified, the schema dictionary is passed to Fiona to\n        better control how the file is written. If None, GeoPandas\n        will determine the schema based on each column's dtype\n\n    The *kwargs* are passed to fiona.open and can be used to write\n    to multi-layer data, store data within archives (zip files), etc.\n    The path may specify a fiona VSI scheme.\n    \"\"\"\n    if schema is None:\n        schema = infer_schema(df)\n    with fiona_env():\n        with fiona.open(\n            filename, \"w\", driver=driver, crs=df.crs, schema=schema, **kwargs\n        ) as colxn:\n            colxn.writerecords(df.iterfeatures())\n\n\ndef infer_schema(df):\n    try:\n        from collections import OrderedDict\n    except ImportError:\n        from ordereddict import OrderedDict\n\n    def convert_type(column, in_type):\n        if in_type == object:\n            return \"str\"\n        if in_type.name.startswith(\"datetime64\"):\n            # numpy datetime type regardless of frequency\n            return \"datetime\"\n        out_type = type(np.zeros(1, in_type).item()).__name__\n        if out_type == \"long\":\n            out_type = \"int\"\n        if not _FIONA18 and out_type == \"bool\":\n            raise ValueError(\n                'column \"{}\" is boolean type, '.format(column)\n                + \"which is unsupported in file writing with fiona \"\n                \"< 1.8. Consider casting the column to int type.\"\n            )\n        return out_type\n\n    properties = OrderedDict(\n        [\n            (col, convert_type(col, _type))\n            for col, _type in zip(df.columns, df.dtypes)\n            if col != df._geometry_column_name\n        ]\n    )\n\n    if df.empty:\n        raise ValueError(\"Cannot write empty DataFrame to file.\")\n\n    # Since https://github.com/Toblerity/Fiona/issues/446 resolution,\n    # Fiona allows a list of geometry types\n    geom_types = _geometry_types(df)\n\n    schema = {\"geometry\": geom_types, \"properties\": properties}\n\n    return schema\n\n\ndef _geometry_types(df):\n    \"\"\"\n    Determine the geometry types in the GeoDataFrame for the schema.\n    \"\"\"\n    if _FIONA18:\n        # Starting from Fiona 1.8, schema submitted to fiona to write a gdf\n        # can have mixed geometries:\n        # - 3D and 2D shapes can coexist in inferred schema\n        # - Shape and MultiShape types can (and must) coexist in inferred\n        #   schema\n        geom_types_2D = df[~df.geometry.has_z].geometry.geom_type.unique()\n        geom_types_2D = [gtype for gtype in geom_types_2D if gtype is not None]\n        geom_types_3D = df[df.geometry.has_z].geometry.geom_type.unique()\n        geom_types_3D = [\"3D \" + gtype for gtype in geom_types_3D if gtype is not None]\n        geom_types = geom_types_3D + geom_types_2D\n\n    else:\n        # Before Fiona 1.8, schema submitted to write a gdf should have\n        # one single geometry type whenever possible:\n        # - 3D and 2D shapes cannot coexist in inferred schema\n        # - Shape and MultiShape can not coexist in inferred schema\n        geom_types = _geometry_types_back_compat(df)\n\n    if len(geom_types) == 0:\n        # Default geometry type supported by Fiona\n        # (Since https://github.com/Toblerity/Fiona/issues/446 resolution)\n        return \"Unknown\"\n\n    if len(geom_types) == 1:\n        geom_types = geom_types[0]\n\n    return geom_types\n\n\ndef _geometry_types_back_compat(df):\n    \"\"\"\n    for backward compatibility with Fiona<1.8 only\n    \"\"\"\n    unique_geom_types = df.geometry.geom_type.unique()\n    unique_geom_types = [gtype for gtype in unique_geom_types if gtype is not None]\n\n    # merge single and Multi types (eg Polygon and MultiPolygon)\n    unique_geom_types = [\n        gtype\n        for gtype in unique_geom_types\n        if not gtype.startswith(\"Multi\") or gtype[5:] not in unique_geom_types\n    ]\n\n    if df.geometry.has_z.any():\n        # declare all geometries as 3D geometries\n        unique_geom_types = [\"3D \" + type for type in unique_geom_types]\n    # by default, all geometries are 2D geometries\n\n    return unique_geom_types\n", "path": "geopandas/io/file.py"}], "after_files": [{"content": "from distutils.version import LooseVersion\n\nimport numpy as np\n\nimport fiona\n\nfrom geopandas import GeoDataFrame, GeoSeries\n\ntry:\n    from fiona import Env as fiona_env\nexcept ImportError:\n    from fiona import drivers as fiona_env\n# Adapted from pandas.io.common\nfrom urllib.request import urlopen as _urlopen\nfrom urllib.parse import urlparse as parse_url\nfrom urllib.parse import uses_relative, uses_netloc, uses_params\n\n_FIONA18 = LooseVersion(fiona.__version__) >= LooseVersion(\"1.8\")\n\n\n_VALID_URLS = set(uses_relative + uses_netloc + uses_params)\n_VALID_URLS.discard(\"\")\n\n\ndef _is_url(url):\n    \"\"\"Check to see if *url* has a valid protocol.\"\"\"\n    try:\n        return parse_url(url).scheme in _VALID_URLS\n    except Exception:\n        return False\n\n\ndef read_file(filename, bbox=None, **kwargs):\n    \"\"\"\n    Returns a GeoDataFrame from a file or URL.\n\n    Parameters\n    ----------\n    filename: str\n        Either the absolute or relative path to the file or URL to\n        be opened.\n    bbox : tuple | GeoDataFrame or GeoSeries, default None\n        Filter features by given bounding box, GeoSeries, or GeoDataFrame.\n        CRS mis-matches are resolved if given a GeoSeries or GeoDataFrame.\n    **kwargs:\n        Keyword args to be passed to the `open` or `BytesCollection` method\n        in the fiona library when opening the file. For more information on\n        possible keywords, type:\n        ``import fiona; help(fiona.open)``\n\n    Examples\n    --------\n    >>> df = geopandas.read_file(\"nybb.shp\")\n\n    Returns\n    -------\n    geodataframe : GeoDataFrame\n    \"\"\"\n    if _is_url(filename):\n        req = _urlopen(filename)\n        path_or_bytes = req.read()\n        reader = fiona.BytesCollection\n    else:\n        path_or_bytes = filename\n        reader = fiona.open\n\n    with fiona_env():\n        with reader(path_or_bytes, **kwargs) as features:\n\n            # In a future Fiona release the crs attribute of features will\n            # no longer be a dict. The following code will be both forward\n            # and backward compatible.\n            if hasattr(features.crs, \"to_dict\"):\n                crs = features.crs.to_dict()\n            else:\n                crs = features.crs\n\n            if bbox is not None:\n                if isinstance(bbox, GeoDataFrame) or isinstance(bbox, GeoSeries):\n                    bbox = tuple(bbox.to_crs(crs).total_bounds)\n                assert len(bbox) == 4\n                f_filt = features.filter(bbox=bbox)\n            else:\n                f_filt = features\n\n            columns = list(features.meta[\"schema\"][\"properties\"]) + [\"geometry\"]\n            gdf = GeoDataFrame.from_features(f_filt, crs=crs, columns=columns)\n\n    return gdf\n\n\ndef to_file(df, filename, driver=\"ESRI Shapefile\", schema=None, **kwargs):\n    \"\"\"\n    Write this GeoDataFrame to an OGR data source\n\n    A dictionary of supported OGR providers is available via:\n    >>> import fiona\n    >>> fiona.supported_drivers\n\n    Parameters\n    ----------\n    df : GeoDataFrame to be written\n    filename : string\n        File path or file handle to write to.\n    driver : string, default 'ESRI Shapefile'\n        The OGR format driver used to write the vector file.\n    schema : dict, default None\n        If specified, the schema dictionary is passed to Fiona to\n        better control how the file is written. If None, GeoPandas\n        will determine the schema based on each column's dtype\n\n    The *kwargs* are passed to fiona.open and can be used to write\n    to multi-layer data, store data within archives (zip files), etc.\n    The path may specify a fiona VSI scheme.\n    \"\"\"\n    if schema is None:\n        schema = infer_schema(df)\n    with fiona_env():\n        with fiona.open(\n            filename, \"w\", driver=driver, crs=df.crs, schema=schema, **kwargs\n        ) as colxn:\n            colxn.writerecords(df.iterfeatures())\n\n\ndef infer_schema(df):\n    from collections import OrderedDict\n\n    # TODO: test pandas string type and boolean type once released\n    types = {\"Int64\": \"int\", \"string\": \"str\", \"boolean\": \"bool\"}\n\n    def convert_type(column, in_type):\n        if in_type == object:\n            return \"str\"\n        if in_type.name.startswith(\"datetime64\"):\n            # numpy datetime type regardless of frequency\n            return \"datetime\"\n        if str(in_type) in types:\n            out_type = types[str(in_type)]\n        else:\n            out_type = type(np.zeros(1, in_type).item()).__name__\n        if out_type == \"long\":\n            out_type = \"int\"\n        if not _FIONA18 and out_type == \"bool\":\n            raise ValueError(\n                'column \"{}\" is boolean type, '.format(column)\n                + \"which is unsupported in file writing with fiona \"\n                \"< 1.8. Consider casting the column to int type.\"\n            )\n        return out_type\n\n    properties = OrderedDict(\n        [\n            (col, convert_type(col, _type))\n            for col, _type in zip(df.columns, df.dtypes)\n            if col != df._geometry_column_name\n        ]\n    )\n\n    if df.empty:\n        raise ValueError(\"Cannot write empty DataFrame to file.\")\n\n    # Since https://github.com/Toblerity/Fiona/issues/446 resolution,\n    # Fiona allows a list of geometry types\n    geom_types = _geometry_types(df)\n\n    schema = {\"geometry\": geom_types, \"properties\": properties}\n\n    return schema\n\n\ndef _geometry_types(df):\n    \"\"\"\n    Determine the geometry types in the GeoDataFrame for the schema.\n    \"\"\"\n    if _FIONA18:\n        # Starting from Fiona 1.8, schema submitted to fiona to write a gdf\n        # can have mixed geometries:\n        # - 3D and 2D shapes can coexist in inferred schema\n        # - Shape and MultiShape types can (and must) coexist in inferred\n        #   schema\n        geom_types_2D = df[~df.geometry.has_z].geometry.geom_type.unique()\n        geom_types_2D = [gtype for gtype in geom_types_2D if gtype is not None]\n        geom_types_3D = df[df.geometry.has_z].geometry.geom_type.unique()\n        geom_types_3D = [\"3D \" + gtype for gtype in geom_types_3D if gtype is not None]\n        geom_types = geom_types_3D + geom_types_2D\n\n    else:\n        # Before Fiona 1.8, schema submitted to write a gdf should have\n        # one single geometry type whenever possible:\n        # - 3D and 2D shapes cannot coexist in inferred schema\n        # - Shape and MultiShape can not coexist in inferred schema\n        geom_types = _geometry_types_back_compat(df)\n\n    if len(geom_types) == 0:\n        # Default geometry type supported by Fiona\n        # (Since https://github.com/Toblerity/Fiona/issues/446 resolution)\n        return \"Unknown\"\n\n    if len(geom_types) == 1:\n        geom_types = geom_types[0]\n\n    return geom_types\n\n\ndef _geometry_types_back_compat(df):\n    \"\"\"\n    for backward compatibility with Fiona<1.8 only\n    \"\"\"\n    unique_geom_types = df.geometry.geom_type.unique()\n    unique_geom_types = [gtype for gtype in unique_geom_types if gtype is not None]\n\n    # merge single and Multi types (eg Polygon and MultiPolygon)\n    unique_geom_types = [\n        gtype\n        for gtype in unique_geom_types\n        if not gtype.startswith(\"Multi\") or gtype[5:] not in unique_geom_types\n    ]\n\n    if df.geometry.has_z.any():\n        # declare all geometries as 3D geometries\n        unique_geom_types = [\"3D \" + type for type in unique_geom_types]\n    # by default, all geometries are 2D geometries\n\n    return unique_geom_types\n", "path": "geopandas/io/file.py"}]}