Repository: geopandas/geopandas

Issue:
Implement the __geo_interface__
Implement a `__geo_interface__` method on `GeoDataFrames` and `GeoSeries`. See https://gist.github.com/sgillies/2217756



Discussion:
I think this is a great idea, but it seems like `__geo_interface__` is defined for a single feature or single geometry, not a collection. https://gist.github.com/sgillies/2217756 doesn't list `FeatureCollection` as a valid `type` and I always assumed it's not implied in the "etc.". I don't know if existing users of `__geo_interface__` could get burned by adding `FeatureCollection`. @sgillies? 


Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -202,7 +202,7 @@ def from_postgis(cls, sql, con, geom_col='geom', crs=None, index_col=None,
 
 
     def to_json(self, na='null', **kwargs):
-        """Returns a GeoJSON representation of the GeoDataFrame.
+        """Returns a GeoJSON string representation of the GeoDataFrame.
 
         Parameters
         ----------
@@ -216,6 +216,33 @@ def to_json(self, na='null', **kwargs):
         
         The remaining *kwargs* are passed to json.dumps().
         """
+        return json.dumps(self._to_geo(na), **kwargs)
+
+    @property
+    def __geo_interface__(self):
+        """Returns a python feature collection (i.e. the geointerface)
+           representation of the GeoDataFrame.
+
+           This differs from `_to_geo()` only in that it is a property
+           with a default `na` arg instead of a method
+        """
+        return self._to_geo(na='null')
+
+    def _to_geo(self, na='null'):
+        """Returns a python feature collection (i.e. the geointerface)
+           representation of the GeoDataFrame.
+
+        Parameters
+        ----------
+        na : {'null', 'drop', 'keep'}, default 'null'
+            Indicates how to output missing (NaN) values in the GeoDataFrame
+            * null: ouput the missing entries as JSON null
+            * drop: remove the property from the feature. This applies to
+                    each feature individually so that features may have
+                    different properties
+            * keep: output the missing entries as NaN
+        """
+
         def fill_none(row):
             """
             Takes in a Series, converts to a dictionary with null values
@@ -246,10 +273,8 @@ def feature(i, row):
                     dict((k, v) for k, v in iteritems(row) if k != self._geometry_column_name),
                 'geometry': mapping(row[self._geometry_column_name]) }
 
-        return json.dumps(
-            {'type': 'FeatureCollection',
-             'features': [feature(i, row) for i, row in self.iterrows()]},
-            **kwargs )
+        return {'type': 'FeatureCollection',
+                'features': [feature(i, row) for i, row in self.iterrows()]}
             
     def to_file(self, filename, driver="ESRI Shapefile", **kwargs):
         """
diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py
--- a/geopandas/geoseries.py
+++ b/geopandas/geoseries.py
@@ -82,6 +82,13 @@ def from_file(cls, filename, **kwargs):
         g.crs = crs
         return g
 
+    @property
+    def __geo_interface__(self):
+        """Returns a GeoSeries as a python feature collection
+        """
+        from geopandas import GeoDataFrame
+        return GeoDataFrame({'geometry': self}).__geo_interface__
+
     def to_file(self, filename, driver="ESRI Shapefile", **kwargs):
         from geopandas import GeoDataFrame
         data = GeoDataFrame({"geometry": self,


Test patch:
diff --git a/tests/test_geodataframe.py b/tests/test_geodataframe.py
--- a/tests/test_geodataframe.py
+++ b/tests/test_geodataframe.py
@@ -425,3 +425,9 @@ def test_dataframe_to_geodataframe(self):
 
         with self.assertRaises(ValueError):
             df.set_geometry('location', inplace=True)
+
+    def test_geodataframe_geointerface(self):
+        self.assertEqual(self.df.__geo_interface__['type'], 'FeatureCollection')
+        self.assertEqual(len(self.df.__geo_interface__['features']),
+                         self.df.shape[0])
+
diff --git a/tests/test_geoseries.py b/tests/test_geoseries.py
--- a/tests/test_geoseries.py
+++ b/tests/test_geoseries.py
@@ -139,5 +139,10 @@ def test_fillna(self):
         # XXX: method works inconsistently for different pandas versions
         #self.na_none.fillna(method='backfill')
 
+    def test_geoseries_geointerface(self):
+        self.assertEqual(self.g1.__geo_interface__['type'], 'FeatureCollection')
+        self.assertEqual(len(self.g1.__geo_interface__['features']),
+                         self.g1.shape[0])
+
 if __name__ == '__main__':
     unittest.main()