Commit 1da170a2 authored by Claude Paroz's avatar Claude Paroz
Browse files

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.
parent 774c16d1
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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.