We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
BUG: clip GeoSeries by non-overlapping mask raises error
Clipping GeoSeries by non-overlapping `mask` raises error as we try to return GeoDataFrame with original columns in here: https://github.com/geopandas/geopandas/blob/7350b49688f51b281f69638c8c51a448a0115fb0/geopandas/tools/clip.py#L155

In case of GeoSeries we should return empty GeoSeries I would say.
MRE:

```py
import geopandas as gpd
from shapely.geometry import Point

s = gpd.GeoSeries([Point(0,0), Point(1,1)])
mask = Point(10, 10).buffer(2)
gpd.clip(s, mask)
```


<details>

```
~/Dropbox/Python/geopandas/martinfleis/geopandas/tools/clip.py in clip(gdf, mask, keep_geom_type)
    153         and ((box_mask[1] <= box_gdf[3]) and (box_gdf[1] <= box_mask[3]))
    154     ):
--> 155         return GeoDataFrame(columns=gdf.columns, crs=gdf.crs)
    156 
    157     if isinstance(mask, (GeoDataFrame, GeoSeries)):

~/anaconda3/envs/geo_dev/lib/python3.8/site-packages/pandas/core/generic.py in __getattr__(self, name)
   5173             or name in self._accessors
   5174         ):
-> 5175             return object.__getattribute__(self, name)
   5176         else:
   5177             if self._info_axis._can_hold_identifiers_and_holds_name(name):

AttributeError: 'GeoSeries' object has no attribute 'columns'
```

</details>


--- 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/tools/clip.py`
Content:
```
1 """
2 geopandas.clip
3 ==============
4 
5 A module to clip vector data using GeoPandas.
6 
7 """
8 import warnings
9 
10 import numpy as np
11 import pandas as pd
12 
13 from shapely.geometry import Polygon, MultiPolygon
14 
15 from geopandas import GeoDataFrame, GeoSeries
16 
17 
18 def _clip_points(gdf, poly):
19     """Clip point geometry to the polygon extent.
20 
21     Clip an input point GeoDataFrame to the polygon extent of the poly
22     parameter. Points that intersect the poly geometry are extracted with
23     associated attributes and returned.
24 
25     Parameters
26     ----------
27     gdf : GeoDataFrame, GeoSeries
28         Composed of point geometry that will be clipped to the poly.
29 
30     poly : (Multi)Polygon
31         Reference geometry used to spatially clip the data.
32 
33     Returns
34     -------
35     GeoDataFrame
36         The returned GeoDataFrame is a subset of gdf that intersects
37         with poly.
38     """
39     spatial_index = gdf.sindex
40     bbox = poly.bounds
41     sidx = list(spatial_index.intersection(bbox))
42     gdf_sub = gdf.iloc[sidx]
43 
44     return gdf_sub[gdf_sub.geometry.intersects(poly)]
45 
46 
47 def _clip_line_poly(gdf, poly):
48     """Clip line and polygon geometry to the polygon extent.
49 
50     Clip an input line or polygon to the polygon extent of the poly
51     parameter. Parts of Lines or Polygons that intersect the poly geometry are
52     extracted with associated attributes and returned.
53 
54     Parameters
55     ----------
56     gdf : GeoDataFrame, GeoSeries
57         Line or polygon geometry that is clipped to poly.
58 
59     poly : (Multi)Polygon
60         Reference polygon for clipping.
61 
62     Returns
63     -------
64     GeoDataFrame
65         The returned GeoDataFrame is a clipped subset of gdf
66         that intersects with poly.
67     """
68     spatial_index = gdf.sindex
69 
70     # Create a box for the initial intersection
71     bbox = poly.bounds
72     # Get a list of id's for each object that overlaps the bounding box and
73     # subset the data to just those lines
74     sidx = list(spatial_index.intersection(bbox))
75     gdf_sub = gdf.iloc[sidx]
76 
77     # Clip the data with the polygon
78     if isinstance(gdf_sub, GeoDataFrame):
79         clipped = gdf_sub.copy()
80         clipped["geometry"] = gdf_sub.intersection(poly)
81 
82         # Return the clipped layer with no null geometry values or empty geometries
83         return clipped[~clipped.geometry.is_empty & clipped.geometry.notnull()]
84     else:
85         # GeoSeries
86         clipped = gdf_sub.intersection(poly)
87         return clipped[~clipped.is_empty & clipped.notnull()]
88 
89 
90 def clip(gdf, mask, keep_geom_type=False):
91     """Clip points, lines, or polygon geometries to the mask extent.
92 
93     Both layers must be in the same Coordinate Reference System (CRS).
94     The `gdf` will be clipped to the full extent of the clip object.
95 
96     If there are multiple polygons in mask, data from `gdf` will be
97     clipped to the total boundary of all polygons in mask.
98 
99     Parameters
100     ----------
101     gdf : GeoDataFrame or GeoSeries
102         Vector layer (point, line, polygon) to be clipped to mask.
103     mask : GeoDataFrame, GeoSeries, (Multi)Polygon
104         Polygon vector layer used to clip `gdf`.
105         The mask's geometry is dissolved into one geometric feature
106         and intersected with `gdf`.
107     keep_geom_type : boolean, default False
108         If True, return only geometries of original type in case of intersection
109         resulting in multiple geometry types or GeometryCollections.
110         If False, return all resulting geometries (potentially mixed-types).
111 
112     Returns
113     -------
114     GeoDataFrame or GeoSeries
115          Vector data (points, lines, polygons) from `gdf` clipped to
116          polygon boundary from mask.
117 
118     Examples
119     --------
120     Clip points (global cities) with a polygon (the South American continent):
121 
122     >>> import geopandas
123     >>> path =
124     >>> world = geopandas.read_file(
125     ...     geopandas.datasets.get_path('naturalearth_lowres'))
126     >>> south_america = world[world['continent'] == "South America"]
127     >>> capitals = geopandas.read_file(
128     ...     geopandas.datasets.get_path('naturalearth_cities'))
129     >>> capitals.shape
130     (202, 2)
131     >>> sa_capitals = geopandas.clip(capitals, south_america)
132     >>> sa_capitals.shape
133     (12, 2)
134     """
135     if not isinstance(gdf, (GeoDataFrame, GeoSeries)):
136         raise TypeError(
137             "'gdf' should be GeoDataFrame or GeoSeries, got {}".format(type(gdf))
138         )
139 
140     if not isinstance(mask, (GeoDataFrame, GeoSeries, Polygon, MultiPolygon)):
141         raise TypeError(
142             "'mask' should be GeoDataFrame, GeoSeries or"
143             "(Multi)Polygon, got {}".format(type(gdf))
144         )
145 
146     if isinstance(mask, (GeoDataFrame, GeoSeries)):
147         box_mask = mask.total_bounds
148     else:
149         box_mask = mask.bounds
150     box_gdf = gdf.total_bounds
151     if not (
152         ((box_mask[0] <= box_gdf[2]) and (box_gdf[0] <= box_mask[2]))
153         and ((box_mask[1] <= box_gdf[3]) and (box_gdf[1] <= box_mask[3]))
154     ):
155         return GeoDataFrame(columns=gdf.columns, crs=gdf.crs)
156 
157     if isinstance(mask, (GeoDataFrame, GeoSeries)):
158         poly = mask.geometry.unary_union
159     else:
160         poly = mask
161 
162     geom_types = gdf.geometry.type
163     poly_idx = np.asarray((geom_types == "Polygon") | (geom_types == "MultiPolygon"))
164     line_idx = np.asarray(
165         (geom_types == "LineString")
166         | (geom_types == "LinearRing")
167         | (geom_types == "MultiLineString")
168     )
169     point_idx = np.asarray((geom_types == "Point") | (geom_types == "MultiPoint"))
170     geomcoll_idx = np.asarray((geom_types == "GeometryCollection"))
171 
172     if point_idx.any():
173         point_gdf = _clip_points(gdf[point_idx], poly)
174     else:
175         point_gdf = None
176 
177     if poly_idx.any():
178         poly_gdf = _clip_line_poly(gdf[poly_idx], poly)
179     else:
180         poly_gdf = None
181 
182     if line_idx.any():
183         line_gdf = _clip_line_poly(gdf[line_idx], poly)
184     else:
185         line_gdf = None
186 
187     if geomcoll_idx.any():
188         geomcoll_gdf = _clip_line_poly(gdf[geomcoll_idx], poly)
189     else:
190         geomcoll_gdf = None
191 
192     order = pd.Series(range(len(gdf)), index=gdf.index)
193     concat = pd.concat([point_gdf, line_gdf, poly_gdf, geomcoll_gdf])
194 
195     if keep_geom_type:
196         geomcoll_concat = (concat.geom_type == "GeometryCollection").any()
197         geomcoll_orig = geomcoll_idx.any()
198 
199         new_collection = geomcoll_concat and not geomcoll_orig
200 
201         if geomcoll_orig:
202             warnings.warn(
203                 "keep_geom_type can not be called on a "
204                 "GeoDataFrame with GeometryCollection."
205             )
206         else:
207             polys = ["Polygon", "MultiPolygon"]
208             lines = ["LineString", "MultiLineString", "LinearRing"]
209             points = ["Point", "MultiPoint"]
210 
211             # Check that the gdf for multiple geom types (points, lines and/or polys)
212             orig_types_total = sum(
213                 [
214                     gdf.geom_type.isin(polys).any(),
215                     gdf.geom_type.isin(lines).any(),
216                     gdf.geom_type.isin(points).any(),
217                 ]
218             )
219 
220             # Check how many geometry types are in the clipped GeoDataFrame
221             clip_types_total = sum(
222                 [
223                     concat.geom_type.isin(polys).any(),
224                     concat.geom_type.isin(lines).any(),
225                     concat.geom_type.isin(points).any(),
226                 ]
227             )
228 
229             # Check there aren't any new geom types in the clipped GeoDataFrame
230             more_types = orig_types_total < clip_types_total
231 
232             if orig_types_total > 1:
233                 warnings.warn(
234                     "keep_geom_type can not be called on a mixed type GeoDataFrame."
235                 )
236             elif new_collection or more_types:
237                 orig_type = gdf.geom_type.iloc[0]
238                 if new_collection:
239                     concat = concat.explode()
240                 if orig_type in polys:
241                     concat = concat.loc[concat.geom_type.isin(polys)]
242                 elif orig_type in lines:
243                     concat = concat.loc[concat.geom_type.isin(lines)]
244 
245     # preserve the original order of the input
246     if isinstance(concat, GeoDataFrame):
247         concat["_order"] = order
248         return concat.sort_values(by="_order").drop(columns="_order")
249     else:
250         concat = GeoDataFrame(geometry=concat)
251         concat["_order"] = order
252         return concat.sort_values(by="_order").geometry
253
```
--- 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/tools/clip.py b/geopandas/tools/clip.py
--- a/geopandas/tools/clip.py
+++ b/geopandas/tools/clip.py
@@ -152,7 +152,11 @@
         ((box_mask[0] <= box_gdf[2]) and (box_gdf[0] <= box_mask[2]))
         and ((box_mask[1] <= box_gdf[3]) and (box_gdf[1] <= box_mask[3]))
     ):
-        return GeoDataFrame(columns=gdf.columns, crs=gdf.crs)
+        return (
+            GeoDataFrame(columns=gdf.columns, crs=gdf.crs)
+            if isinstance(gdf, GeoDataFrame)
+            else GeoSeries(crs=gdf.crs)
+        )
 
     if isinstance(mask, (GeoDataFrame, GeoSeries)):
         poly = mask.geometry.unary_union

{"golden_diff": "diff --git a/geopandas/tools/clip.py b/geopandas/tools/clip.py\n--- a/geopandas/tools/clip.py\n+++ b/geopandas/tools/clip.py\n@@ -152,7 +152,11 @@\n         ((box_mask[0] <= box_gdf[2]) and (box_gdf[0] <= box_mask[2]))\n         and ((box_mask[1] <= box_gdf[3]) and (box_gdf[1] <= box_mask[3]))\n     ):\n-        return GeoDataFrame(columns=gdf.columns, crs=gdf.crs)\n+        return (\n+            GeoDataFrame(columns=gdf.columns, crs=gdf.crs)\n+            if isinstance(gdf, GeoDataFrame)\n+            else GeoSeries(crs=gdf.crs)\n+        )\n \n     if isinstance(mask, (GeoDataFrame, GeoSeries)):\n         poly = mask.geometry.unary_union\n", "issue": "BUG: clip GeoSeries by non-overlapping mask raises error\nClipping GeoSeries by non-overlapping `mask` raises error as we try to return GeoDataFrame with original columns in here: https://github.com/geopandas/geopandas/blob/7350b49688f51b281f69638c8c51a448a0115fb0/geopandas/tools/clip.py#L155\r\n\r\nIn case of GeoSeries we should return empty GeoSeries I would say.\r\nMRE:\r\n\r\n```py\r\nimport geopandas as gpd\r\nfrom shapely.geometry import Point\r\n\r\ns = gpd.GeoSeries([Point(0,0), Point(1,1)])\r\nmask = Point(10, 10).buffer(2)\r\ngpd.clip(s, mask)\r\n```\r\n\r\n\r\n<details>\r\n\r\n```\r\n~/Dropbox/Python/geopandas/martinfleis/geopandas/tools/clip.py in clip(gdf, mask, keep_geom_type)\r\n    153         and ((box_mask[1] <= box_gdf[3]) and (box_gdf[1] <= box_mask[3]))\r\n    154     ):\r\n--> 155         return GeoDataFrame(columns=gdf.columns, crs=gdf.crs)\r\n    156 \r\n    157     if isinstance(mask, (GeoDataFrame, GeoSeries)):\r\n\r\n~/anaconda3/envs/geo_dev/lib/python3.8/site-packages/pandas/core/generic.py in __getattr__(self, name)\r\n   5173             or name in self._accessors\r\n   5174         ):\r\n-> 5175             return object.__getattribute__(self, name)\r\n   5176         else:\r\n   5177             if self._info_axis._can_hold_identifiers_and_holds_name(name):\r\n\r\nAttributeError: 'GeoSeries' object has no attribute 'columns'\r\n```\r\n\r\n</details>\r\n\n", "before_files": [{"content": "\"\"\"\ngeopandas.clip\n==============\n\nA module to clip vector data using GeoPandas.\n\n\"\"\"\nimport warnings\n\nimport numpy as np\nimport pandas as pd\n\nfrom shapely.geometry import Polygon, MultiPolygon\n\nfrom geopandas import GeoDataFrame, GeoSeries\n\n\ndef _clip_points(gdf, poly):\n    \"\"\"Clip point geometry to the polygon extent.\n\n    Clip an input point GeoDataFrame to the polygon extent of the poly\n    parameter. Points that intersect the poly geometry are extracted with\n    associated attributes and returned.\n\n    Parameters\n    ----------\n    gdf : GeoDataFrame, GeoSeries\n        Composed of point geometry that will be clipped to the poly.\n\n    poly : (Multi)Polygon\n        Reference geometry used to spatially clip the data.\n\n    Returns\n    -------\n    GeoDataFrame\n        The returned GeoDataFrame is a subset of gdf that intersects\n        with poly.\n    \"\"\"\n    spatial_index = gdf.sindex\n    bbox = poly.bounds\n    sidx = list(spatial_index.intersection(bbox))\n    gdf_sub = gdf.iloc[sidx]\n\n    return gdf_sub[gdf_sub.geometry.intersects(poly)]\n\n\ndef _clip_line_poly(gdf, poly):\n    \"\"\"Clip line and polygon geometry to the polygon extent.\n\n    Clip an input line or polygon to the polygon extent of the poly\n    parameter. Parts of Lines or Polygons that intersect the poly geometry are\n    extracted with associated attributes and returned.\n\n    Parameters\n    ----------\n    gdf : GeoDataFrame, GeoSeries\n        Line or polygon geometry that is clipped to poly.\n\n    poly : (Multi)Polygon\n        Reference polygon for clipping.\n\n    Returns\n    -------\n    GeoDataFrame\n        The returned GeoDataFrame is a clipped subset of gdf\n        that intersects with poly.\n    \"\"\"\n    spatial_index = gdf.sindex\n\n    # Create a box for the initial intersection\n    bbox = poly.bounds\n    # Get a list of id's for each object that overlaps the bounding box and\n    # subset the data to just those lines\n    sidx = list(spatial_index.intersection(bbox))\n    gdf_sub = gdf.iloc[sidx]\n\n    # Clip the data with the polygon\n    if isinstance(gdf_sub, GeoDataFrame):\n        clipped = gdf_sub.copy()\n        clipped[\"geometry\"] = gdf_sub.intersection(poly)\n\n        # Return the clipped layer with no null geometry values or empty geometries\n        return clipped[~clipped.geometry.is_empty & clipped.geometry.notnull()]\n    else:\n        # GeoSeries\n        clipped = gdf_sub.intersection(poly)\n        return clipped[~clipped.is_empty & clipped.notnull()]\n\n\ndef clip(gdf, mask, keep_geom_type=False):\n    \"\"\"Clip points, lines, or polygon geometries to the mask extent.\n\n    Both layers must be in the same Coordinate Reference System (CRS).\n    The `gdf` will be clipped to the full extent of the clip object.\n\n    If there are multiple polygons in mask, data from `gdf` will be\n    clipped to the total boundary of all polygons in mask.\n\n    Parameters\n    ----------\n    gdf : GeoDataFrame or GeoSeries\n        Vector layer (point, line, polygon) to be clipped to mask.\n    mask : GeoDataFrame, GeoSeries, (Multi)Polygon\n        Polygon vector layer used to clip `gdf`.\n        The mask's geometry is dissolved into one geometric feature\n        and intersected with `gdf`.\n    keep_geom_type : boolean, default False\n        If True, return only geometries of original type in case of intersection\n        resulting in multiple geometry types or GeometryCollections.\n        If False, return all resulting geometries (potentially mixed-types).\n\n    Returns\n    -------\n    GeoDataFrame or GeoSeries\n         Vector data (points, lines, polygons) from `gdf` clipped to\n         polygon boundary from mask.\n\n    Examples\n    --------\n    Clip points (global cities) with a polygon (the South American continent):\n\n    >>> import geopandas\n    >>> path =\n    >>> world = geopandas.read_file(\n    ...     geopandas.datasets.get_path('naturalearth_lowres'))\n    >>> south_america = world[world['continent'] == \"South America\"]\n    >>> capitals = geopandas.read_file(\n    ...     geopandas.datasets.get_path('naturalearth_cities'))\n    >>> capitals.shape\n    (202, 2)\n    >>> sa_capitals = geopandas.clip(capitals, south_america)\n    >>> sa_capitals.shape\n    (12, 2)\n    \"\"\"\n    if not isinstance(gdf, (GeoDataFrame, GeoSeries)):\n        raise TypeError(\n            \"'gdf' should be GeoDataFrame or GeoSeries, got {}\".format(type(gdf))\n        )\n\n    if not isinstance(mask, (GeoDataFrame, GeoSeries, Polygon, MultiPolygon)):\n        raise TypeError(\n            \"'mask' should be GeoDataFrame, GeoSeries or\"\n            \"(Multi)Polygon, got {}\".format(type(gdf))\n        )\n\n    if isinstance(mask, (GeoDataFrame, GeoSeries)):\n        box_mask = mask.total_bounds\n    else:\n        box_mask = mask.bounds\n    box_gdf = gdf.total_bounds\n    if not (\n        ((box_mask[0] <= box_gdf[2]) and (box_gdf[0] <= box_mask[2]))\n        and ((box_mask[1] <= box_gdf[3]) and (box_gdf[1] <= box_mask[3]))\n    ):\n        return GeoDataFrame(columns=gdf.columns, crs=gdf.crs)\n\n    if isinstance(mask, (GeoDataFrame, GeoSeries)):\n        poly = mask.geometry.unary_union\n    else:\n        poly = mask\n\n    geom_types = gdf.geometry.type\n    poly_idx = np.asarray((geom_types == \"Polygon\") | (geom_types == \"MultiPolygon\"))\n    line_idx = np.asarray(\n        (geom_types == \"LineString\")\n        | (geom_types == \"LinearRing\")\n        | (geom_types == \"MultiLineString\")\n    )\n    point_idx = np.asarray((geom_types == \"Point\") | (geom_types == \"MultiPoint\"))\n    geomcoll_idx = np.asarray((geom_types == \"GeometryCollection\"))\n\n    if point_idx.any():\n        point_gdf = _clip_points(gdf[point_idx], poly)\n    else:\n        point_gdf = None\n\n    if poly_idx.any():\n        poly_gdf = _clip_line_poly(gdf[poly_idx], poly)\n    else:\n        poly_gdf = None\n\n    if line_idx.any():\n        line_gdf = _clip_line_poly(gdf[line_idx], poly)\n    else:\n        line_gdf = None\n\n    if geomcoll_idx.any():\n        geomcoll_gdf = _clip_line_poly(gdf[geomcoll_idx], poly)\n    else:\n        geomcoll_gdf = None\n\n    order = pd.Series(range(len(gdf)), index=gdf.index)\n    concat = pd.concat([point_gdf, line_gdf, poly_gdf, geomcoll_gdf])\n\n    if keep_geom_type:\n        geomcoll_concat = (concat.geom_type == \"GeometryCollection\").any()\n        geomcoll_orig = geomcoll_idx.any()\n\n        new_collection = geomcoll_concat and not geomcoll_orig\n\n        if geomcoll_orig:\n            warnings.warn(\n                \"keep_geom_type can not be called on a \"\n                \"GeoDataFrame with GeometryCollection.\"\n            )\n        else:\n            polys = [\"Polygon\", \"MultiPolygon\"]\n            lines = [\"LineString\", \"MultiLineString\", \"LinearRing\"]\n            points = [\"Point\", \"MultiPoint\"]\n\n            # Check that the gdf for multiple geom types (points, lines and/or polys)\n            orig_types_total = sum(\n                [\n                    gdf.geom_type.isin(polys).any(),\n                    gdf.geom_type.isin(lines).any(),\n                    gdf.geom_type.isin(points).any(),\n                ]\n            )\n\n            # Check how many geometry types are in the clipped GeoDataFrame\n            clip_types_total = sum(\n                [\n                    concat.geom_type.isin(polys).any(),\n                    concat.geom_type.isin(lines).any(),\n                    concat.geom_type.isin(points).any(),\n                ]\n            )\n\n            # Check there aren't any new geom types in the clipped GeoDataFrame\n            more_types = orig_types_total < clip_types_total\n\n            if orig_types_total > 1:\n                warnings.warn(\n                    \"keep_geom_type can not be called on a mixed type GeoDataFrame.\"\n                )\n            elif new_collection or more_types:\n                orig_type = gdf.geom_type.iloc[0]\n                if new_collection:\n                    concat = concat.explode()\n                if orig_type in polys:\n                    concat = concat.loc[concat.geom_type.isin(polys)]\n                elif orig_type in lines:\n                    concat = concat.loc[concat.geom_type.isin(lines)]\n\n    # preserve the original order of the input\n    if isinstance(concat, GeoDataFrame):\n        concat[\"_order\"] = order\n        return concat.sort_values(by=\"_order\").drop(columns=\"_order\")\n    else:\n        concat = GeoDataFrame(geometry=concat)\n        concat[\"_order\"] = order\n        return concat.sort_values(by=\"_order\").geometry\n", "path": "geopandas/tools/clip.py"}], "after_files": [{"content": "\"\"\"\ngeopandas.clip\n==============\n\nA module to clip vector data using GeoPandas.\n\n\"\"\"\nimport warnings\n\nimport numpy as np\nimport pandas as pd\n\nfrom shapely.geometry import Polygon, MultiPolygon\n\nfrom geopandas import GeoDataFrame, GeoSeries\n\n\ndef _clip_points(gdf, poly):\n    \"\"\"Clip point geometry to the polygon extent.\n\n    Clip an input point GeoDataFrame to the polygon extent of the poly\n    parameter. Points that intersect the poly geometry are extracted with\n    associated attributes and returned.\n\n    Parameters\n    ----------\n    gdf : GeoDataFrame, GeoSeries\n        Composed of point geometry that will be clipped to the poly.\n\n    poly : (Multi)Polygon\n        Reference geometry used to spatially clip the data.\n\n    Returns\n    -------\n    GeoDataFrame\n        The returned GeoDataFrame is a subset of gdf that intersects\n        with poly.\n    \"\"\"\n    spatial_index = gdf.sindex\n    bbox = poly.bounds\n    sidx = list(spatial_index.intersection(bbox))\n    gdf_sub = gdf.iloc[sidx]\n\n    return gdf_sub[gdf_sub.geometry.intersects(poly)]\n\n\ndef _clip_line_poly(gdf, poly):\n    \"\"\"Clip line and polygon geometry to the polygon extent.\n\n    Clip an input line or polygon to the polygon extent of the poly\n    parameter. Parts of Lines or Polygons that intersect the poly geometry are\n    extracted with associated attributes and returned.\n\n    Parameters\n    ----------\n    gdf : GeoDataFrame, GeoSeries\n        Line or polygon geometry that is clipped to poly.\n\n    poly : (Multi)Polygon\n        Reference polygon for clipping.\n\n    Returns\n    -------\n    GeoDataFrame\n        The returned GeoDataFrame is a clipped subset of gdf\n        that intersects with poly.\n    \"\"\"\n    spatial_index = gdf.sindex\n\n    # Create a box for the initial intersection\n    bbox = poly.bounds\n    # Get a list of id's for each object that overlaps the bounding box and\n    # subset the data to just those lines\n    sidx = list(spatial_index.intersection(bbox))\n    gdf_sub = gdf.iloc[sidx]\n\n    # Clip the data with the polygon\n    if isinstance(gdf_sub, GeoDataFrame):\n        clipped = gdf_sub.copy()\n        clipped[\"geometry\"] = gdf_sub.intersection(poly)\n\n        # Return the clipped layer with no null geometry values or empty geometries\n        return clipped[~clipped.geometry.is_empty & clipped.geometry.notnull()]\n    else:\n        # GeoSeries\n        clipped = gdf_sub.intersection(poly)\n        return clipped[~clipped.is_empty & clipped.notnull()]\n\n\ndef clip(gdf, mask, keep_geom_type=False):\n    \"\"\"Clip points, lines, or polygon geometries to the mask extent.\n\n    Both layers must be in the same Coordinate Reference System (CRS).\n    The `gdf` will be clipped to the full extent of the clip object.\n\n    If there are multiple polygons in mask, data from `gdf` will be\n    clipped to the total boundary of all polygons in mask.\n\n    Parameters\n    ----------\n    gdf : GeoDataFrame or GeoSeries\n        Vector layer (point, line, polygon) to be clipped to mask.\n    mask : GeoDataFrame, GeoSeries, (Multi)Polygon\n        Polygon vector layer used to clip `gdf`.\n        The mask's geometry is dissolved into one geometric feature\n        and intersected with `gdf`.\n    keep_geom_type : boolean, default False\n        If True, return only geometries of original type in case of intersection\n        resulting in multiple geometry types or GeometryCollections.\n        If False, return all resulting geometries (potentially mixed-types).\n\n    Returns\n    -------\n    GeoDataFrame or GeoSeries\n         Vector data (points, lines, polygons) from `gdf` clipped to\n         polygon boundary from mask.\n\n    Examples\n    --------\n    Clip points (global cities) with a polygon (the South American continent):\n\n    >>> import geopandas\n    >>> path =\n    >>> world = geopandas.read_file(\n    ...     geopandas.datasets.get_path('naturalearth_lowres'))\n    >>> south_america = world[world['continent'] == \"South America\"]\n    >>> capitals = geopandas.read_file(\n    ...     geopandas.datasets.get_path('naturalearth_cities'))\n    >>> capitals.shape\n    (202, 2)\n    >>> sa_capitals = geopandas.clip(capitals, south_america)\n    >>> sa_capitals.shape\n    (12, 2)\n    \"\"\"\n    if not isinstance(gdf, (GeoDataFrame, GeoSeries)):\n        raise TypeError(\n            \"'gdf' should be GeoDataFrame or GeoSeries, got {}\".format(type(gdf))\n        )\n\n    if not isinstance(mask, (GeoDataFrame, GeoSeries, Polygon, MultiPolygon)):\n        raise TypeError(\n            \"'mask' should be GeoDataFrame, GeoSeries or\"\n            \"(Multi)Polygon, got {}\".format(type(gdf))\n        )\n\n    if isinstance(mask, (GeoDataFrame, GeoSeries)):\n        box_mask = mask.total_bounds\n    else:\n        box_mask = mask.bounds\n    box_gdf = gdf.total_bounds\n    if not (\n        ((box_mask[0] <= box_gdf[2]) and (box_gdf[0] <= box_mask[2]))\n        and ((box_mask[1] <= box_gdf[3]) and (box_gdf[1] <= box_mask[3]))\n    ):\n        return (\n            GeoDataFrame(columns=gdf.columns, crs=gdf.crs)\n            if isinstance(gdf, GeoDataFrame)\n            else GeoSeries(crs=gdf.crs)\n        )\n\n    if isinstance(mask, (GeoDataFrame, GeoSeries)):\n        poly = mask.geometry.unary_union\n    else:\n        poly = mask\n\n    geom_types = gdf.geometry.type\n    poly_idx = np.asarray((geom_types == \"Polygon\") | (geom_types == \"MultiPolygon\"))\n    line_idx = np.asarray(\n        (geom_types == \"LineString\")\n        | (geom_types == \"LinearRing\")\n        | (geom_types == \"MultiLineString\")\n    )\n    point_idx = np.asarray((geom_types == \"Point\") | (geom_types == \"MultiPoint\"))\n    geomcoll_idx = np.asarray((geom_types == \"GeometryCollection\"))\n\n    if point_idx.any():\n        point_gdf = _clip_points(gdf[point_idx], poly)\n    else:\n        point_gdf = None\n\n    if poly_idx.any():\n        poly_gdf = _clip_line_poly(gdf[poly_idx], poly)\n    else:\n        poly_gdf = None\n\n    if line_idx.any():\n        line_gdf = _clip_line_poly(gdf[line_idx], poly)\n    else:\n        line_gdf = None\n\n    if geomcoll_idx.any():\n        geomcoll_gdf = _clip_line_poly(gdf[geomcoll_idx], poly)\n    else:\n        geomcoll_gdf = None\n\n    order = pd.Series(range(len(gdf)), index=gdf.index)\n    concat = pd.concat([point_gdf, line_gdf, poly_gdf, geomcoll_gdf])\n\n    if keep_geom_type:\n        geomcoll_concat = (concat.geom_type == \"GeometryCollection\").any()\n        geomcoll_orig = geomcoll_idx.any()\n\n        new_collection = geomcoll_concat and not geomcoll_orig\n\n        if geomcoll_orig:\n            warnings.warn(\n                \"keep_geom_type can not be called on a \"\n                \"GeoDataFrame with GeometryCollection.\"\n            )\n        else:\n            polys = [\"Polygon\", \"MultiPolygon\"]\n            lines = [\"LineString\", \"MultiLineString\", \"LinearRing\"]\n            points = [\"Point\", \"MultiPoint\"]\n\n            # Check that the gdf for multiple geom types (points, lines and/or polys)\n            orig_types_total = sum(\n                [\n                    gdf.geom_type.isin(polys).any(),\n                    gdf.geom_type.isin(lines).any(),\n                    gdf.geom_type.isin(points).any(),\n                ]\n            )\n\n            # Check how many geometry types are in the clipped GeoDataFrame\n            clip_types_total = sum(\n                [\n                    concat.geom_type.isin(polys).any(),\n                    concat.geom_type.isin(lines).any(),\n                    concat.geom_type.isin(points).any(),\n                ]\n            )\n\n            # Check there aren't any new geom types in the clipped GeoDataFrame\n            more_types = orig_types_total < clip_types_total\n\n            if orig_types_total > 1:\n                warnings.warn(\n                    \"keep_geom_type can not be called on a mixed type GeoDataFrame.\"\n                )\n            elif new_collection or more_types:\n                orig_type = gdf.geom_type.iloc[0]\n                if new_collection:\n                    concat = concat.explode()\n                if orig_type in polys:\n                    concat = concat.loc[concat.geom_type.isin(polys)]\n                elif orig_type in lines:\n                    concat = concat.loc[concat.geom_type.isin(lines)]\n\n    # preserve the original order of the input\n    if isinstance(concat, GeoDataFrame):\n        concat[\"_order\"] = order\n        return concat.sort_values(by=\"_order\").drop(columns=\"_order\")\n    else:\n        concat = GeoDataFrame(geometry=concat)\n        concat[\"_order\"] = order\n        return concat.sort_values(by=\"_order\").geometry\n", "path": "geopandas/tools/clip.py"}]}