Added a new GeoJSON serialization format for GeoDjango
Thanks Reinout van Rees for the review.
This commit is contained in:
parent
c5132382f0
commit
35dac5070b
|
@ -1,4 +1,5 @@
|
|||
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 +1,68 @@
|
|||
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")
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
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', '{}')
|
|
@ -24,7 +24,7 @@ class Serializer(PythonSerializer):
|
|||
"""
|
||||
internal_use_only = False
|
||||
|
||||
def start_serialization(self):
|
||||
def _init_options(self):
|
||||
if json.__version__.split('.') >= ['2', '1', '3']:
|
||||
# Use JS strings to represent Python Decimal instances (ticket #16850)
|
||||
self.options.update({'use_decimal': False})
|
||||
|
@ -35,6 +35,9 @@ class Serializer(PythonSerializer):
|
|||
if self.options.get('indent'):
|
||||
# Prevent trailing spaces
|
||||
self.json_kwargs['separators'] = (',', ': ')
|
||||
|
||||
def start_serialization(self):
|
||||
self._init_options()
|
||||
self.stream.write("[")
|
||||
|
||||
def end_serialization(self):
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
.. _ref-geojson-serializer:
|
||||
|
||||
==================
|
||||
GeoJSON Serializer
|
||||
==================
|
||||
|
||||
.. versionadded:: 1.8
|
||||
|
||||
.. 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.
|
||||
|
||||
__ http://geojson.org/
|
||||
|
||||
The ``geojson`` serializer is not meant for round-tripping data, as it has no
|
||||
deserializer equivalent. For example, you cannot use :djadmin:`loaddata` to
|
||||
reload the output produced by this serializer. If you plan to reload the
|
||||
outputted data, use the plain :ref:`json serializer <serialization-formats-json>`
|
||||
instead.
|
||||
|
||||
In addition to the options of the ``json`` serializer, the ``geojson``
|
||||
serializer accepts the following additional option when it is called by
|
||||
``serializers.serialize()``:
|
||||
|
||||
* ``geometry_field``: A string containing the name of a geometry field to use
|
||||
for the ``geometry`` key of the GeoJSON feature. This is only needed when you
|
||||
have a model with more than one geometry field and you don't want to use the
|
||||
first defined geometry field (by default, the first geometry field is picked).
|
||||
|
||||
* ``srid``: The SRID to use for the ``geometry`` content. Defaults to 4326
|
||||
(WGS 84).
|
||||
|
||||
The :ref:`fields <subset-of-fields>` option can be used to limit fields that
|
||||
will be present in the ``properties`` key, as it works with all other
|
||||
serializers.
|
||||
|
||||
Example::
|
||||
|
||||
from django.core.serializers import serialize
|
||||
from my_app.models import City
|
||||
|
||||
serialize('geojson', City.objects.all(),
|
||||
geometry_field='point',
|
||||
fields=('name',))
|
||||
|
||||
Would output::
|
||||
|
||||
{
|
||||
'type': 'FeatureCollection',
|
||||
'crs': {
|
||||
'type': 'name',
|
||||
'properties': {'name': 'EPSG:4326'}
|
||||
},
|
||||
'features': [
|
||||
{
|
||||
'type': 'Feature',
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': [-87.650175, 41.850385]
|
||||
},
|
||||
'properties': {
|
||||
'name': 'Chicago'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -15,3 +15,4 @@ useful in creating geospatial Web applications.
|
|||
|
||||
layermapping
|
||||
ogrinspect
|
||||
serializers
|
||||
|
|
|
@ -131,12 +131,15 @@ Minor features
|
|||
:mod:`django.contrib.gis`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* Compatibility shims for ``SpatialRefSys`` and ``GeometryColumns`` changed in
|
||||
Django 1.2 have been removed.
|
||||
* A new :doc:`GeoJSON serializer </ref/contrib/gis/serializers>` is now
|
||||
available.
|
||||
|
||||
* The Spatialite backend now supports ``Collect`` and ``Extent`` aggregates
|
||||
when the database version is 3.0 or later.
|
||||
|
||||
* Compatibility shims for ``SpatialRefSys`` and ``GeometryColumns`` changed in
|
||||
Django 1.2 have been removed.
|
||||
|
||||
:mod:`django.contrib.messages`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ This is useful if you want to serialize data directly to a file-like object
|
|||
:ref:`format <serialization-formats>` will raise a
|
||||
``django.core.serializers.SerializerDoesNotExist`` exception.
|
||||
|
||||
.. _subset-of-fields:
|
||||
|
||||
Subset of fields
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -257,6 +259,9 @@ In particular, :ref:`lazy translation objects <lazy-translations>` need a
|
|||
return force_text(obj)
|
||||
return super(LazyEncoder, self).default(obj)
|
||||
|
||||
Also note that GeoDjango provides a :doc:`customized GeoJSON serializer
|
||||
</ref/contrib/gis/serializers>`.
|
||||
|
||||
.. _special encoder: http://docs.python.org/library/json.html#encoders-and-decoders
|
||||
.. _ecma-262: http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
|
||||
|
||||
|
|
|
@ -578,7 +578,7 @@ def naturalKeyTest(format, self):
|
|||
|
||||
|
||||
for format in [f for f in serializers.get_serializer_formats()
|
||||
if not isinstance(serializers.get_serializer(f), serializers.BadSerializer)]:
|
||||
if not isinstance(serializers.get_serializer(f), serializers.BadSerializer) and not f == 'geojson']:
|
||||
setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format))
|
||||
setattr(SerializerTests, 'test_' + format + '_natural_key_serializer', curry(naturalKeySerializerTest, format))
|
||||
setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))
|
||||
|
|
Loading…
Reference in New Issue