Repository: geopandas/geopandas

Issue:
REGR: sindex is broken after pickling
When we moved `sindex` to `GeometryArray`, we broke pickling. Currently if you load a pickled gdf, `sindex` raises `AttributeError: 'GeometryArray' object has no attribute '_sindex'`.

```py
import geopandas
import pandas

df = geopandas.read_file(geopandas.datasets.get_path('nybb'))
df.to_pickle('tst.pkl')

pandas.read_pickle('tst.pkl').sindex
```
```py

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-b2cfd76702ae> in <module>
----> 1 pandas.read_pickle('tst.pkl').sindex

/opt/miniconda3/envs/geo_dev/lib/python3.7/site-packages/pandas/core/generic.py in __getattr__(self, name)
   5137             if self._info_axis._can_hold_identifiers_and_holds_name(name):
   5138                 return self[name]
-> 5139             return object.__getattribute__(self, name)
   5140 
   5141     def __setattr__(self, name: str, value) -> None:

~/Git/geopandas/martinfleis/geopandas/base.py in sindex(self)
    963     @property
    964     def sindex(self):
--> 965         return self.geometry.values.sindex
    966 
    967     @property

~/Git/geopandas/martinfleis/geopandas/array.py in sindex(self)
    293     @property
    294     def sindex(self):
--> 295         if self._sindex is None:
    296             self._sindex = _get_sindex_class()(self.data)
    297         return self._sindex

AttributeError: 'GeometryArray' object has no attribute '_sindex'
```

I think we need some machinery here as we have for CRS: https://github.com/geopandas/geopandas/blob/1ef924270e950c6e8862335c42ace8d90e5cb3db/geopandas/array.py#L420-L436

There is one related issue with the pickling of `pygeos.STRtree` which currently raises `TypeError: can't pickle pygeos.lib.STRtree objects` so that may need to be resolved before we can properly pickle gdf. 

Anyway, even if sindex will not be pickled, geodataframe read from pickle should be able to build it, which fail atm.

Works in 0.8.1.


Discussion:

Resolving patch:
diff --git a/geopandas/array.py b/geopandas/array.py
--- a/geopandas/array.py
+++ b/geopandas/array.py
@@ -417,20 +417,20 @@ def __setitem__(self, key, value):
         #             "and CRS of existing geometries."
         #         )
 
-    if compat.USE_PYGEOS:
-
-        def __getstate__(self):
+    def __getstate__(self):
+        if compat.USE_PYGEOS:
             return (pygeos.to_wkb(self.data), self._crs)
+        else:
+            return self.__dict__
 
-        def __setstate__(self, state):
+    def __setstate__(self, state):
+        if compat.USE_PYGEOS:
             geoms = pygeos.from_wkb(state[0])
             self._crs = state[1]
+            self._sindex = None  # pygeos.STRtree could not be pickled yet
             self.data = geoms
             self.base = None
-
-    else:
-
-        def __setstate__(self, state):
+        else:
             if "_crs" not in state:
                 state["_crs"] = None
             self.__dict__.update(state)


Test patch:
diff --git a/geopandas/io/tests/test_pickle.py b/geopandas/io/tests/test_pickle.py
--- a/geopandas/io/tests/test_pickle.py
+++ b/geopandas/io/tests/test_pickle.py
@@ -14,7 +14,8 @@
 import pytest
 from geopandas.testing import assert_geodataframe_equal
 from geopandas import _compat as compat
-
+import geopandas
+from shapely.geometry import Point
 
 DATA_PATH = pathlib.Path(os.path.dirname(__file__)) / "data"
 
@@ -35,6 +36,14 @@ def legacy_pickle(request):
     return request.param
 
 
+@pytest.fixture
+def with_use_pygeos_false():
+    orig = geopandas.options.use_pygeos
+    geopandas.options.use_pygeos = not orig
+    yield
+    geopandas.options.use_pygeos = orig
+
+
 @pytest.mark.skipif(
     compat.USE_PYGEOS or (str(pyproj.__version__) < LooseVersion("2.4")),
     reason=(
@@ -58,3 +67,16 @@ def test_round_trip_current(tmpdir, current_pickle_data):
         value.to_pickle(path)
         result = pd.read_pickle(path)
         assert_geodataframe_equal(result, value)
+        assert isinstance(result.has_sindex, bool)
+
+
+@pytest.mark.skipif(not compat.HAS_PYGEOS, reason="requires pygeos to test #1745")
+def test_pygeos_switch(tmpdir, with_use_pygeos_false):
+    gdf_crs = geopandas.GeoDataFrame(
+        {"a": [0.1, 0.2, 0.3], "geometry": [Point(1, 1), Point(2, 2), Point(3, 3)]},
+        crs="EPSG:4326",
+    )
+    path = str(tmpdir / "gdf_crs.pickle")
+    gdf_crs.to_pickle(path)
+    result = pd.read_pickle(path)
+    assert_geodataframe_equal(result, gdf_crs)