We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Spatial join error after using `explode()`
@jorisvandenbossche There is a specific situation that produces an error with `.sjoin()`. 

I receive a following error: 
```
  File "C:\Hyapp\Anaconda3\lib\site-packages\pandas\core\indexes\base.py", line 1330, in set_names
    raise TypeError("Must pass list-like as `names`.")

TypeError: Must pass list-like as `names`.
```

I was able to narrow this down, and the error only happens after using `df.explode()` function to turn MultiPolygons into multiple Polygons. 

Here is an example to produce the error using some data from OSM: 
```
# Test to produce an error
import osmnx as ox
import geopandas as gpd
buildings = ox.footprints_from_place("Punavuori, Finland")
buildings = buildings.explode()
landuse = ox.footprints_from_place("Punavuori, Finland", 'landuse')
join = gpd.sjoin(buildings, landuse)
```

The solution for fixing this was to `reset_index()` after using the `.explode()`. I am not sure if this is a bug or expected behavior, but wanted to report my findings anyway. 🙂 


--- 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/sjoin.py`
Content:
```
1 from warnings import warn
2 
3 import numpy as np
4 import pandas as pd
5 
6 from shapely import prepared
7 
8 from geopandas import GeoDataFrame
9 
10 
11 def sjoin(
12     left_df, right_df, how="inner", op="intersects", lsuffix="left", rsuffix="right"
13 ):
14     """Spatial join of two GeoDataFrames.
15 
16     Parameters
17     ----------
18     left_df, right_df : GeoDataFrames
19     how : string, default 'inner'
20         The type of join:
21 
22         * 'left': use keys from left_df; retain only left_df geometry column
23         * 'right': use keys from right_df; retain only right_df geometry column
24         * 'inner': use intersection of keys from both dfs; retain only
25           left_df geometry column
26     op : string, default 'intersection'
27         Binary predicate, one of {'intersects', 'contains', 'within'}.
28         See http://shapely.readthedocs.io/en/latest/manual.html#binary-predicates.
29     lsuffix : string, default 'left'
30         Suffix to apply to overlapping column names (left GeoDataFrame).
31     rsuffix : string, default 'right'
32         Suffix to apply to overlapping column names (right GeoDataFrame).
33 
34     """
35     if not isinstance(left_df, GeoDataFrame):
36         raise ValueError(
37             "'left_df' should be GeoDataFrame, got {}".format(type(left_df))
38         )
39 
40     if not isinstance(right_df, GeoDataFrame):
41         raise ValueError(
42             "'right_df' should be GeoDataFrame, got {}".format(type(right_df))
43         )
44 
45     allowed_hows = ["left", "right", "inner"]
46     if how not in allowed_hows:
47         raise ValueError(
48             '`how` was "%s" but is expected to be in %s' % (how, allowed_hows)
49         )
50 
51     allowed_ops = ["contains", "within", "intersects"]
52     if op not in allowed_ops:
53         raise ValueError(
54             '`op` was "%s" but is expected to be in %s' % (op, allowed_ops)
55         )
56 
57     if left_df.crs != right_df.crs:
58         warn(
59             (
60                 "CRS of frames being joined does not match!"
61                 "(%s != %s)" % (left_df.crs, right_df.crs)
62             )
63         )
64 
65     index_left = "index_%s" % lsuffix
66     index_right = "index_%s" % rsuffix
67 
68     # due to GH 352
69     if any(left_df.columns.isin([index_left, index_right])) or any(
70         right_df.columns.isin([index_left, index_right])
71     ):
72         raise ValueError(
73             "'{0}' and '{1}' cannot be names in the frames being"
74             " joined".format(index_left, index_right)
75         )
76 
77     # Attempt to re-use spatial indexes, otherwise generate the spatial index
78     # for the longer dataframe
79     if right_df._sindex_generated or (
80         not left_df._sindex_generated and right_df.shape[0] > left_df.shape[0]
81     ):
82         tree_idx = right_df.sindex
83         tree_idx_right = True
84     else:
85         tree_idx = left_df.sindex
86         tree_idx_right = False
87 
88     # the rtree spatial index only allows limited (numeric) index types, but an
89     # index in geopandas may be any arbitrary dtype. so reset both indices now
90     # and store references to the original indices, to be reaffixed later.
91     # GH 352
92     left_df = left_df.copy(deep=True)
93     left_index_name = left_df.index.name
94     left_df.index = left_df.index.rename(index_left)
95     left_df = left_df.reset_index()
96     right_df = right_df.copy(deep=True)
97     right_index_name = right_df.index.name
98     right_df.index = right_df.index.rename(index_right)
99     right_df = right_df.reset_index()
100 
101     if op == "within":
102         # within implemented as the inverse of contains; swap names
103         left_df, right_df = right_df, left_df
104         tree_idx_right = not tree_idx_right
105 
106     r_idx = np.empty((0, 0))
107     l_idx = np.empty((0, 0))
108     # get rtree spatial index
109     if tree_idx_right:
110         idxmatch = left_df.geometry.apply(lambda x: x.bounds).apply(
111             lambda x: list(tree_idx.intersection(x)) if not x == () else []
112         )
113         idxmatch = idxmatch[idxmatch.apply(len) > 0]
114         # indexes of overlapping boundaries
115         if idxmatch.shape[0] > 0:
116             r_idx = np.concatenate(idxmatch.values)
117             l_idx = np.concatenate([[i] * len(v) for i, v in idxmatch.iteritems()])
118     else:
119         # tree_idx_df == 'left'
120         idxmatch = right_df.geometry.apply(lambda x: x.bounds).apply(
121             lambda x: list(tree_idx.intersection(x)) if not x == () else []
122         )
123         idxmatch = idxmatch[idxmatch.apply(len) > 0]
124         if idxmatch.shape[0] > 0:
125             # indexes of overlapping boundaries
126             l_idx = np.concatenate(idxmatch.values)
127             r_idx = np.concatenate([[i] * len(v) for i, v in idxmatch.iteritems()])
128 
129     if len(r_idx) > 0 and len(l_idx) > 0:
130         # Vectorize predicate operations
131         def find_intersects(a1, a2):
132             return a1.intersects(a2)
133 
134         def find_contains(a1, a2):
135             return a1.contains(a2)
136 
137         predicate_d = {
138             "intersects": find_intersects,
139             "contains": find_contains,
140             "within": find_contains,
141         }
142 
143         check_predicates = np.vectorize(predicate_d[op])
144 
145         result = pd.DataFrame(
146             np.column_stack(
147                 [
148                     l_idx,
149                     r_idx,
150                     check_predicates(
151                         left_df.geometry.apply(lambda x: prepared.prep(x))[l_idx],
152                         right_df[right_df.geometry.name][r_idx],
153                     ),
154                 ]
155             )
156         )
157 
158         result.columns = ["_key_left", "_key_right", "match_bool"]
159         result = pd.DataFrame(result[result["match_bool"] == 1]).drop(
160             "match_bool", axis=1
161         )
162 
163     else:
164         # when output from the join has no overlapping geometries
165         result = pd.DataFrame(columns=["_key_left", "_key_right"], dtype=float)
166 
167     if op == "within":
168         # within implemented as the inverse of contains; swap names
169         left_df, right_df = right_df, left_df
170         result = result.rename(
171             columns={"_key_left": "_key_right", "_key_right": "_key_left"}
172         )
173 
174     if how == "inner":
175         result = result.set_index("_key_left")
176         joined = (
177             left_df.merge(result, left_index=True, right_index=True)
178             .merge(
179                 right_df.drop(right_df.geometry.name, axis=1),
180                 left_on="_key_right",
181                 right_index=True,
182                 suffixes=("_%s" % lsuffix, "_%s" % rsuffix),
183             )
184             .set_index(index_left)
185             .drop(["_key_right"], axis=1)
186         )
187         joined.index.name = left_index_name
188 
189     elif how == "left":
190         result = result.set_index("_key_left")
191         joined = (
192             left_df.merge(result, left_index=True, right_index=True, how="left")
193             .merge(
194                 right_df.drop(right_df.geometry.name, axis=1),
195                 how="left",
196                 left_on="_key_right",
197                 right_index=True,
198                 suffixes=("_%s" % lsuffix, "_%s" % rsuffix),
199             )
200             .set_index(index_left)
201             .drop(["_key_right"], axis=1)
202         )
203         joined.index.name = left_index_name
204 
205     else:  # how == 'right':
206         joined = (
207             left_df.drop(left_df.geometry.name, axis=1)
208             .merge(
209                 result.merge(
210                     right_df, left_on="_key_right", right_index=True, how="right"
211                 ),
212                 left_index=True,
213                 right_on="_key_left",
214                 how="right",
215             )
216             .set_index(index_right)
217             .drop(["_key_left", "_key_right"], axis=1)
218         )
219         joined.index.name = right_index_name
220 
221     return joined
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/tools/sjoin.py b/geopandas/tools/sjoin.py
--- a/geopandas/tools/sjoin.py
+++ b/geopandas/tools/sjoin.py
@@ -90,12 +90,27 @@
     # and store references to the original indices, to be reaffixed later.
     # GH 352
     left_df = left_df.copy(deep=True)
-    left_index_name = left_df.index.name
-    left_df.index = left_df.index.rename(index_left)
+    try:
+        left_index_name = left_df.index.name
+        left_df.index = left_df.index.rename(index_left)
+    except TypeError:
+        index_left = [
+            "index_%s" % lsuffix + str(l) for l, ix in enumerate(left_df.index.names)
+        ]
+        left_index_name = left_df.index.names
+        left_df.index = left_df.index.rename(index_left)
     left_df = left_df.reset_index()
+
     right_df = right_df.copy(deep=True)
-    right_index_name = right_df.index.name
-    right_df.index = right_df.index.rename(index_right)
+    try:
+        right_index_name = right_df.index.name
+        right_df.index = right_df.index.rename(index_right)
+    except TypeError:
+        index_right = [
+            "index_%s" % rsuffix + str(l) for l, ix in enumerate(right_df.index.names)
+        ]
+        right_index_name = right_df.index.names
+        right_df.index = right_df.index.rename(index_right)
     right_df = right_df.reset_index()
 
     if op == "within":
@@ -184,7 +199,10 @@
             .set_index(index_left)
             .drop(["_key_right"], axis=1)
         )
-        joined.index.name = left_index_name
+        if isinstance(index_left, list):
+            joined.index.names = left_index_name
+        else:
+            joined.index.name = left_index_name
 
     elif how == "left":
         result = result.set_index("_key_left")
@@ -200,7 +218,10 @@
             .set_index(index_left)
             .drop(["_key_right"], axis=1)
         )
-        joined.index.name = left_index_name
+        if isinstance(index_left, list):
+            joined.index.names = left_index_name
+        else:
+            joined.index.name = left_index_name
 
     else:  # how == 'right':
         joined = (
@@ -216,6 +237,9 @@
             .set_index(index_right)
             .drop(["_key_left", "_key_right"], axis=1)
         )
-        joined.index.name = right_index_name
+        if isinstance(index_right, list):
+            joined.index.names = right_index_name
+        else:
+            joined.index.name = right_index_name
 
     return joined

{"golden_diff": "diff --git a/geopandas/tools/sjoin.py b/geopandas/tools/sjoin.py\n--- a/geopandas/tools/sjoin.py\n+++ b/geopandas/tools/sjoin.py\n@@ -90,12 +90,27 @@\n     # and store references to the original indices, to be reaffixed later.\n     # GH 352\n     left_df = left_df.copy(deep=True)\n-    left_index_name = left_df.index.name\n-    left_df.index = left_df.index.rename(index_left)\n+    try:\n+        left_index_name = left_df.index.name\n+        left_df.index = left_df.index.rename(index_left)\n+    except TypeError:\n+        index_left = [\n+            \"index_%s\" % lsuffix + str(l) for l, ix in enumerate(left_df.index.names)\n+        ]\n+        left_index_name = left_df.index.names\n+        left_df.index = left_df.index.rename(index_left)\n     left_df = left_df.reset_index()\n+\n     right_df = right_df.copy(deep=True)\n-    right_index_name = right_df.index.name\n-    right_df.index = right_df.index.rename(index_right)\n+    try:\n+        right_index_name = right_df.index.name\n+        right_df.index = right_df.index.rename(index_right)\n+    except TypeError:\n+        index_right = [\n+            \"index_%s\" % rsuffix + str(l) for l, ix in enumerate(right_df.index.names)\n+        ]\n+        right_index_name = right_df.index.names\n+        right_df.index = right_df.index.rename(index_right)\n     right_df = right_df.reset_index()\n \n     if op == \"within\":\n@@ -184,7 +199,10 @@\n             .set_index(index_left)\n             .drop([\"_key_right\"], axis=1)\n         )\n-        joined.index.name = left_index_name\n+        if isinstance(index_left, list):\n+            joined.index.names = left_index_name\n+        else:\n+            joined.index.name = left_index_name\n \n     elif how == \"left\":\n         result = result.set_index(\"_key_left\")\n@@ -200,7 +218,10 @@\n             .set_index(index_left)\n             .drop([\"_key_right\"], axis=1)\n         )\n-        joined.index.name = left_index_name\n+        if isinstance(index_left, list):\n+            joined.index.names = left_index_name\n+        else:\n+            joined.index.name = left_index_name\n \n     else:  # how == 'right':\n         joined = (\n@@ -216,6 +237,9 @@\n             .set_index(index_right)\n             .drop([\"_key_left\", \"_key_right\"], axis=1)\n         )\n-        joined.index.name = right_index_name\n+        if isinstance(index_right, list):\n+            joined.index.names = right_index_name\n+        else:\n+            joined.index.name = right_index_name\n \n     return joined\n", "issue": "Spatial join error after using `explode()`\n@jorisvandenbossche There is a specific situation that produces an error with `.sjoin()`. \r\n\r\nI receive a following error: \r\n```\r\n  File \"C:\\Hyapp\\Anaconda3\\lib\\site-packages\\pandas\\core\\indexes\\base.py\", line 1330, in set_names\r\n    raise TypeError(\"Must pass list-like as `names`.\")\r\n\r\nTypeError: Must pass list-like as `names`.\r\n```\r\n\r\nI was able to narrow this down, and the error only happens after using `df.explode()` function to turn MultiPolygons into multiple Polygons. \r\n\r\nHere is an example to produce the error using some data from OSM: \r\n```\r\n# Test to produce an error\r\nimport osmnx as ox\r\nimport geopandas as gpd\r\nbuildings = ox.footprints_from_place(\"Punavuori, Finland\")\r\nbuildings = buildings.explode()\r\nlanduse = ox.footprints_from_place(\"Punavuori, Finland\", 'landuse')\r\njoin = gpd.sjoin(buildings, landuse)\r\n```\r\n\r\nThe solution for fixing this was to `reset_index()` after using the `.explode()`. I am not sure if this is a bug or expected behavior, but wanted to report my findings anyway. \ud83d\ude42 \r\n\n", "before_files": [{"content": "from warnings import warn\n\nimport numpy as np\nimport pandas as pd\n\nfrom shapely import prepared\n\nfrom geopandas import GeoDataFrame\n\n\ndef sjoin(\n    left_df, right_df, how=\"inner\", op=\"intersects\", lsuffix=\"left\", rsuffix=\"right\"\n):\n    \"\"\"Spatial join of two GeoDataFrames.\n\n    Parameters\n    ----------\n    left_df, right_df : GeoDataFrames\n    how : string, default 'inner'\n        The type of join:\n\n        * 'left': use keys from left_df; retain only left_df geometry column\n        * 'right': use keys from right_df; retain only right_df geometry column\n        * 'inner': use intersection of keys from both dfs; retain only\n          left_df geometry column\n    op : string, default 'intersection'\n        Binary predicate, one of {'intersects', 'contains', 'within'}.\n        See http://shapely.readthedocs.io/en/latest/manual.html#binary-predicates.\n    lsuffix : string, default 'left'\n        Suffix to apply to overlapping column names (left GeoDataFrame).\n    rsuffix : string, default 'right'\n        Suffix to apply to overlapping column names (right GeoDataFrame).\n\n    \"\"\"\n    if not isinstance(left_df, GeoDataFrame):\n        raise ValueError(\n            \"'left_df' should be GeoDataFrame, got {}\".format(type(left_df))\n        )\n\n    if not isinstance(right_df, GeoDataFrame):\n        raise ValueError(\n            \"'right_df' should be GeoDataFrame, got {}\".format(type(right_df))\n        )\n\n    allowed_hows = [\"left\", \"right\", \"inner\"]\n    if how not in allowed_hows:\n        raise ValueError(\n            '`how` was \"%s\" but is expected to be in %s' % (how, allowed_hows)\n        )\n\n    allowed_ops = [\"contains\", \"within\", \"intersects\"]\n    if op not in allowed_ops:\n        raise ValueError(\n            '`op` was \"%s\" but is expected to be in %s' % (op, allowed_ops)\n        )\n\n    if left_df.crs != right_df.crs:\n        warn(\n            (\n                \"CRS of frames being joined does not match!\"\n                \"(%s != %s)\" % (left_df.crs, right_df.crs)\n            )\n        )\n\n    index_left = \"index_%s\" % lsuffix\n    index_right = \"index_%s\" % rsuffix\n\n    # due to GH 352\n    if any(left_df.columns.isin([index_left, index_right])) or any(\n        right_df.columns.isin([index_left, index_right])\n    ):\n        raise ValueError(\n            \"'{0}' and '{1}' cannot be names in the frames being\"\n            \" joined\".format(index_left, index_right)\n        )\n\n    # Attempt to re-use spatial indexes, otherwise generate the spatial index\n    # for the longer dataframe\n    if right_df._sindex_generated or (\n        not left_df._sindex_generated and right_df.shape[0] > left_df.shape[0]\n    ):\n        tree_idx = right_df.sindex\n        tree_idx_right = True\n    else:\n        tree_idx = left_df.sindex\n        tree_idx_right = False\n\n    # the rtree spatial index only allows limited (numeric) index types, but an\n    # index in geopandas may be any arbitrary dtype. so reset both indices now\n    # and store references to the original indices, to be reaffixed later.\n    # GH 352\n    left_df = left_df.copy(deep=True)\n    left_index_name = left_df.index.name\n    left_df.index = left_df.index.rename(index_left)\n    left_df = left_df.reset_index()\n    right_df = right_df.copy(deep=True)\n    right_index_name = right_df.index.name\n    right_df.index = right_df.index.rename(index_right)\n    right_df = right_df.reset_index()\n\n    if op == \"within\":\n        # within implemented as the inverse of contains; swap names\n        left_df, right_df = right_df, left_df\n        tree_idx_right = not tree_idx_right\n\n    r_idx = np.empty((0, 0))\n    l_idx = np.empty((0, 0))\n    # get rtree spatial index\n    if tree_idx_right:\n        idxmatch = left_df.geometry.apply(lambda x: x.bounds).apply(\n            lambda x: list(tree_idx.intersection(x)) if not x == () else []\n        )\n        idxmatch = idxmatch[idxmatch.apply(len) > 0]\n        # indexes of overlapping boundaries\n        if idxmatch.shape[0] > 0:\n            r_idx = np.concatenate(idxmatch.values)\n            l_idx = np.concatenate([[i] * len(v) for i, v in idxmatch.iteritems()])\n    else:\n        # tree_idx_df == 'left'\n        idxmatch = right_df.geometry.apply(lambda x: x.bounds).apply(\n            lambda x: list(tree_idx.intersection(x)) if not x == () else []\n        )\n        idxmatch = idxmatch[idxmatch.apply(len) > 0]\n        if idxmatch.shape[0] > 0:\n            # indexes of overlapping boundaries\n            l_idx = np.concatenate(idxmatch.values)\n            r_idx = np.concatenate([[i] * len(v) for i, v in idxmatch.iteritems()])\n\n    if len(r_idx) > 0 and len(l_idx) > 0:\n        # Vectorize predicate operations\n        def find_intersects(a1, a2):\n            return a1.intersects(a2)\n\n        def find_contains(a1, a2):\n            return a1.contains(a2)\n\n        predicate_d = {\n            \"intersects\": find_intersects,\n            \"contains\": find_contains,\n            \"within\": find_contains,\n        }\n\n        check_predicates = np.vectorize(predicate_d[op])\n\n        result = pd.DataFrame(\n            np.column_stack(\n                [\n                    l_idx,\n                    r_idx,\n                    check_predicates(\n                        left_df.geometry.apply(lambda x: prepared.prep(x))[l_idx],\n                        right_df[right_df.geometry.name][r_idx],\n                    ),\n                ]\n            )\n        )\n\n        result.columns = [\"_key_left\", \"_key_right\", \"match_bool\"]\n        result = pd.DataFrame(result[result[\"match_bool\"] == 1]).drop(\n            \"match_bool\", axis=1\n        )\n\n    else:\n        # when output from the join has no overlapping geometries\n        result = pd.DataFrame(columns=[\"_key_left\", \"_key_right\"], dtype=float)\n\n    if op == \"within\":\n        # within implemented as the inverse of contains; swap names\n        left_df, right_df = right_df, left_df\n        result = result.rename(\n            columns={\"_key_left\": \"_key_right\", \"_key_right\": \"_key_left\"}\n        )\n\n    if how == \"inner\":\n        result = result.set_index(\"_key_left\")\n        joined = (\n            left_df.merge(result, left_index=True, right_index=True)\n            .merge(\n                right_df.drop(right_df.geometry.name, axis=1),\n                left_on=\"_key_right\",\n                right_index=True,\n                suffixes=(\"_%s\" % lsuffix, \"_%s\" % rsuffix),\n            )\n            .set_index(index_left)\n            .drop([\"_key_right\"], axis=1)\n        )\n        joined.index.name = left_index_name\n\n    elif how == \"left\":\n        result = result.set_index(\"_key_left\")\n        joined = (\n            left_df.merge(result, left_index=True, right_index=True, how=\"left\")\n            .merge(\n                right_df.drop(right_df.geometry.name, axis=1),\n                how=\"left\",\n                left_on=\"_key_right\",\n                right_index=True,\n                suffixes=(\"_%s\" % lsuffix, \"_%s\" % rsuffix),\n            )\n            .set_index(index_left)\n            .drop([\"_key_right\"], axis=1)\n        )\n        joined.index.name = left_index_name\n\n    else:  # how == 'right':\n        joined = (\n            left_df.drop(left_df.geometry.name, axis=1)\n            .merge(\n                result.merge(\n                    right_df, left_on=\"_key_right\", right_index=True, how=\"right\"\n                ),\n                left_index=True,\n                right_on=\"_key_left\",\n                how=\"right\",\n            )\n            .set_index(index_right)\n            .drop([\"_key_left\", \"_key_right\"], axis=1)\n        )\n        joined.index.name = right_index_name\n\n    return joined\n", "path": "geopandas/tools/sjoin.py"}], "after_files": [{"content": "from warnings import warn\n\nimport numpy as np\nimport pandas as pd\n\nfrom shapely import prepared\n\nfrom geopandas import GeoDataFrame\n\n\ndef sjoin(\n    left_df, right_df, how=\"inner\", op=\"intersects\", lsuffix=\"left\", rsuffix=\"right\"\n):\n    \"\"\"Spatial join of two GeoDataFrames.\n\n    Parameters\n    ----------\n    left_df, right_df : GeoDataFrames\n    how : string, default 'inner'\n        The type of join:\n\n        * 'left': use keys from left_df; retain only left_df geometry column\n        * 'right': use keys from right_df; retain only right_df geometry column\n        * 'inner': use intersection of keys from both dfs; retain only\n          left_df geometry column\n    op : string, default 'intersection'\n        Binary predicate, one of {'intersects', 'contains', 'within'}.\n        See http://shapely.readthedocs.io/en/latest/manual.html#binary-predicates.\n    lsuffix : string, default 'left'\n        Suffix to apply to overlapping column names (left GeoDataFrame).\n    rsuffix : string, default 'right'\n        Suffix to apply to overlapping column names (right GeoDataFrame).\n\n    \"\"\"\n    if not isinstance(left_df, GeoDataFrame):\n        raise ValueError(\n            \"'left_df' should be GeoDataFrame, got {}\".format(type(left_df))\n        )\n\n    if not isinstance(right_df, GeoDataFrame):\n        raise ValueError(\n            \"'right_df' should be GeoDataFrame, got {}\".format(type(right_df))\n        )\n\n    allowed_hows = [\"left\", \"right\", \"inner\"]\n    if how not in allowed_hows:\n        raise ValueError(\n            '`how` was \"%s\" but is expected to be in %s' % (how, allowed_hows)\n        )\n\n    allowed_ops = [\"contains\", \"within\", \"intersects\"]\n    if op not in allowed_ops:\n        raise ValueError(\n            '`op` was \"%s\" but is expected to be in %s' % (op, allowed_ops)\n        )\n\n    if left_df.crs != right_df.crs:\n        warn(\n            (\n                \"CRS of frames being joined does not match!\"\n                \"(%s != %s)\" % (left_df.crs, right_df.crs)\n            )\n        )\n\n    index_left = \"index_%s\" % lsuffix\n    index_right = \"index_%s\" % rsuffix\n\n    # due to GH 352\n    if any(left_df.columns.isin([index_left, index_right])) or any(\n        right_df.columns.isin([index_left, index_right])\n    ):\n        raise ValueError(\n            \"'{0}' and '{1}' cannot be names in the frames being\"\n            \" joined\".format(index_left, index_right)\n        )\n\n    # Attempt to re-use spatial indexes, otherwise generate the spatial index\n    # for the longer dataframe\n    if right_df._sindex_generated or (\n        not left_df._sindex_generated and right_df.shape[0] > left_df.shape[0]\n    ):\n        tree_idx = right_df.sindex\n        tree_idx_right = True\n    else:\n        tree_idx = left_df.sindex\n        tree_idx_right = False\n\n    # the rtree spatial index only allows limited (numeric) index types, but an\n    # index in geopandas may be any arbitrary dtype. so reset both indices now\n    # and store references to the original indices, to be reaffixed later.\n    # GH 352\n    left_df = left_df.copy(deep=True)\n    try:\n        left_index_name = left_df.index.name\n        left_df.index = left_df.index.rename(index_left)\n    except TypeError:\n        index_left = [\n            \"index_%s\" % lsuffix + str(l) for l, ix in enumerate(left_df.index.names)\n        ]\n        left_index_name = left_df.index.names\n        left_df.index = left_df.index.rename(index_left)\n    left_df = left_df.reset_index()\n\n    right_df = right_df.copy(deep=True)\n    try:\n        right_index_name = right_df.index.name\n        right_df.index = right_df.index.rename(index_right)\n    except TypeError:\n        index_right = [\n            \"index_%s\" % rsuffix + str(l) for l, ix in enumerate(right_df.index.names)\n        ]\n        right_index_name = right_df.index.names\n        right_df.index = right_df.index.rename(index_right)\n    right_df = right_df.reset_index()\n\n    if op == \"within\":\n        # within implemented as the inverse of contains; swap names\n        left_df, right_df = right_df, left_df\n        tree_idx_right = not tree_idx_right\n\n    r_idx = np.empty((0, 0))\n    l_idx = np.empty((0, 0))\n    # get rtree spatial index\n    if tree_idx_right:\n        idxmatch = left_df.geometry.apply(lambda x: x.bounds).apply(\n            lambda x: list(tree_idx.intersection(x)) if not x == () else []\n        )\n        idxmatch = idxmatch[idxmatch.apply(len) > 0]\n        # indexes of overlapping boundaries\n        if idxmatch.shape[0] > 0:\n            r_idx = np.concatenate(idxmatch.values)\n            l_idx = np.concatenate([[i] * len(v) for i, v in idxmatch.iteritems()])\n    else:\n        # tree_idx_df == 'left'\n        idxmatch = right_df.geometry.apply(lambda x: x.bounds).apply(\n            lambda x: list(tree_idx.intersection(x)) if not x == () else []\n        )\n        idxmatch = idxmatch[idxmatch.apply(len) > 0]\n        if idxmatch.shape[0] > 0:\n            # indexes of overlapping boundaries\n            l_idx = np.concatenate(idxmatch.values)\n            r_idx = np.concatenate([[i] * len(v) for i, v in idxmatch.iteritems()])\n\n    if len(r_idx) > 0 and len(l_idx) > 0:\n        # Vectorize predicate operations\n        def find_intersects(a1, a2):\n            return a1.intersects(a2)\n\n        def find_contains(a1, a2):\n            return a1.contains(a2)\n\n        predicate_d = {\n            \"intersects\": find_intersects,\n            \"contains\": find_contains,\n            \"within\": find_contains,\n        }\n\n        check_predicates = np.vectorize(predicate_d[op])\n\n        result = pd.DataFrame(\n            np.column_stack(\n                [\n                    l_idx,\n                    r_idx,\n                    check_predicates(\n                        left_df.geometry.apply(lambda x: prepared.prep(x))[l_idx],\n                        right_df[right_df.geometry.name][r_idx],\n                    ),\n                ]\n            )\n        )\n\n        result.columns = [\"_key_left\", \"_key_right\", \"match_bool\"]\n        result = pd.DataFrame(result[result[\"match_bool\"] == 1]).drop(\n            \"match_bool\", axis=1\n        )\n\n    else:\n        # when output from the join has no overlapping geometries\n        result = pd.DataFrame(columns=[\"_key_left\", \"_key_right\"], dtype=float)\n\n    if op == \"within\":\n        # within implemented as the inverse of contains; swap names\n        left_df, right_df = right_df, left_df\n        result = result.rename(\n            columns={\"_key_left\": \"_key_right\", \"_key_right\": \"_key_left\"}\n        )\n\n    if how == \"inner\":\n        result = result.set_index(\"_key_left\")\n        joined = (\n            left_df.merge(result, left_index=True, right_index=True)\n            .merge(\n                right_df.drop(right_df.geometry.name, axis=1),\n                left_on=\"_key_right\",\n                right_index=True,\n                suffixes=(\"_%s\" % lsuffix, \"_%s\" % rsuffix),\n            )\n            .set_index(index_left)\n            .drop([\"_key_right\"], axis=1)\n        )\n        if isinstance(index_left, list):\n            joined.index.names = left_index_name\n        else:\n            joined.index.name = left_index_name\n\n    elif how == \"left\":\n        result = result.set_index(\"_key_left\")\n        joined = (\n            left_df.merge(result, left_index=True, right_index=True, how=\"left\")\n            .merge(\n                right_df.drop(right_df.geometry.name, axis=1),\n                how=\"left\",\n                left_on=\"_key_right\",\n                right_index=True,\n                suffixes=(\"_%s\" % lsuffix, \"_%s\" % rsuffix),\n            )\n            .set_index(index_left)\n            .drop([\"_key_right\"], axis=1)\n        )\n        if isinstance(index_left, list):\n            joined.index.names = left_index_name\n        else:\n            joined.index.name = left_index_name\n\n    else:  # how == 'right':\n        joined = (\n            left_df.drop(left_df.geometry.name, axis=1)\n            .merge(\n                result.merge(\n                    right_df, left_on=\"_key_right\", right_index=True, how=\"right\"\n                ),\n                left_index=True,\n                right_on=\"_key_left\",\n                how=\"right\",\n            )\n            .set_index(index_right)\n            .drop([\"_key_left\", \"_key_right\"], axis=1)\n        )\n        if isinstance(index_right, list):\n            joined.index.names = right_index_name\n        else:\n            joined.index.name = right_index_name\n\n    return joined\n", "path": "geopandas/tools/sjoin.py"}]}