Repository: geopandas/geopandas

Issue:
Optionally include bbox in GeoJSON representations
The json (and geo interface) representations of the data frame should optionally include the bounding box (`bbox`).

This could be implemented at the `FeatureCollection` level (using `df.total_bounds`) as well as on each `Feature`. 

http://geojson.org/geojson-spec.html#bounding-boxes



Discussion:

Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -201,7 +201,7 @@ def from_postgis(cls, sql, con, geom_col='geom', crs=None, index_col=None,
                      coerce_float, params)
 
 
-    def to_json(self, na='null', **kwargs):
+    def to_json(self, na='null', show_bbox=False, **kwargs):
         """Returns a GeoJSON string representation of the GeoDataFrame.
 
         Parameters
@@ -213,10 +213,12 @@ def to_json(self, na='null', **kwargs):
                     each feature individually so that features may have
                     different properties
             * keep: output the missing entries as NaN
+
+        show_bbox : include bbox (bounds) in the geojson
         
         The remaining *kwargs* are passed to json.dumps().
         """
-        return json.dumps(self._to_geo(na), **kwargs)
+        return json.dumps(self._to_geo(na, show_bbox), **kwargs)
 
     @property
     def __geo_interface__(self):
@@ -224,11 +226,11 @@ def __geo_interface__(self):
            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
+           with default args instead of a method
         """
-        return self._to_geo(na='null')
+        return self._to_geo(na='null', show_bbox=True)
 
-    def _to_geo(self, na='null'):
+    def _to_geo(self, na='null', show_bbox=False):
         """Returns a python feature collection (i.e. the geointerface)
            representation of the GeoDataFrame.
 
@@ -241,6 +243,8 @@ def _to_geo(self, na='null'):
                     each feature individually so that features may have
                     different properties
             * keep: output the missing entries as NaN
+
+        show_bbox : include bbox (bounds) in the geojson. default False
         """
 
         def fill_none(row):
@@ -265,16 +269,27 @@ def fill_none(row):
         f = na_methods[na]
 
         def feature(i, row):
+            bbox = row[self._geometry_column_name].bounds
             row = f(row)
-            return {
+            feat = {
                 'id': str(i),
                 'type': 'Feature',
                 'properties':
                     dict((k, v) for k, v in iteritems(row) if k != self._geometry_column_name),
                 'geometry': mapping(row[self._geometry_column_name]) }
 
-        return {'type': 'FeatureCollection',
-                'features': [feature(i, row) for i, row in self.iterrows()]}
+            if show_bbox:
+                feat['bbox'] = bbox
+
+            return feat
+
+        geo = {'type': 'FeatureCollection',
+               'features': [feature(i, row) for i, row in self.iterrows()]}
+
+        if show_bbox:
+            geo['bbox'] = self.total_bounds
+
+        return geo
             
     def to_file(self, filename, driver="ESRI Shapefile", **kwargs):
         """


Test patch:
diff --git a/tests/test_geodataframe.py b/tests/test_geodataframe.py
--- a/tests/test_geodataframe.py
+++ b/tests/test_geodataframe.py
@@ -433,3 +433,16 @@ def test_geodataframe_geointerface(self):
         self.assertEqual(len(self.df.__geo_interface__['features']),
                          self.df.shape[0])
 
+    def test_geodataframe_geojson_no_bbox(self):
+        geo = self.df._to_geo(na="null", show_bbox=False)
+        self.assertFalse('bbox' in geo.keys())
+        for feature in geo['features']:
+            self.assertFalse('bbox' in feature.keys())
+
+    def test_geodataframe_geojson_bbox(self):
+        geo = self.df._to_geo(na="null", show_bbox=True)
+        self.assertTrue('bbox' in geo.keys())
+        self.assertEqual(len(geo['bbox']), 4)
+        self.assertTrue(isinstance(geo['bbox'], tuple))
+        for feature in geo['features']:
+            self.assertTrue('bbox' in feature.keys())