Skip to content

Commit

Permalink
Fixed #5472 --Added OpenLayers-based widgets in contrib.gis
Browse files Browse the repository at this point in the history
Largely inspired from django-floppyforms. Designed to not depend
on OpenLayers at code level.
  • Loading branch information
claudep committed May 17, 2013
1 parent d4d1145 commit b16b72d
Show file tree
Hide file tree
Showing 12 changed files with 931 additions and 20 deletions.
13 changes: 12 additions & 1 deletion django/contrib/gis/db/models/fields.py
Expand Up @@ -44,6 +44,7 @@ class GeometryField(Field):


# The OpenGIS Geometry name. # The OpenGIS Geometry name.
geom_type = 'GEOMETRY' geom_type = 'GEOMETRY'
form_class = forms.GeometryField


# Geodetic units. # Geodetic units.
geodetic_units = ('Decimal Degree', 'degree') geodetic_units = ('Decimal Degree', 'degree')
Expand Down Expand Up @@ -201,11 +202,14 @@ def db_type(self, connection):
return connection.ops.geo_db_type(self) return connection.ops.geo_db_type(self)


def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'form_class' : forms.GeometryField, defaults = {'form_class' : self.form_class,
'geom_type' : self.geom_type, 'geom_type' : self.geom_type,
'srid' : self.srid, 'srid' : self.srid,
} }
defaults.update(kwargs) defaults.update(kwargs)
if (self.dim > 2 and not 'widget' in kwargs and
not getattr(defaults['form_class'].widget, 'supports_3d', False)):
defaults['widget'] = forms.Textarea
return super(GeometryField, self).formfield(**defaults) return super(GeometryField, self).formfield(**defaults)


def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
Expand Down Expand Up @@ -267,28 +271,35 @@ def get_placeholder(self, value, connection):
# The OpenGIS Geometry Type Fields # The OpenGIS Geometry Type Fields
class PointField(GeometryField): class PointField(GeometryField):
geom_type = 'POINT' geom_type = 'POINT'
form_class = forms.PointField
description = _("Point") description = _("Point")


class LineStringField(GeometryField): class LineStringField(GeometryField):
geom_type = 'LINESTRING' geom_type = 'LINESTRING'
form_class = forms.LineStringField
description = _("Line string") description = _("Line string")


class PolygonField(GeometryField): class PolygonField(GeometryField):
geom_type = 'POLYGON' geom_type = 'POLYGON'
form_class = forms.PolygonField
description = _("Polygon") description = _("Polygon")


class MultiPointField(GeometryField): class MultiPointField(GeometryField):
geom_type = 'MULTIPOINT' geom_type = 'MULTIPOINT'
form_class = forms.MultiPointField
description = _("Multi-point") description = _("Multi-point")


class MultiLineStringField(GeometryField): class MultiLineStringField(GeometryField):
geom_type = 'MULTILINESTRING' geom_type = 'MULTILINESTRING'
form_class = forms.MultiLineStringField
description = _("Multi-line string") description = _("Multi-line string")


class MultiPolygonField(GeometryField): class MultiPolygonField(GeometryField):
geom_type = 'MULTIPOLYGON' geom_type = 'MULTIPOLYGON'
form_class = forms.MultiPolygonField
description = _("Multi polygon") description = _("Multi polygon")


class GeometryCollectionField(GeometryField): class GeometryCollectionField(GeometryField):
geom_type = 'GEOMETRYCOLLECTION' geom_type = 'GEOMETRYCOLLECTION'
form_class = forms.GeometryCollectionField
description = _("Geometry collection") description = _("Geometry collection")
5 changes: 4 additions & 1 deletion django/contrib/gis/forms/__init__.py
@@ -1,2 +1,5 @@
from django.forms import * from django.forms import *
from django.contrib.gis.forms.fields import GeometryField from .fields import (GeometryField, GeometryCollectionField, PointField,
MultiPointField, LineStringField, MultiLineStringField, PolygonField,
MultiPolygonField)
from .widgets import BaseGeometryWidget, OpenLayersWidget, OSMWidget
35 changes: 33 additions & 2 deletions django/contrib/gis/forms/fields.py
Expand Up @@ -9,6 +9,7 @@
# While this couples the geographic forms to the GEOS library, # While this couples the geographic forms to the GEOS library,
# it decouples from database (by not importing SpatialBackend). # it decouples from database (by not importing SpatialBackend).
from django.contrib.gis.geos import GEOSException, GEOSGeometry, fromstr from django.contrib.gis.geos import GEOSException, GEOSGeometry, fromstr
from .widgets import OpenLayersWidget




class GeometryField(forms.Field): class GeometryField(forms.Field):
Expand All @@ -17,7 +18,8 @@ class GeometryField(forms.Field):
accepted by GEOSGeometry is accepted by this form. By default, accepted by GEOSGeometry is accepted by this form. By default,
this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON. this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON.
""" """
widget = forms.Textarea widget = OpenLayersWidget
geom_type = 'GEOMETRY'


default_error_messages = { default_error_messages = {
'required' : _('No geometry value provided.'), 'required' : _('No geometry value provided.'),
Expand All @@ -31,12 +33,13 @@ def __init__(self, **kwargs):
# Pop out attributes from the database field, or use sensible # Pop out attributes from the database field, or use sensible
# defaults (e.g., allow None). # defaults (e.g., allow None).
self.srid = kwargs.pop('srid', None) self.srid = kwargs.pop('srid', None)
self.geom_type = kwargs.pop('geom_type', 'GEOMETRY') self.geom_type = kwargs.pop('geom_type', self.geom_type)
if 'null' in kwargs: if 'null' in kwargs:
kwargs.pop('null', True) kwargs.pop('null', True)
warnings.warn("Passing 'null' keyword argument to GeometryField is deprecated.", warnings.warn("Passing 'null' keyword argument to GeometryField is deprecated.",
DeprecationWarning, stacklevel=2) DeprecationWarning, stacklevel=2)
super(GeometryField, self).__init__(**kwargs) super(GeometryField, self).__init__(**kwargs)
self.widget.attrs['geom_type'] = self.geom_type


def to_python(self, value): def to_python(self, value):
""" """
Expand Down Expand Up @@ -98,3 +101,31 @@ def _has_changed(self, initial, data):
else: else:
# Check for change of state of existence # Check for change of state of existence
return bool(initial) != bool(data) return bool(initial) != bool(data)


class GeometryCollectionField(GeometryField):
geom_type = 'GEOMETRYCOLLECTION'


class PointField(GeometryField):
geom_type = 'POINT'


class MultiPointField(GeometryField):
geom_type = 'MULTIPOINT'


class LineStringField(GeometryField):
geom_type = 'LINESTRING'


class MultiLineStringField(GeometryField):
geom_type = 'MULTILINESTRING'


class PolygonField(GeometryField):
geom_type = 'POLYGON'


class MultiPolygonField(GeometryField):
geom_type = 'MULTIPOLYGON'
112 changes: 112 additions & 0 deletions django/contrib/gis/forms/widgets.py
@@ -0,0 +1,112 @@
from __future__ import unicode_literals

import logging

from django.conf import settings
from django.contrib.gis import gdal
from django.contrib.gis.geos import GEOSGeometry, GEOSException
from django.forms.widgets import Widget
from django.template import loader
from django.utils import six
from django.utils import translation

logger = logging.getLogger('django.contrib.gis')


class BaseGeometryWidget(Widget):
"""
The base class for rich geometry widgets.
Renders a map using the WKT of the geometry.
"""
geom_type = 'GEOMETRY'
map_srid = 4326
map_width = 600
map_height = 400
display_wkt = False

supports_3d = False
template_name = '' # set on subclasses

def __init__(self, attrs=None):
self.attrs = {}
for key in ('geom_type', 'map_srid', 'map_width', 'map_height', 'display_wkt'):
self.attrs[key] = getattr(self, key)
if attrs:
self.attrs.update(attrs)

def render(self, name, value, attrs=None):
# If a string reaches here (via a validation error on another
# field) then just reconstruct the Geometry.
if isinstance(value, six.string_types):
try:
value = GEOSGeometry(value)
except (GEOSException, ValueError) as err:
logger.error(
"Error creating geometry from value '%s' (%s)" % (
value, err)
)
value = None

wkt = ''
if value:
# Check that srid of value and map match
if value.srid != self.map_srid:
try:
ogr = value.ogr
ogr.transform(self.map_srid)
wkt = ogr.wkt
except gdal.OGRException as err:
logger.error(
"Error transforming geometry from srid '%s' to srid '%s' (%s)" % (
value.srid, self.map_srid, err)
)
else:
wkt = value.wkt

context = self.build_attrs(attrs,
name=name,
module='geodjango_%s' % name.replace('-','_'), # JS-safe
wkt=wkt,
geom_type=gdal.OGRGeomType(self.attrs['geom_type']),
STATIC_URL=settings.STATIC_URL,
LANGUAGE_BIDI=translation.get_language_bidi(),
)
return loader.render_to_string(self.template_name, context)


class OpenLayersWidget(BaseGeometryWidget):
template_name = 'gis/openlayers.html'
class Media:
js = (
'http://openlayers.org/api/2.11/OpenLayers.js',
'gis/js/OLMapWidget.js',
)


class OSMWidget(BaseGeometryWidget):
"""
An OpenLayers/OpenStreetMap-based widget.
"""
template_name = 'gis/openlayers-osm.html'
default_lon = 5
default_lat = 47

class Media:
js = (
'http://openlayers.org/api/2.11/OpenLayers.js',
'http://www.openstreetmap.org/openlayers/OpenStreetMap.js',
'gis/js/OLMapWidget.js',
)

@property
def map_srid(self):
# Use the official spherical mercator projection SRID on versions
# of GDAL that support it; otherwise, fallback to 900913.
if gdal.HAS_GDAL and gdal.GDAL_VERSION >= (1, 7):
return 3857
else:
return 900913

def render(self, name, value, attrs=None):
return super(self, OSMWidget).render(name, value,
{'default_lon': self.default_lon, 'default_lat': self.default_lat})

0 comments on commit b16b72d

Please sign in to comment.