Browse files

Initial import of working version

  • Loading branch information...
0 parents commit c3cac0c4b00428aad0c83166d252b4662a992d7c @leplatrem leplatrem committed Aug 2, 2012
8 CHANGES
@@ -0,0 +1,8 @@
+=========
+CHANGELOG
+=========
+
+1.0.0 (unreleased)
+==================
+
+- Initial working version.
165 LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
1 MANIFEST.in
@@ -0,0 +1 @@
+include README.rst CHANGES LICENSE
103 README.rst
@@ -0,0 +1,103 @@
+*django-geojson*
+
+
+=======
+INSTALL
+=======
+
+::
+
+ pip install django-geojson
+
+=====
+USAGE
+=====
+
+GeoJSON layer view
+------------------
+
+Very useful for web mapping :
+
+::
+
+ from djgeojson.views import GeoJSONLayerView
+
+
+ class MeetingLayer(GeoJSONLayerView):
+ model = Meeting
+ fields = ('title', 'datetime',)
+ # Options
+ srid = 4326 # projection
+ precision = 4 # float
+ simplify = 0.5 # generalization
+
+
+Consume the vector layer as usual, for example, with Leaflet :
+
+::
+
+ var layer = L.GeoJSON();
+ map.addLayer(layer);
+ $.getJSON('{% url viewname %}', function (data){
+ layer.addData(data);
+ });
+
+
+GeoJSON template filter
+-----------------------
+
+Will work either for a model, a geometry field or a queryset.
+
+::
+
+ {% load geojson_tags %}
+
+ var feature = {{ object|geojsonfeature }};
+
+ var geom = {{ object.geom|geojsonfeature }};
+
+ var collection = {{ object_list|geojsonfeature }};
+
+
+Dump GIS models
+---------------
+
+Register the serializer in your project :
+
+::
+
+ # settings.py
+
+ SERIALIZATION_MODULES = {
+ 'geojson' : 'djgeojson.serializers'
+ }
+
+Command-line ``dumpdata`` can export files, viewable in GIS software like QGis :
+
+::
+
+ django dumpdata --format=geojson yourapp.Model > export.geojson
+
+Works with ``loaddata`` as well, which can now import GeoJSON files.
+
+
+
+=======
+AUTHORS
+=======
+
+ * Mathieu Leplatre <mathieu.leplatre@makina-corpus.com>
+ * Daniel Sokolowski, author of original serializer snippet
+
+Relies massively on Sean Gillies' `geojson <>`_ python module.
+
+|makinacom|_
+
+.. |makinacom| image:: http://depot.makina-corpus.org/public/logo.gif
+.. _makinacom: http://www.makina-corpus.com
+
+=======
+LICENSE
+=======
+
+ * Lesser GNU Public License
0 djgeojson/__init__.py
No changes.
7 djgeojson/http.py
@@ -0,0 +1,7 @@
+from django.http import HttpResponse
+
+
+class HttpJSONResponse(HttpResponse):
+ def __init__(self, **kwargs):
+ kwargs['content_type'] = 'application/json'
+ super(HttpJSONResponse, self).__init__(**kwargs)
0 djgeojson/models.py
No changes.
140 djgeojson/serializers.py
@@ -0,0 +1,140 @@
+"""
+
+ This code is a modified version of Daniel's GeoJSON serializer.
+
+ http://djangosnippets.org/snippets/2596/
+
+ Created on 2011-05-12
+ Updated on 2011-11-09 -- added desrializer support
+
+ @author: Daniel Sokolowski
+
+ Extends django's built in JSON serializer to support GEOJSON encoding
+"""
+import logging
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+from simplejson import encoder
+
+from django.core.serializers.base import DeserializationError
+from django.core.serializers.json import (Serializer as JsonSerializer,
+ DateTimeAwareJSONEncoder)
+from django.utils import simplejson
+from django.utils.translation import gettext as _
+from django.contrib.gis.db.models.fields import GeometryField
+from django.core.serializers.python import Deserializer as PythonDeserializer
+from django.utils.encoding import smart_unicode
+
+import geojson
+from shapely.geometry import asShape
+
+
+class Serializer(JsonSerializer):
+ def __init__(self, *args, **kwargs):
+ super(Serializer, self).__init__(*args, **kwargs)
+ self.collection = None
+
+ def end_serialization(self):
+ precision = self.options.get('precision')
+ floatrepr = encoder.FLOAT_REPR
+ if precision is not None:
+ # Monkey patch for float precision!
+ encoder.FLOAT_REPR = lambda o: format(o, '.%sf' % precision)

Could we curry() some default encoders instead of this violent monkey patch ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ self.collection = geojson.FeatureCollection(features=self.objects)
+ dump = geojson.dump(self.collection, self.stream)
+ encoder.FLOAT_REPR = floatrepr # Restore
+ return dump
+
+ def _geomfield(self, obj):
+ geomattrs = [field for field in obj._meta.fields if isinstance(field, GeometryField)]
+ if not geomattrs:
+ raise ValueError("No GeometryField found in object")
+ geomattr = geomattrs.pop()
+ if geomattrs:
+ logging.warn(_("More than one GeometryField found in object, used %s" % geomattr.name))
+ geomfield = getattr(obj, geomattr.name)
+ return geomfield
+
+ def _preparegeom(self, geomfield):
+ """ Geometry processing, depending on options """
+ # Optional geometry simplification
+ simplify = self.options.get('simplify')
+ if simplify is not None:
+ geomfield = geomfield.simplify(tolerance=simplify, preserve_topology=True)
+ # Optional geometry reprojection
+ srid = self.options.get('srid')
+ if srid is not None and srid != geomfield.srid:
+ geomfield.transform(srid)
+ return geomfield
+
+ def _properties(self):
+ """ Build properties from object fields """
+ properties = dict(self._current.iteritems())
+ if self.selected_fields is not None:
+ properties = {k:v for k,v in properties.items() if k in self.selected_fields}
+ # Properties are expected to be serializable, force it brutally
+ properties = simplejson.loads(simplejson.dumps(properties, cls=DateTimeAwareJSONEncoder))

There should be a nicer way to rely on base serializer behaviour !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ return properties
+
+ def end_object(self, obj):
+ pk = smart_unicode(obj._get_pk_val(), strings_only=True)
+ geomfield = self._geomfield(obj)
+ self._preparegeom(geomfield)
+
+ # Load Django geojson representation as dict
+ geometry = simplejson.loads(geomfield.geojson)

geojson lib expects a dict, this is a waste, since we dump it eventually

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ properties = self._properties()
+ # Add extra-info for deserializing
+ properties['model'] = smart_unicode(obj._meta)
+ properties['pk'] = pk
+ # Build pure geojson object
+ feature = geojson.Feature(id=pk,
+ properties=properties,
+ geometry=geometry)
+ self.objects.append(feature)
+ self._current = None
+
+ def handle_field(self, obj, field):
+ """
+ If field is of GeometryField than encode otherwise call parent's method
+ """
+ value = field._get_val_from_obj(obj)
+ if isinstance(field, GeometryField):
+ self._current[field.name] = value
+ else:
+ super(Serializer, self).handle_field(obj, field)
+
+
+def Deserializer(stream_or_string, **options):
+ """
+ Deserialize a stream or string of JSON data.
+ """
+ def FeatureToPython(dictobj):
+ properties = dictobj['properties']
+ obj = {
+ "model" : properties.pop("model"),
+ "pk" : dictobj['id'],
+ "fields" : properties
+ }
+ shape = asShape(dictobj['geometry'])
+ obj['geom'] = shape.wkt
+ return obj
+
+ if isinstance(stream_or_string, basestring):
+ stream = StringIO(stream_or_string)
+ else:
+ stream = stream_or_string
+ try:
+ collection = simplejson.load(stream)
+ objects = map(FeatureToPython, collection['features'])
+ for obj in PythonDeserializer(objects, **options):
+ yield obj
+ except GeneratorExit:
+ raise
+ except Exception, e:
+ # Map to deserializer error
+ raise DeserializationError(e)
0 djgeojson/templatetags/__init__.py
No changes.
29 djgeojson/templatetags/geojson_tags.py
@@ -0,0 +1,29 @@
+from django import template
+from django.conf import settings
+from django.utils import simplejson
+from django.contrib.gis.geos import GEOSGeometry
+
+import geojson
+
+from djgeojson.serializers import Serializer
+
+
+register = template.Library()
+
+
+@register.filter
+def geojsonfeature(obj, srid=None):
+ if obj is None or isinstance(obj, basestring):
+ return 'null'
+
+ if srid is None:
+ srid = getattr(settings, 'MAP_SRID', getattr(settings, 'SRID', 4326))
+ geojsonvalue = ''
+ if isinstance(obj, GEOSGeometry):
+ obj.transform(settings.MAP_SRID)

Using srid parameter as expected in commit ee7b859

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ feature = geojson.Feature(geometry=simplejson.loads(obj.geojson))
+ geojsonvalue = geojson.dumps(feature)
+ else:
+ serializer = Serializer()
+ geojsonvalue = serializer.serialize([obj], fields=[], srid=settings.MAP_SRID)
+ return geojsonvalue
54 djgeojson/tests.py
@@ -0,0 +1,54 @@
+from django.test import TestCase
+from django.core import serializers
+from django.contrib.gis.db import models
+
+
+class Route(models.Model):

Use a dedicated test project !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ name = models.CharField(max_length=20)
+ geom = models.LineStringField(spatial_index=False)
+
+ objects = models.Manager()
+
+
+class GeoJsonSerializerTest(TestCase):
+ def test_deserializer(self):
+ # Input text
+ input_geojson = """
+ {"type": "FeatureCollection",
+ "features": [
+ { "type": "Feature",
+ "properties": {"model": "djgeojson.route", "name": "green"},
+ "id": 1,
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [0.0, 0.0],
+ [1.0, 1.0]
+ ]
+ }
+ }
+ ]}"""
+
+ # Deserialize into a list of objects
+ objects = list(serializers.deserialize('geojson', input_geojson))
+
+ # Were three objects deserialized?
+ self.assertEqual(len(objects), 1)
+
+ # Did the objects deserialize correctly?
+ self.assertEqual(objects[0].object.name, "green")
+
+ def test_serializer(self):
+ # Stuff to serialize
+ Route(name='green', geom="LINESTRING (0 0, 1 1)").save()
+ Route(name='blue', geom="LINESTRING (0 0, 1 1)").save()
+ Route(name='red', geom="LINESTRING (0 0, 1 1)").save()
+
+ # Expected output
+ expect_geojson = """{"type": "FeatureCollection", "features": [{"geometry": {"type": "LineString", "coordinates": [[0.0, 0.0], [1.0, 1.0]]}, "type": "Feature", "properties": {"pk": 1, "model": "djgeojson.route", "name": "green"}, "id": 1}, {"geometry": {"type": "LineString", "coordinates": [[0.0, 0.0], [1.0, 1.0]]}, "type": "Feature", "properties": {"pk": 2, "model": "djgeojson.route", "name": "blue"}, "id": 2}, {"geometry": {"type": "LineString", "coordinates": [[0.0, 0.0], [1.0, 1.0]]}, "type": "Feature", "properties": {"pk": 3, "model": "djgeojson.route", "name": "red"}, "id": 3}]}"""
+
+ # Do the serialization
+ actual_geojson = serializers.serialize('geojson', Route.objects.all(), fields=['name'])
+
+ # Did it work?
+ self.assertEqual(actual_geojson, expect_geojson)
42 djgeojson/views.py
@@ -0,0 +1,42 @@
+from django.views.generic import ListView
+from django.utils.decorators import method_decorator
+from django.views.decorators.gzip import gzip_page
+
+from .http import HttpJSONResponse
+from .serializers import Serializer as GeoJSONSerializer
+
+
+class GeoJSONResponseMixin(object):
+ """
+ A mixin that can be used to render a GeoJSON response.
+ """
+ response_class = HttpJSONResponse
+ """ Select fields for properties """
+ fields = []
+ """ Limit float precision """
+ precision = None
+ """ Simplify geometries """
+ simplify = None
+ """ Change projection of geometries """
+ srid = None
+
+ def render_to_response(self, context, **response_kwargs):
+ """
+ Returns a JSON response, transforming 'context' to make the payload.
+ """
+ serializer = GeoJSONSerializer()
+ response = self.response_class(**response_kwargs)
+ serializer.serialize(self.get_queryset(), stream=response,
+ fields=self.fields, simplify=self.simplify,
+ srid=self.srid, precision=self.precision,
+ ensure_ascii=False)
+ return response
+
+
+class GeoJSONLayerView(GeoJSONResponseMixin, ListView):
+ """
+ A generic view to serve a model as a layer.
+ """
+ @method_decorator(gzip_page)

This should be optional or left to subclasses...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ def dispatch(self, *args, **kwargs):
+ return super(GeoJSONLayerView, self).dispatch(*args, **kwargs)
32 setup.py
@@ -0,0 +1,32 @@
+import os
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+setup(
+ name='django-geojson',
+ version='1.0.0',
+ author='Mathieu Leplatre',
+ author_email='mathieu.leplatre@makina-corpus.com',
+ url='https://github.com/makinacorpus/django-geojson',
+ download_url = "http://pypi.python.org/pypi/django-geojson/",
+ description="",
+ long_description=open(os.path.join(here, 'README.rst')).read() + '\n\n' +
+ open(os.path.join(here, 'CHANGES')).read(),
+ license='LPGL, see LICENSE file.',
+ install_requires=[
+ 'geojson == 1.0.1',
+ 'shapely',
+ ],
+ packages=find_packages(),
+ include_package_data = True,
+ zip_safe = False,
+ classifiers = ['Topic :: Utilities',
+ 'Natural Language :: English',
+ 'Operating System :: OS Independent',
+ 'Intended Audience :: Developers',
+ 'Environment :: Web Environment',
+ 'Framework :: Django',
+ 'Development Status :: 5 - Production/Stable',
+ 'Programming Language :: Python :: 2.7'],
+)

0 comments on commit c3cac0c

Please sign in to comment.