Commit 35dac507 authored by Claude Paroz's avatar Claude Paroz
Browse files

Added a new GeoJSON serialization format for GeoDjango

Thanks Reinout van Rees for the review.
parent c5132382
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
from django.apps import AppConfig
from django.core import serializers

from django.utils.translation import ugettext_lazy as _

@@ -6,3 +7,7 @@ from django.utils.translation import ugettext_lazy as _
class GISConfig(AppConfig):
    name = 'django.contrib.gis'
    verbose_name = _("GIS")

    def ready(self):
        if 'geojson' not in serializers.BUILTIN_SERIALIZERS:
            serializers.BUILTIN_SERIALIZERS['geojson'] = "django.contrib.gis.serializers.geojson"
+0 −0

Empty file added.

+68 −0
Original line number Diff line number Diff line
from __future__ import unicode_literals

from django.contrib.gis.gdal import HAS_GDAL
from django.core.serializers.base import SerializerDoesNotExist, SerializationError
from django.core.serializers.json import Serializer as JSONSerializer

if HAS_GDAL:
    from django.contrib.gis.gdal import CoordTransform, SpatialReference


class Serializer(JSONSerializer):
    """
    Convert a queryset to GeoJSON, http://geojson.org/
    """
    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))

    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)

    def end_serialization(self):
        self.stream.write(']}')

    def start_object(self, obj):
        super(Serializer, self).start_object(obj)
        self._geometry = None
        if self.geometry_field is None:
            # Find the first declared geometry field
            for field in obj._meta.fields:
                if hasattr(field, 'geom_type'):
                    self.geometry_field = field.name
                    break

    def get_dump_object(self, obj):
        data = {
            "type": "Feature",
            "properties": self._current,
        }
        if self._geometry:
            if self._geometry.srid != self.srs.srid:
                # If needed, transform the geometry in the srid of the global geojson srid
                if self._geometry.srid not in self._cts:
                    self._cts[self._geometry.srid] = CoordTransform(self._geometry.srs, self.srs)
                self._geometry.transform(self._cts[self._geometry.srid])
            data["geometry"] = eval(self._geometry.geojson)
        else:
            data["geometry"] = None
        return data

    def handle_field(self, obj, field):
        if field.name == self.geometry_field:
            self._geometry = field._get_val_from_obj(obj)
        else:
            super(Serializer, self).handle_field(obj, field)


class Deserializer(object):
    def __init__(self, *args, **kwargs):
        raise SerializerDoesNotExist("geojson is a serialization-only serializer")
+6 −0
Original line number Diff line number Diff line
@@ -46,6 +46,12 @@ class Track(NamedModel):
    line = models.LineStringField()


class MultiFields(NamedModel):
    city = models.ForeignKey(City)
    point = models.PointField()
    poly = models.PolygonField()


class Truth(models.Model):
    val = models.BooleanField(default=False)

+81 −0
Original line number Diff line number Diff line
from __future__ import unicode_literals

import json

from django.contrib.gis.geos import HAS_GEOS
from django.core import serializers
from django.test import TestCase, skipUnlessDBFeature

from .models import City, MultiFields, PennsylvaniaCity

if HAS_GEOS:
    from django.contrib.gis.geos import LinearRing, Point, Polygon


@skipUnlessDBFeature("gis_enabled")
class GeoJSONSerializerTests(TestCase):
    fixtures = ['initial']

    def test_builtin_serializers(self):
        """
        'geojson' should be listed in available serializers.
        """
        all_formats = set(serializers.get_serializer_formats())
        public_formats = set(serializers.get_public_serializer_formats())

        self.assertIn('geojson', all_formats),
        self.assertIn('geojson', public_formats)

    def test_serialization_base(self):
        geojson = serializers.serialize('geojson', City.objects.all().order_by('name'))
        try:
            geodata = json.loads(geojson)
        except Exception:
            self.fail("Serialized output is not valid JSON")
        self.assertEqual(len(geodata['features']), len(City.objects.all()))
        self.assertEqual(geodata['features'][0]['geometry']['type'], 'Point')
        self.assertEqual(geodata['features'][0]['properties']['name'], 'Chicago')

    def test_geometry_field_option(self):
        """
        When a model has several geometry fields, the 'geometry_field' option
        can be used to specify the field to use as the 'geometry' key.
        """
        MultiFields.objects.create(
            city=City.objects.first(), name='Name', point=Point(5, 23),
            poly=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))

        geojson = serializers.serialize('geojson', MultiFields.objects.all())
        geodata = json.loads(geojson)
        self.assertEqual(geodata['features'][0]['geometry']['type'], 'Point')

        geojson = serializers.serialize('geojson', MultiFields.objects.all(),
            geometry_field='poly')
        geodata = json.loads(geojson)
        self.assertEqual(geodata['features'][0]['geometry']['type'], 'Polygon')

    def test_fields_option(self):
        """
        The fields option allows to define a subset of fields to be present in
        the 'properties' of the generated output.
        """
        PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
        geojson = serializers.serialize('geojson', PennsylvaniaCity.objects.all(),
            fields=('county', 'point'))
        geodata = json.loads(geojson)
        self.assertIn('county', geodata['features'][0]['properties'])
        self.assertNotIn('founded', geodata['features'][0]['properties'])

    def test_srid_option(self):
        geojson = serializers.serialize('geojson', City.objects.all().order_by('name'), srid=2847)
        geodata = json.loads(geojson)
        self.assertEqual(
            [int(c) for c in geodata['features'][0]['geometry']['coordinates']],
            [1564802, 5613214])

    def test_deserialization_exception(self):
        """
        GeoJSON cannot be deserialized.
        """
        with self.assertRaises(serializers.base.SerializerDoesNotExist):
            serializers.deserialize('geojson', '{}')
Loading