Repository: geopandas/geopandas

Issue:
crs is lost after saving GeoDataFrame to pickle and reading it back
Example below. I'm using geopandas 0.1.1 (with pandas 0.16.1, in case it matters).

``` python
>>> from geopandas import GeoDataFrame
>>> import pandas as pd
>>> df = GeoDataFrame.from_file('file.shp')
>>> df.to_pickle('file.pkl')
>>> df2 = pd.read_pickle('file.pkl')
>>> df2.geometry
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-98-59c7c6352665> in <module>()
----> 1 foo.geometry

/opt/anaconda3/lib/python3.4/site-packages/pandas/core/generic.py in __getattr__(self, name)
   2079         else:
   2080             if name in self._info_axis:
-> 2081                 return self[name]
   2082             raise AttributeError("'%s' object has no attribute '%s'" %
   2083                                  (type(self).__name__, name))

/opt/anaconda3/lib/python3.4/site-packages/geopandas/geodataframe.py in __getitem__(self, key)
    334         if isinstance(key, string_types) and key == geo_col:
    335             result.__class__ = GeoSeries
--> 336             result.crs = self.crs
    337         elif isinstance(result, DataFrame) and geo_col in result:
    338             result.__class__ = GeoDataFrame

/opt/anaconda3/lib/python3.4/site-packages/pandas/core/generic.py in __getattr__(self, name)
   2076                 or name in self._metadata
   2077                 or name in self._accessors):
-> 2078             return object.__getattribute__(self, name)
   2079         else:
   2080             if name in self._info_axis:

AttributeError: 'GeoDataFrame' object has no attribute 'crs'
```



Discussion:
I think this would be easiest to address upstream in pandas: pydata/pandas#10553

Great, many thanks for digging into this. I wasn't aware of the `_metadata` attribute in pandas, hopefully that'll make it a relatively easy fix.


Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -37,7 +37,15 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
         If str, column to use as geometry. If array, will be set as 'geometry'
         column on GeoDataFrame.
     """
+
+    # XXX: This will no longer be necessary in pandas 0.17
+    _internal_names = ['_data', '_cacher', '_item_cache', '_cache',
+                       'is_copy', '_subtyp', '_index',
+                       '_default_kind', '_default_fill_value', '_metadata',
+                       '__array_struct__', '__array_interface__']
+
     _metadata = ['crs', '_geometry_column_name']
+
     _geometry_column_name = DEFAULT_GEO_COLUMN_NAME
 
     def __init__(self, *args, **kwargs):
@@ -49,6 +57,13 @@ def __init__(self, *args, **kwargs):
             self.set_geometry(geometry, inplace=True)
         self._invalidate_sindex()
 
+    # Serialize metadata (will no longer be necessary in pandas 0.17+)
+    # See https://github.com/pydata/pandas/pull/10557
+    def __getstate__(self):
+        meta = dict((k, getattr(self, k, None)) for k in self._metadata)
+        return dict(_data=self._data, _typ=self._typ,
+                    _metadata=self._metadata, **meta)
+
     def __setattr__(self, attr, val):
         # have to special case geometry b/c pandas tries to use as column...
         if attr == 'geometry':


Test patch:
diff --git a/tests/test_geodataframe.py b/tests/test_geodataframe.py
--- a/tests/test_geodataframe.py
+++ b/tests/test_geodataframe.py
@@ -487,3 +487,10 @@ def test_geodataframe_geojson_bbox(self):
         self.assertTrue(isinstance(geo['bbox'], tuple))
         for feature in geo['features']:
             self.assertTrue('bbox' in feature.keys())
+
+    def test_pickle(self):
+        filename = os.path.join(self.tempdir, 'df.pkl')
+        self.df.to_pickle(filename)
+        unpickled = pd.read_pickle(filename)
+        assert_frame_equal(self.df, unpickled)
+        self.assertEqual(self.df.crs, unpickled.crs)