Browse Source

Fixed #25141 -- Diminished GDAL dependence during geojson serialization

Only require GDAL if contained geometries need coordinate transformations.
Thanks drepo for the report and Tim Graham for the review.
Claude Paroz 9 years ago
parent
commit
1da170a203

+ 14 - 0
django/contrib/gis/geos/collections.py

@@ -2,6 +2,7 @@
  This module houses the Geometry Collection objects:
  GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
 """
+import json
 from ctypes import byref, c_int, c_uint
 
 from django.contrib.gis.geos import prototypes as capi
@@ -83,6 +84,19 @@ class GeometryCollection(GEOSGeometry):
     _set_single = GEOSGeometry._set_single_rebuild
     _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
 
+    @property
+    def json(self):
+        if self.__class__.__name__ == 'GeometryCollection':
+            return json.dumps({
+                'type': self.__class__.__name__,
+                'geometries': [
+                    {'type': geom.__class__.__name__, 'coordinates': geom.coords}
+                    for geom in self
+                ],
+            })
+        return super(GeometryCollection, self).json
+    geojson = json
+
     @property
     def kml(self):
         "Returns the KML for this Geometry Collection."

+ 3 - 5
django/contrib/gis/geos/geometry.py

@@ -4,6 +4,7 @@
 """
 from __future__ import unicode_literals
 
+import json
 from ctypes import addressof, byref, c_double
 
 from django.contrib.gis import gdal
@@ -411,12 +412,9 @@ class GEOSGeometry(GEOSBase, ListMixin):
     @property
     def json(self):
         """
-        Returns GeoJSON representation of this Geometry if GDAL is installed.
+        Returns GeoJSON representation of this Geometry.
         """
-        if gdal.HAS_GDAL:
-            return self.ogr.json
-        else:
-            raise GEOSException('GeoJSON output only supported when GDAL is installed.')
+        return json.dumps({'type': self.__class__.__name__, 'coordinates': self.coords})
     geojson = json
 
     @property

+ 9 - 7
django/contrib/gis/serializers/geojson.py

@@ -17,17 +17,14 @@ class Serializer(JSONSerializer):
     def _init_options(self):
         super(Serializer, self)._init_options()
         self.geometry_field = self.json_kwargs.pop('geometry_field', None)
-        self.srs = SpatialReference(self.json_kwargs.pop('srid', 4326))
+        self.srid = self.json_kwargs.pop('srid', 4326)
 
     def start_serialization(self):
-        if not HAS_GDAL:
-            # GDAL is needed for the geometry.geojson call
-            raise SerializationError("The geojson serializer requires the GDAL library.")
         self._init_options()
         self._cts = {}  # cache of CoordTransform's
         self.stream.write(
             '{"type": "FeatureCollection", "crs": {"type": "name", "properties": {"name": "EPSG:%d"}},'
-            ' "features": [' % self.srs.srid)
+            ' "features": [' % self.srid)
 
     def end_serialization(self):
         self.stream.write(']}')
@@ -48,10 +45,15 @@ class Serializer(JSONSerializer):
             "properties": self._current,
         }
         if self._geometry:
-            if self._geometry.srid != self.srs.srid:
+            if self._geometry.srid != self.srid:
                 # If needed, transform the geometry in the srid of the global geojson srid
+                if not HAS_GDAL:
+                    raise SerializationError(
+                        'Unable to convert geometry to SRID %s when GDAL is not installed.' % self.srid
+                    )
                 if self._geometry.srid not in self._cts:
-                    self._cts[self._geometry.srid] = CoordTransform(self._geometry.srs, self.srs)
+                    srs = SpatialReference(self.srid)
+                    self._cts[self._geometry.srid] = CoordTransform(self._geometry.srs, srs)
                 self._geometry.transform(self._cts[self._geometry.srid])
             data["geometry"] = eval(self._geometry.geojson)
         else:

+ 11 - 3
docs/ref/contrib/gis/serializers.txt

@@ -7,9 +7,17 @@ GeoJSON Serializer
 .. module:: django.contrib.gis.serializers.geojson
    :synopsis: Serialization of GeoDjango models in the GeoJSON format.
 
-GeoDjango provides a specific serializer for the `GeoJSON`__ format. The GDAL
-library is required for this serializer. See :doc:`/topics/serialization` for
-more information on serialization.
+GeoDjango provides a specific serializer for the `GeoJSON`__ format. See
+:doc:`/topics/serialization` for more information on serialization.
+
+The GDAL library is required if any of the serialized geometries need
+coordinate transformations (that is if the geometry's spatial reference system
+differs from the ``srid`` serializer option).
+
+.. versionchanged:: 1.9
+
+    The GeoJSON serializer no longer needs GDAL if all geometries are in the
+    same coordinate system as the ``srid`` serializer option.
 
 __ http://geojson.org/
 

+ 10 - 1
tests/gis_tests/geoapp/test_serializers.py

@@ -4,7 +4,8 @@ import json
 
 from django.contrib.gis.geos import LinearRing, Point, Polygon
 from django.core import serializers
-from django.test import TestCase, skipUnlessDBFeature
+from django.test import TestCase, mock, skipUnlessDBFeature
+from django.utils import six
 
 from .models import City, MultiFields, PennsylvaniaCity
 
@@ -70,6 +71,14 @@ class GeoJSONSerializerTests(TestCase):
             [int(c) for c in geodata['features'][0]['geometry']['coordinates']],
             [1564802, 5613214])
 
+    @mock.patch('django.contrib.gis.serializers.geojson.HAS_GDAL', False)
+    def test_without_gdal(self):
+        # Without coordinate transformation, the serialization should succeed:
+        serializers.serialize('geojson', City.objects.all())
+        with six.assertRaisesRegex(self, serializers.base.SerializationError, '.*GDAL is not installed'):
+            # Coordinate transformations need GDAL
+            serializers.serialize('geojson', City.objects.all(), srid=2847)
+
     def test_deserialization_exception(self):
         """
         GeoJSON cannot be deserialized.