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.
This commit is contained in:
Claude Paroz 2015-07-18 14:50:08 +02:00
parent 774c16d16e
commit 1da170a203
5 changed files with 47 additions and 16 deletions

View File

@ -2,6 +2,7 @@
This module houses the Geometry Collection objects: This module houses the Geometry Collection objects:
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
""" """
import json
from ctypes import byref, c_int, c_uint from ctypes import byref, c_int, c_uint
from django.contrib.gis.geos import prototypes as capi from django.contrib.gis.geos import prototypes as capi
@ -83,6 +84,19 @@ class GeometryCollection(GEOSGeometry):
_set_single = GEOSGeometry._set_single_rebuild _set_single = GEOSGeometry._set_single_rebuild
_assign_extended_slice = GEOSGeometry._assign_extended_slice_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 @property
def kml(self): def kml(self):
"Returns the KML for this Geometry Collection." "Returns the KML for this Geometry Collection."

View File

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

View File

@ -17,17 +17,14 @@ class Serializer(JSONSerializer):
def _init_options(self): def _init_options(self):
super(Serializer, self)._init_options() super(Serializer, self)._init_options()
self.geometry_field = self.json_kwargs.pop('geometry_field', None) 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): 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._init_options()
self._cts = {} # cache of CoordTransform's self._cts = {} # cache of CoordTransform's
self.stream.write( self.stream.write(
'{"type": "FeatureCollection", "crs": {"type": "name", "properties": {"name": "EPSG:%d"}},' '{"type": "FeatureCollection", "crs": {"type": "name", "properties": {"name": "EPSG:%d"}},'
' "features": [' % self.srs.srid) ' "features": [' % self.srid)
def end_serialization(self): def end_serialization(self):
self.stream.write(']}') self.stream.write(']}')
@ -48,10 +45,15 @@ class Serializer(JSONSerializer):
"properties": self._current, "properties": self._current,
} }
if self._geometry: 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 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: 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]) self._geometry.transform(self._cts[self._geometry.srid])
data["geometry"] = eval(self._geometry.geojson) data["geometry"] = eval(self._geometry.geojson)
else: else:

View File

@ -7,9 +7,17 @@ GeoJSON Serializer
.. module:: django.contrib.gis.serializers.geojson .. module:: django.contrib.gis.serializers.geojson
:synopsis: Serialization of GeoDjango models in the GeoJSON format. :synopsis: Serialization of GeoDjango models in the GeoJSON format.
GeoDjango provides a specific serializer for the `GeoJSON`__ format. The GDAL GeoDjango provides a specific serializer for the `GeoJSON`__ format. See
library is required for this serializer. See :doc:`/topics/serialization` for :doc:`/topics/serialization` for more information on serialization.
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/ __ http://geojson.org/

View File

@ -4,7 +4,8 @@ import json
from django.contrib.gis.geos import LinearRing, Point, Polygon from django.contrib.gis.geos import LinearRing, Point, Polygon
from django.core import serializers 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 from .models import City, MultiFields, PennsylvaniaCity
@ -70,6 +71,14 @@ class GeoJSONSerializerTests(TestCase):
[int(c) for c in geodata['features'][0]['geometry']['coordinates']], [int(c) for c in geodata['features'][0]['geometry']['coordinates']],
[1564802, 5613214]) [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): def test_deserialization_exception(self):
""" """
GeoJSON cannot be deserialized. GeoJSON cannot be deserialized.