mirror of https://github.com/django/django.git
Fixed #5472 --Added OpenLayers-based widgets in contrib.gis
Largely inspired from django-floppyforms. Designed to not depend on OpenLayers at code level.
This commit is contained in:
parent
d4d1145632
commit
b16b72d415
|
@ -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')
|
||||||
|
@ -201,11 +202,14 @@ class GeometryField(Field):
|
||||||
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):
|
||||||
|
@ -267,28 +271,35 @@ class GeometryField(Field):
|
||||||
# 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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
# 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):
|
||||||
|
@ -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.'),
|
||||||
|
@ -31,12 +33,13 @@ class GeometryField(forms.Field):
|
||||||
# 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):
|
||||||
"""
|
"""
|
||||||
|
@ -98,3 +101,31 @@ class GeometryField(forms.Field):
|
||||||
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'
|
||||||
|
|
|
@ -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,0 +1,371 @@
|
||||||
|
(function() {
|
||||||
|
/**
|
||||||
|
* Transforms an array of features to a single feature with the merged
|
||||||
|
* geometry of geom_type
|
||||||
|
*/
|
||||||
|
OpenLayers.Util.properFeatures = function(features, geom_type) {
|
||||||
|
if (features.constructor == Array) {
|
||||||
|
var geoms = [];
|
||||||
|
for (var i=0; i<features.length; i++) {
|
||||||
|
geoms.push(features[i].geometry);
|
||||||
|
}
|
||||||
|
var geom = new geom_type(geoms);
|
||||||
|
features = new OpenLayers.Feature.Vector(geom);
|
||||||
|
}
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires OpenLayers/Format/WKT.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class: OpenLayers.Format.DjangoWKT
|
||||||
|
* Class for reading Well-Known Text, with workarounds to successfully parse
|
||||||
|
* geometries and collections as returnes by django.contrib.gis.geos.
|
||||||
|
*
|
||||||
|
* Inherits from:
|
||||||
|
* - <OpenLayers.Format.WKT>
|
||||||
|
*/
|
||||||
|
|
||||||
|
OpenLayers.Format.DjangoWKT = OpenLayers.Class(OpenLayers.Format.WKT, {
|
||||||
|
initialize: function(options) {
|
||||||
|
OpenLayers.Format.WKT.prototype.initialize.apply(this, [options]);
|
||||||
|
this.regExes.justComma = /\s*,\s*/;
|
||||||
|
},
|
||||||
|
|
||||||
|
parse: {
|
||||||
|
'point': function(str) {
|
||||||
|
var coords = OpenLayers.String.trim(str).split(this.regExes.spaces);
|
||||||
|
return new OpenLayers.Feature.Vector(
|
||||||
|
new OpenLayers.Geometry.Point(coords[0], coords[1])
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
'multipoint': function(str) {
|
||||||
|
var point;
|
||||||
|
var points = OpenLayers.String.trim(str).split(this.regExes.justComma);
|
||||||
|
var components = [];
|
||||||
|
for(var i=0, len=points.length; i<len; ++i) {
|
||||||
|
point = points[i].replace(this.regExes.trimParens, '$1');
|
||||||
|
components.push(this.parse.point.apply(this, [point]).geometry);
|
||||||
|
}
|
||||||
|
return new OpenLayers.Feature.Vector(
|
||||||
|
new OpenLayers.Geometry.MultiPoint(components)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
'linestring': function(str) {
|
||||||
|
var points = OpenLayers.String.trim(str).split(',');
|
||||||
|
var components = [];
|
||||||
|
for(var i=0, len=points.length; i<len; ++i) {
|
||||||
|
components.push(this.parse.point.apply(this, [points[i]]).geometry);
|
||||||
|
}
|
||||||
|
return new OpenLayers.Feature.Vector(
|
||||||
|
new OpenLayers.Geometry.LineString(components)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
'multilinestring': function(str) {
|
||||||
|
var line;
|
||||||
|
var lines = OpenLayers.String.trim(str).split(this.regExes.parenComma);
|
||||||
|
var components = [];
|
||||||
|
for(var i=0, len=lines.length; i<len; ++i) {
|
||||||
|
line = lines[i].replace(this.regExes.trimParens, '$1');
|
||||||
|
components.push(this.parse.linestring.apply(this, [line]).geometry);
|
||||||
|
}
|
||||||
|
return new OpenLayers.Feature.Vector(
|
||||||
|
new OpenLayers.Geometry.MultiLineString(components)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
'polygon': function(str) {
|
||||||
|
var ring, linestring, linearring;
|
||||||
|
var rings = OpenLayers.String.trim(str).split(this.regExes.parenComma);
|
||||||
|
var components = [];
|
||||||
|
for(var i=0, len=rings.length; i<len; ++i) {
|
||||||
|
ring = rings[i].replace(this.regExes.trimParens, '$1');
|
||||||
|
linestring = this.parse.linestring.apply(this, [ring]).geometry;
|
||||||
|
linearring = new OpenLayers.Geometry.LinearRing(linestring.components);
|
||||||
|
components.push(linearring);
|
||||||
|
}
|
||||||
|
return new OpenLayers.Feature.Vector(
|
||||||
|
new OpenLayers.Geometry.Polygon(components)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
'multipolygon': function(str) {
|
||||||
|
var polygon;
|
||||||
|
var polygons = OpenLayers.String.trim(str).split(this.regExes.doubleParenComma);
|
||||||
|
var components = [];
|
||||||
|
for(var i=0, len=polygons.length; i<len; ++i) {
|
||||||
|
polygon = polygons[i].replace(this.regExes.trimParens, '$1');
|
||||||
|
components.push(this.parse.polygon.apply(this, [polygon]).geometry);
|
||||||
|
}
|
||||||
|
return new OpenLayers.Feature.Vector(
|
||||||
|
new OpenLayers.Geometry.MultiPolygon(components)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
'geometrycollection': function(str) {
|
||||||
|
// separate components of the collection with |
|
||||||
|
str = str.replace(/,\s*([A-Za-z])/g, '|$1');
|
||||||
|
var wktArray = OpenLayers.String.trim(str).split('|');
|
||||||
|
var components = [];
|
||||||
|
for(var i=0, len=wktArray.length; i<len; ++i) {
|
||||||
|
components.push(OpenLayers.Format.WKT.prototype.read.apply(this,[wktArray[i]]));
|
||||||
|
}
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
extractGeometry: function(geometry) {
|
||||||
|
var type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
|
||||||
|
if (!this.extract[type]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.internalProjection && this.externalProjection) {
|
||||||
|
geometry = geometry.clone();
|
||||||
|
geometry.transform(this.internalProjection, this.externalProjection);
|
||||||
|
}
|
||||||
|
var wktType = type == 'collection' ? 'GEOMETRYCOLLECTION' : type.toUpperCase();
|
||||||
|
var data = wktType + '(' + this.extract[type].apply(this, [geometry]) + ')';
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patched write: successfully writes WKT for geometries and
|
||||||
|
* geometrycollections.
|
||||||
|
*/
|
||||||
|
write: function(features) {
|
||||||
|
var collection, geometry, type, data, isCollection;
|
||||||
|
isCollection = features.geometry.CLASS_NAME == "OpenLayers.Geometry.Collection";
|
||||||
|
var pieces = [];
|
||||||
|
if (isCollection) {
|
||||||
|
collection = features.geometry.components;
|
||||||
|
pieces.push('GEOMETRYCOLLECTION(');
|
||||||
|
for (var i=0, len=collection.length; i<len; ++i) {
|
||||||
|
if (i>0) {
|
||||||
|
pieces.push(',');
|
||||||
|
}
|
||||||
|
pieces.push(this.extractGeometry(collection[i]));
|
||||||
|
}
|
||||||
|
pieces.push(')');
|
||||||
|
} else {
|
||||||
|
pieces.push(this.extractGeometry(features.geometry));
|
||||||
|
}
|
||||||
|
return pieces.join('');
|
||||||
|
},
|
||||||
|
|
||||||
|
CLASS_NAME: "OpenLayers.Format.DjangoWKT"
|
||||||
|
});
|
||||||
|
|
||||||
|
function MapWidget(options) {
|
||||||
|
this.map = null;
|
||||||
|
this.controls = null;
|
||||||
|
this.panel = null;
|
||||||
|
this.layers = {};
|
||||||
|
this.wkt_f = new OpenLayers.Format.DjangoWKT();
|
||||||
|
|
||||||
|
// Mapping from OGRGeomType name to OpenLayers.Geometry name
|
||||||
|
if (options['geom_name'] == 'Unknown') options['geom_type'] = OpenLayers.Geometry;
|
||||||
|
else if (options['geom_name'] == 'GeometryCollection') options['geom_type'] = OpenLayers.Geometry.Collection;
|
||||||
|
else options['geom_type'] = eval('OpenLayers.Geometry' + options['geom_name']);
|
||||||
|
|
||||||
|
// Default options
|
||||||
|
this.options = {
|
||||||
|
color: 'ee9900',
|
||||||
|
default_lat: 0,
|
||||||
|
default_lon: 0,
|
||||||
|
default_zoom: 4,
|
||||||
|
is_collection: options['geom_type'] instanceof OpenLayers.Geometry.Collection,
|
||||||
|
layerswitcher: false,
|
||||||
|
map_options: {},
|
||||||
|
map_srid: 4326,
|
||||||
|
modifiable: true,
|
||||||
|
mouse_position: false,
|
||||||
|
opacity: 0.4,
|
||||||
|
point_zoom: 12,
|
||||||
|
scale_text: false,
|
||||||
|
scrollable: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Altering using user-provied options
|
||||||
|
for (var property in options) {
|
||||||
|
if (options.hasOwnProperty(property)) {
|
||||||
|
this.options[property] = options[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map = new OpenLayers.Map(this.options.map_id, this.options.map_options);
|
||||||
|
if (this.options.base_layer) this.layers.base = this.options.base_layer;
|
||||||
|
else this.layers.base = new OpenLayers.Layer.WMS('OpenLayers WMS', 'http://vmap0.tiles.osgeo.org/wms/vmap0', {layers: 'basic'});
|
||||||
|
this.map.addLayer(this.layers.base);
|
||||||
|
|
||||||
|
var defaults_style = {
|
||||||
|
'fillColor': '#' + this.options.color,
|
||||||
|
'fillOpacity': this.options.opacity,
|
||||||
|
'strokeColor': '#' + this.options.color,
|
||||||
|
};
|
||||||
|
if (this.options.geom_name == 'LineString') {
|
||||||
|
defaults_style['strokeWidth'] = 3;
|
||||||
|
}
|
||||||
|
var styleMap = new OpenLayers.StyleMap({'default': OpenLayers.Util.applyDefaults(defaults_style, OpenLayers.Feature.Vector.style['default'])});
|
||||||
|
this.layers.vector = new OpenLayers.Layer.Vector(" " + this.options.name, {styleMap: styleMap});
|
||||||
|
this.map.addLayer(this.layers.vector);
|
||||||
|
wkt = document.getElementById(this.options.id).value;
|
||||||
|
if (wkt) {
|
||||||
|
var feat = OpenLayers.Util.properFeatures(this.read_wkt(wkt), this.options.geom_type);
|
||||||
|
this.write_wkt(feat);
|
||||||
|
if (this.options.is_collection) {
|
||||||
|
for (var i=0; i<this.num_geom; i++) {
|
||||||
|
this.layers.vector.addFeatures([new OpenLayers.Feature.Vector(feat.geometry.components[i].clone())]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.layers.vector.addFeatures([feat]);
|
||||||
|
}
|
||||||
|
this.map.zoomToExtent(feat.geometry.getBounds());
|
||||||
|
if (this.options.geom_name == 'Point') {
|
||||||
|
this.map.zoomTo(this.options.point_zoom);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.map.setCenter(this.defaultCenter(), this.options.default_zoom);
|
||||||
|
}
|
||||||
|
this.layers.vector.events.on({'featuremodified': this.modify_wkt, scope: this});
|
||||||
|
this.layers.vector.events.on({'featureadded': this.add_wkt, scope: this});
|
||||||
|
|
||||||
|
this.getControls(this.layers.vector);
|
||||||
|
this.panel.addControls(this.controls);
|
||||||
|
this.map.addControl(this.panel);
|
||||||
|
this.addSelectControl();
|
||||||
|
|
||||||
|
if (this.options.mouse_position) {
|
||||||
|
this.map.addControl(new OpenLayers.Control.MousePosition());
|
||||||
|
}
|
||||||
|
if (this.options.scale_text) {
|
||||||
|
this.map.addControl(new OpenLayers.Control.Scale());
|
||||||
|
}
|
||||||
|
if (this.options.layerswitcher) {
|
||||||
|
this.map.addControl(new OpenLayers.Control.LayerSwitcher());
|
||||||
|
}
|
||||||
|
if (!this.options.scrollable) {
|
||||||
|
this.map.getControlsByClass('OpenLayers.Control.Navigation')[0].disableZoomWheel();
|
||||||
|
}
|
||||||
|
if (wkt) {
|
||||||
|
if (this.options.modifiable) {
|
||||||
|
this.enableEditing();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.enableDrawing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MapWidget.prototype.get_ewkt = function(feat) {
|
||||||
|
return "SRID=" + this.options.map_srid + ";" + this.wkt_f.write(feat);
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.read_wkt = function(wkt) {
|
||||||
|
var prefix = 'SRID=' + this.options.map_srid + ';'
|
||||||
|
if (wkt.indexOf(prefix) === 0) {
|
||||||
|
wkt = wkt.slice(prefix.length);
|
||||||
|
}
|
||||||
|
return this.wkt_f.read(wkt);
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.write_wkt = function(feat) {
|
||||||
|
feat = OpenLayers.Util.properFeatures(feat, this.options.geom_type);
|
||||||
|
if (this.options.is_collection) {
|
||||||
|
this.num_geom = feat.geometry.components.length;
|
||||||
|
} else {
|
||||||
|
this.num_geom = 1;
|
||||||
|
}
|
||||||
|
document.getElementById(this.options.id).value = this.get_ewkt(feat);
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.add_wkt = function(event) {
|
||||||
|
if (this.options.is_collection) {
|
||||||
|
var feat = new OpenLayers.Feature.Vector(new this.options.geom_type());
|
||||||
|
for (var i=0; i<this.layers.vector.features.length; i++) {
|
||||||
|
feat.geometry.addComponents([this.layers.vector.features[i].geometry]);
|
||||||
|
}
|
||||||
|
this.write_wkt(feat);
|
||||||
|
} else {
|
||||||
|
if (this.layers.vector.features.length > 1) {
|
||||||
|
old_feats = [this.layers.vector.features[0]];
|
||||||
|
this.layers.vector.removeFeatures(old_feats);
|
||||||
|
this.layers.vector.destroyFeatures(old_feats);
|
||||||
|
}
|
||||||
|
this.write_wkt(event.feature);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.modify_wkt = function(event) {
|
||||||
|
if (this.options.is_collection) {
|
||||||
|
if (this.options.geom_name == 'MultiPoint') {
|
||||||
|
this.add_wkt(event);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
var feat = new OpenLayers.Feature.Vector(new this.options.geom_type());
|
||||||
|
for (var i=0; i<this.num_geom; i++) {
|
||||||
|
feat.geometry.addComponents([this.layers.vector.features[i].geometry]);
|
||||||
|
}
|
||||||
|
this.write_wkt(feat);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.write_wkt(event.feature);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.deleteFeatures = function() {
|
||||||
|
this.layers.vector.removeFeatures(this.layers.vector.features);
|
||||||
|
this.layers.vector.destroyFeatures();
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.clearFeatures = function() {
|
||||||
|
this.deleteFeatures();
|
||||||
|
document.getElementById(this.options.id).value = '';
|
||||||
|
this.map.setCenter(this.defaultCenter(), this.options.default_zoom);
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.defaultCenter = function() {
|
||||||
|
var center = new OpenLayers.LonLat(this.options.default_lon, this.options.default_lat);
|
||||||
|
if (this.options.map_srid) {
|
||||||
|
return center.transform(new OpenLayers.Projection("EPSG:4326"), this.map.getProjectionObject());
|
||||||
|
}
|
||||||
|
return center;
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.addSelectControl = function() {
|
||||||
|
var select = new OpenLayers.Control.SelectFeature(this.layers.vector, {'toggle': true, 'clickout': true});
|
||||||
|
this.map.addControl(select);
|
||||||
|
select.activate();
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.enableDrawing = function () {
|
||||||
|
this.map.getControlsByClass('OpenLayers.Control.DrawFeature')[0].activate();
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.enableEditing = function () {
|
||||||
|
this.map.getControlsByClass('OpenLayers.Control.ModifyFeature')[0].activate();
|
||||||
|
};
|
||||||
|
|
||||||
|
MapWidget.prototype.getControls = function(layer) {
|
||||||
|
this.panel = new OpenLayers.Control.Panel({'displayClass': 'olControlEditingToolbar'});
|
||||||
|
this.controls = [new OpenLayers.Control.Navigation()];
|
||||||
|
if (!this.options.modifiable && layer.features.length)
|
||||||
|
return;
|
||||||
|
if (this.options.geom_name == 'LineString' || this.options.geom_name == 'Unknown') {
|
||||||
|
this.controls.push(new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'}));
|
||||||
|
}
|
||||||
|
if (this.options.geom_name == 'Polygon' || this.options.geom_name == 'Unknown') {
|
||||||
|
this.controls.push(new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'}));
|
||||||
|
}
|
||||||
|
if (this.options.geom_name == 'Point' || this.options.geom_name == 'Unknown') {
|
||||||
|
this.controls.push(new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'}));
|
||||||
|
}
|
||||||
|
if (this.options.modifiable) {
|
||||||
|
this.controls.push(new OpenLayers.Control.ModifyFeature(layer, {'displayClass': 'olControlModifyFeature'}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.MapWidget = MapWidget;
|
||||||
|
})();
|
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "gis/openlayers.html" %}
|
||||||
|
{% load l10n %}
|
||||||
|
|
||||||
|
{% block map_options %}var map_options = {
|
||||||
|
maxExtend: new OpenLayers.Bounds(-20037508,-20037508,20037508,20037508),
|
||||||
|
maxResolution: 156543.0339,
|
||||||
|
numZoomLevels: 20,
|
||||||
|
units: 'm'
|
||||||
|
};{% endblock %}
|
||||||
|
|
||||||
|
{% block options %}{{ block.super }}
|
||||||
|
options['scale_text'] = true;
|
||||||
|
options['mouse_position'] = true;
|
||||||
|
options['default_lon'] = {{ default_lon|unlocalize }};
|
||||||
|
options['default_lat'] = {{ default_lat|unlocalize }};
|
||||||
|
options['base_layer'] = new OpenLayers.Layer.OSM.Mapnik("OpenStreetMap (Mapnik)");
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<style type="text/css">{% block map_css %}
|
||||||
|
#{{ id }}_map { width: {{ map_width }}px; height: {{ map_height }}px; }
|
||||||
|
#{{ id }}_map .aligned label { float: inherit; }
|
||||||
|
#{{ id }}_div_map { position: relative; vertical-align: top; float: {{ LANGUAGE_BIDI|yesno:"right,left" }}; }
|
||||||
|
{% if not display_wkt %}#{{ id }} { display: none; }{% endif %}
|
||||||
|
.olControlEditingToolbar .olControlModifyFeatureItemActive {
|
||||||
|
background-image: url("{{ STATIC_URL }}admin/img/gis/move_vertex_on.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.olControlEditingToolbar .olControlModifyFeatureItemInactive {
|
||||||
|
background-image: url("{{ STATIC_URL }}admin/img/gis/move_vertex_off.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}{% endblock %}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="{{ id }}_div_map">
|
||||||
|
<div id="{{ id }}_map"></div>
|
||||||
|
<span class="clear_features"><a href="javascript:{{ module }}.clearFeatures()">Delete all Features</a></span>
|
||||||
|
{% if display_wkt %}<p> WKT debugging window:</p>{% endif %}
|
||||||
|
<textarea id="{{ id }}" class="vWKTField required" cols="150" rows="10" name="{{ name }}">{{ wkt }}</textarea>
|
||||||
|
<script type="text/javascript">
|
||||||
|
{% block map_options %}var map_options = {};{% endblock %}
|
||||||
|
{% block options %}var options = {
|
||||||
|
geom_name: '{{ geom_type }}',
|
||||||
|
id: '{{ id }}',
|
||||||
|
map_id: '{{ id }}_map',
|
||||||
|
map_options: map_options,
|
||||||
|
map_srid: {{ map_srid }},
|
||||||
|
name: '{{ name }}'
|
||||||
|
};
|
||||||
|
{% endblock %}
|
||||||
|
var {{ module }} = new MapWidget(options);
|
||||||
|
</script>
|
||||||
|
</div>
|
|
@ -1,24 +1,25 @@
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.contrib.gis.gdal import HAS_GDAL
|
from django.contrib.gis.gdal import HAS_GDAL
|
||||||
from django.contrib.gis.tests.utils import HAS_SPATIALREFSYS
|
from django.contrib.gis.tests.utils import HAS_SPATIALREFSYS
|
||||||
|
from django.test import SimpleTestCase
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils import unittest
|
from django.utils.unittest import skipUnless
|
||||||
|
|
||||||
|
|
||||||
if HAS_SPATIALREFSYS:
|
if HAS_SPATIALREFSYS:
|
||||||
from django.contrib.gis import forms
|
from django.contrib.gis import forms
|
||||||
from django.contrib.gis.geos import GEOSGeometry
|
from django.contrib.gis.geos import GEOSGeometry
|
||||||
|
|
||||||
@unittest.skipUnless(HAS_GDAL and HAS_SPATIALREFSYS, "GeometryFieldTest needs gdal support and a spatial database")
|
@skipUnless(HAS_GDAL and HAS_SPATIALREFSYS, "GeometryFieldTest needs gdal support and a spatial database")
|
||||||
class GeometryFieldTest(unittest.TestCase):
|
class GeometryFieldTest(SimpleTestCase):
|
||||||
|
|
||||||
def test00_init(self):
|
def test_init(self):
|
||||||
"Testing GeometryField initialization with defaults."
|
"Testing GeometryField initialization with defaults."
|
||||||
fld = forms.GeometryField()
|
fld = forms.GeometryField()
|
||||||
for bad_default in ('blah', 3, 'FoO', None, 0):
|
for bad_default in ('blah', 3, 'FoO', None, 0):
|
||||||
self.assertRaises(ValidationError, fld.clean, bad_default)
|
self.assertRaises(ValidationError, fld.clean, bad_default)
|
||||||
|
|
||||||
def test01_srid(self):
|
def test_srid(self):
|
||||||
"Testing GeometryField with a SRID set."
|
"Testing GeometryField with a SRID set."
|
||||||
# Input that doesn't specify the SRID is assumed to be in the SRID
|
# Input that doesn't specify the SRID is assumed to be in the SRID
|
||||||
# of the input field.
|
# of the input field.
|
||||||
|
@ -34,7 +35,7 @@ class GeometryFieldTest(unittest.TestCase):
|
||||||
cleaned_geom = fld.clean('SRID=4326;POINT (-95.363151 29.763374)')
|
cleaned_geom = fld.clean('SRID=4326;POINT (-95.363151 29.763374)')
|
||||||
self.assertTrue(xform_geom.equals_exact(cleaned_geom, tol))
|
self.assertTrue(xform_geom.equals_exact(cleaned_geom, tol))
|
||||||
|
|
||||||
def test02_null(self):
|
def test_null(self):
|
||||||
"Testing GeometryField's handling of null (None) geometries."
|
"Testing GeometryField's handling of null (None) geometries."
|
||||||
# Form fields, by default, are required (`required=True`)
|
# Form fields, by default, are required (`required=True`)
|
||||||
fld = forms.GeometryField()
|
fld = forms.GeometryField()
|
||||||
|
@ -46,7 +47,7 @@ class GeometryFieldTest(unittest.TestCase):
|
||||||
fld = forms.GeometryField(required=False)
|
fld = forms.GeometryField(required=False)
|
||||||
self.assertIsNone(fld.clean(None))
|
self.assertIsNone(fld.clean(None))
|
||||||
|
|
||||||
def test03_geom_type(self):
|
def test_geom_type(self):
|
||||||
"Testing GeometryField's handling of different geometry types."
|
"Testing GeometryField's handling of different geometry types."
|
||||||
# By default, all geometry types are allowed.
|
# By default, all geometry types are allowed.
|
||||||
fld = forms.GeometryField()
|
fld = forms.GeometryField()
|
||||||
|
@ -60,7 +61,7 @@ class GeometryFieldTest(unittest.TestCase):
|
||||||
# but rejected by `clean`
|
# but rejected by `clean`
|
||||||
self.assertRaises(forms.ValidationError, pnt_fld.clean, 'LINESTRING(0 0, 1 1)')
|
self.assertRaises(forms.ValidationError, pnt_fld.clean, 'LINESTRING(0 0, 1 1)')
|
||||||
|
|
||||||
def test04_to_python(self):
|
def test_to_python(self):
|
||||||
"""
|
"""
|
||||||
Testing to_python returns a correct GEOSGeometry object or
|
Testing to_python returns a correct GEOSGeometry object or
|
||||||
a ValidationError
|
a ValidationError
|
||||||
|
@ -74,13 +75,169 @@ class GeometryFieldTest(unittest.TestCase):
|
||||||
self.assertRaises(forms.ValidationError, fld.to_python, wkt)
|
self.assertRaises(forms.ValidationError, fld.to_python, wkt)
|
||||||
|
|
||||||
|
|
||||||
def suite():
|
@skipUnless(HAS_GDAL and HAS_SPATIALREFSYS,
|
||||||
s = unittest.TestSuite()
|
"SpecializedFieldTest needs gdal support and a spatial database")
|
||||||
s.addTest(unittest.makeSuite(GeometryFieldTest))
|
class SpecializedFieldTest(SimpleTestCase):
|
||||||
return s
|
def setUp(self):
|
||||||
|
self.geometries = {
|
||||||
|
'point': GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
|
||||||
|
'multipoint': GEOSGeometry("SRID=4326;MULTIPOINT("
|
||||||
|
"(13.18634033203125 14.504356384277344),"
|
||||||
|
"(13.207969665527 14.490966796875),"
|
||||||
|
"(13.177070617675 14.454917907714))"),
|
||||||
|
'linestring': GEOSGeometry("SRID=4326;LINESTRING("
|
||||||
|
"-8.26171875 -0.52734375,"
|
||||||
|
"-7.734375 4.21875,"
|
||||||
|
"6.85546875 3.779296875,"
|
||||||
|
"5.44921875 -3.515625)"),
|
||||||
|
'multilinestring': GEOSGeometry("SRID=4326;MULTILINESTRING("
|
||||||
|
"(-16.435546875 -2.98828125,"
|
||||||
|
"-17.2265625 2.98828125,"
|
||||||
|
"-0.703125 3.515625,"
|
||||||
|
"-1.494140625 -3.33984375),"
|
||||||
|
"(-8.0859375 -5.9765625,"
|
||||||
|
"8.525390625 -8.7890625,"
|
||||||
|
"12.392578125 -0.87890625,"
|
||||||
|
"10.01953125 7.646484375))"),
|
||||||
|
'polygon': GEOSGeometry("SRID=4326;POLYGON("
|
||||||
|
"(-1.669921875 6.240234375,"
|
||||||
|
"-3.8671875 -0.615234375,"
|
||||||
|
"5.9765625 -3.955078125,"
|
||||||
|
"18.193359375 3.955078125,"
|
||||||
|
"9.84375 9.4921875,"
|
||||||
|
"-1.669921875 6.240234375))"),
|
||||||
|
'multipolygon': GEOSGeometry("SRID=4326;MULTIPOLYGON("
|
||||||
|
"((-17.578125 13.095703125,"
|
||||||
|
"-17.2265625 10.8984375,"
|
||||||
|
"-13.974609375 10.1953125,"
|
||||||
|
"-13.359375 12.744140625,"
|
||||||
|
"-15.732421875 13.7109375,"
|
||||||
|
"-17.578125 13.095703125)),"
|
||||||
|
"((-8.525390625 5.537109375,"
|
||||||
|
"-8.876953125 2.548828125,"
|
||||||
|
"-5.888671875 1.93359375,"
|
||||||
|
"-5.09765625 4.21875,"
|
||||||
|
"-6.064453125 6.240234375,"
|
||||||
|
"-8.525390625 5.537109375)))"),
|
||||||
|
'geometrycollection': GEOSGeometry("SRID=4326;GEOMETRYCOLLECTION("
|
||||||
|
"POINT(5.625 -0.263671875),"
|
||||||
|
"POINT(6.767578125 -3.603515625),"
|
||||||
|
"POINT(8.525390625 0.087890625),"
|
||||||
|
"POINT(8.0859375 -2.13134765625),"
|
||||||
|
"LINESTRING("
|
||||||
|
"6.273193359375 -1.175537109375,"
|
||||||
|
"5.77880859375 -1.812744140625,"
|
||||||
|
"7.27294921875 -2.230224609375,"
|
||||||
|
"7.657470703125 -1.25244140625))"),
|
||||||
|
}
|
||||||
|
|
||||||
def run(verbosity=2):
|
def assertMapWidget(self, form_instance):
|
||||||
unittest.TextTestRunner(verbosity=verbosity).run(suite())
|
"""
|
||||||
|
Make sure the MapWidget js is passed in the form media and a MapWidget
|
||||||
|
is actually created
|
||||||
|
"""
|
||||||
|
self.assertTrue(form_instance.is_valid())
|
||||||
|
rendered = form_instance.as_p()
|
||||||
|
self.assertIn('new MapWidget(options);', rendered)
|
||||||
|
self.assertIn('gis/js/OLMapWidget.js', str(form_instance.media))
|
||||||
|
|
||||||
if __name__=="__main__":
|
def assertTextarea(self, geom, rendered):
|
||||||
run()
|
"""Makes sure the wkt and a textarea are in the content"""
|
||||||
|
|
||||||
|
self.assertIn('<textarea ', rendered)
|
||||||
|
self.assertIn('required', rendered)
|
||||||
|
self.assertIn(geom.wkt, rendered)
|
||||||
|
|
||||||
|
def test_pointfield(self):
|
||||||
|
class PointForm(forms.Form):
|
||||||
|
p = forms.PointField()
|
||||||
|
|
||||||
|
geom = self.geometries['point']
|
||||||
|
form = PointForm(data={'p': geom})
|
||||||
|
self.assertTextarea(geom, form.as_p())
|
||||||
|
self.assertMapWidget(form)
|
||||||
|
self.assertFalse(PointForm().is_valid())
|
||||||
|
invalid = PointForm(data={'p': 'some invalid geom'})
|
||||||
|
self.assertFalse(invalid.is_valid())
|
||||||
|
self.assertTrue('Invalid geometry value' in str(invalid.errors))
|
||||||
|
|
||||||
|
for invalid in [geom for key, geom in self.geometries.items() if key!='point']:
|
||||||
|
self.assertFalse(PointForm(data={'p': invalid.wkt}).is_valid())
|
||||||
|
|
||||||
|
def test_multipointfield(self):
|
||||||
|
class PointForm(forms.Form):
|
||||||
|
p = forms.MultiPointField()
|
||||||
|
|
||||||
|
geom = self.geometries['multipoint']
|
||||||
|
form = PointForm(data={'p': geom})
|
||||||
|
self.assertTextarea(geom, form.as_p())
|
||||||
|
self.assertMapWidget(form)
|
||||||
|
self.assertFalse(PointForm().is_valid())
|
||||||
|
|
||||||
|
for invalid in [geom for key, geom in self.geometries.items() if key!='multipoint']:
|
||||||
|
self.assertFalse(PointForm(data={'p': invalid.wkt}).is_valid())
|
||||||
|
|
||||||
|
def test_linestringfield(self):
|
||||||
|
class LineStringForm(forms.Form):
|
||||||
|
l = forms.LineStringField()
|
||||||
|
|
||||||
|
geom = self.geometries['linestring']
|
||||||
|
form = LineStringForm(data={'l': geom})
|
||||||
|
self.assertTextarea(geom, form.as_p())
|
||||||
|
self.assertMapWidget(form)
|
||||||
|
self.assertFalse(LineStringForm().is_valid())
|
||||||
|
|
||||||
|
for invalid in [geom for key, geom in self.geometries.items() if key!='linestring']:
|
||||||
|
self.assertFalse(LineStringForm(data={'p': invalid.wkt}).is_valid())
|
||||||
|
|
||||||
|
def test_multilinestringfield(self):
|
||||||
|
class LineStringForm(forms.Form):
|
||||||
|
l = forms.MultiLineStringField()
|
||||||
|
|
||||||
|
geom = self.geometries['multilinestring']
|
||||||
|
form = LineStringForm(data={'l': geom})
|
||||||
|
self.assertTextarea(geom, form.as_p())
|
||||||
|
self.assertMapWidget(form)
|
||||||
|
self.assertFalse(LineStringForm().is_valid())
|
||||||
|
|
||||||
|
for invalid in [geom for key, geom in self.geometries.items() if key!='multilinestring']:
|
||||||
|
self.assertFalse(LineStringForm(data={'p': invalid.wkt}).is_valid())
|
||||||
|
|
||||||
|
def test_polygonfield(self):
|
||||||
|
class PolygonForm(forms.Form):
|
||||||
|
p = forms.PolygonField()
|
||||||
|
|
||||||
|
geom = self.geometries['polygon']
|
||||||
|
form = PolygonForm(data={'p': geom})
|
||||||
|
self.assertTextarea(geom, form.as_p())
|
||||||
|
self.assertMapWidget(form)
|
||||||
|
self.assertFalse(PolygonForm().is_valid())
|
||||||
|
|
||||||
|
for invalid in [geom for key, geom in self.geometries.items() if key!='polygon']:
|
||||||
|
self.assertFalse(PolygonForm(data={'p': invalid.wkt}).is_valid())
|
||||||
|
|
||||||
|
def test_multipolygonfield(self):
|
||||||
|
class PolygonForm(forms.Form):
|
||||||
|
p = forms.MultiPolygonField()
|
||||||
|
|
||||||
|
geom = self.geometries['multipolygon']
|
||||||
|
form = PolygonForm(data={'p': geom})
|
||||||
|
self.assertTextarea(geom, form.as_p())
|
||||||
|
self.assertMapWidget(form)
|
||||||
|
self.assertFalse(PolygonForm().is_valid())
|
||||||
|
|
||||||
|
for invalid in [geom for key, geom in self.geometries.items() if key!='multipolygon']:
|
||||||
|
self.assertFalse(PolygonForm(data={'p': invalid.wkt}).is_valid())
|
||||||
|
|
||||||
|
def test_geometrycollectionfield(self):
|
||||||
|
class GeometryForm(forms.Form):
|
||||||
|
g = forms.GeometryCollectionField()
|
||||||
|
|
||||||
|
geom = self.geometries['geometrycollection']
|
||||||
|
form = GeometryForm(data={'g': geom})
|
||||||
|
self.assertTextarea(geom, form.as_p())
|
||||||
|
self.assertMapWidget(form)
|
||||||
|
self.assertFalse(GeometryForm().is_valid())
|
||||||
|
|
||||||
|
for invalid in [geom for key, geom in self.geometries.items() if key!='geometrycollection']:
|
||||||
|
self.assertFalse(GeometryForm(data={'g': invalid.wkt}).is_valid())
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
.. _ref-gis-forms-api:
|
||||||
|
|
||||||
|
===================
|
||||||
|
GeoDjango Forms API
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. module:: django.contrib.gis.forms
|
||||||
|
:synopsis: GeoDjango forms API.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
GeoDjango provides some specialized form fields and widgets in order to visually
|
||||||
|
display and edit geolocalized data on a map. By default, they use
|
||||||
|
`OpenLayers`_-powered maps, with a base WMS layer provided by `Metacarta`_.
|
||||||
|
|
||||||
|
.. _OpenLayers: http://openlayers.org/
|
||||||
|
.. _Metacarta: http://metacarta.com/
|
||||||
|
|
||||||
|
Field arguments
|
||||||
|
===============
|
||||||
|
In addition to the regular :ref:`form field arguments <core-field-arguments>`,
|
||||||
|
GeoDjango form fields take the following optional arguments.
|
||||||
|
|
||||||
|
``srid``
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
.. attribute:: Field.srid
|
||||||
|
|
||||||
|
This is the SRID code that the field value should be transformed to. For
|
||||||
|
example, if the map widget SRID is different from the SRID more generally
|
||||||
|
used by your application or database, the field will automatically convert
|
||||||
|
input values into that SRID.
|
||||||
|
|
||||||
|
``geom_type``
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attribute:: Field.geom_type
|
||||||
|
|
||||||
|
You generally shouldn't have to set or change that attribute which should
|
||||||
|
be setup depending on the field class. It matches the OpenGIS standard
|
||||||
|
geometry name.
|
||||||
|
|
||||||
|
Form field classes
|
||||||
|
==================
|
||||||
|
|
||||||
|
``GeometryField``
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: GeometryField
|
||||||
|
|
||||||
|
``PointField``
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: PointField
|
||||||
|
|
||||||
|
``LineStringField``
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: LineStringField
|
||||||
|
|
||||||
|
``PolygonField``
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: PolygonField
|
||||||
|
|
||||||
|
``MultiPointField``
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: MultiPointField
|
||||||
|
|
||||||
|
``MultiLineStringField``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: MultiLineStringField
|
||||||
|
|
||||||
|
``MultiPolygonField``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: MultiPolygonField
|
||||||
|
|
||||||
|
``GeometryCollectionField``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: GeometryCollectionField
|
||||||
|
|
||||||
|
Form widgets
|
||||||
|
============
|
||||||
|
|
||||||
|
.. module:: django.contrib.gis.widgets
|
||||||
|
:synopsis: GeoDjango widgets API.
|
||||||
|
|
||||||
|
GeoDjango form widgets allow you to display and edit geographic data on a
|
||||||
|
visual map.
|
||||||
|
Note that none of the currently available widgets supports 3D geometries, hence
|
||||||
|
geometry fields will fallback using a simple ``Textarea`` widget for such data.
|
||||||
|
|
||||||
|
Widget attributes
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
GeoDjango widgets are template-based, so their attributes are mostly different
|
||||||
|
from other Django widget attributes.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: BaseGeometryWidget.geom_type
|
||||||
|
|
||||||
|
The OpenGIS geometry type, generally set by the form field.
|
||||||
|
|
||||||
|
.. attribute:: BaseGeometryWidget.map_height
|
||||||
|
.. attribute:: BaseGeometryWidget.map_width
|
||||||
|
|
||||||
|
Height and width of the widget map (default is 400x600).
|
||||||
|
|
||||||
|
.. attribute:: BaseGeometryWidget.map_srid
|
||||||
|
|
||||||
|
SRID code used by the map (default is 4326).
|
||||||
|
|
||||||
|
.. attribute:: BaseGeometryWidget.display_wkt
|
||||||
|
|
||||||
|
Boolean value specifying if a textarea input showing the WKT representation
|
||||||
|
of the current geometry is visible, mainly for debugging purposes (default
|
||||||
|
is ``False``).
|
||||||
|
|
||||||
|
.. attribute:: BaseGeometryWidget.supports_3d
|
||||||
|
|
||||||
|
Indicates if the widget supports edition of 3D data (default is ``False``).
|
||||||
|
|
||||||
|
.. attribute:: BaseGeometryWidget.template_name
|
||||||
|
|
||||||
|
The template used to render the map widget.
|
||||||
|
|
||||||
|
You can pass widget attributes in the same manner that for any other Django
|
||||||
|
widget. For example::
|
||||||
|
|
||||||
|
from django.contrib.gis import forms
|
||||||
|
|
||||||
|
class MyGeoForm(forms.Form):
|
||||||
|
point = forms.PointField(widget=
|
||||||
|
forms.OSMWidget(attrs={'map_width': 800, 'map_height': 500}))
|
||||||
|
|
||||||
|
Widget classes
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
``BaseGeometryWidget``
|
||||||
|
|
||||||
|
.. class:: BaseGeometryWidget
|
||||||
|
|
||||||
|
This is an abstract base widget containing the logic needed by subclasses.
|
||||||
|
You cannot directly use this widget for a geometry field.
|
||||||
|
Note that the rendering of GeoDjango widgets is based on a template,
|
||||||
|
identified by the :attr:`template_name` class attribute.
|
||||||
|
|
||||||
|
``OpenLayersWidget``
|
||||||
|
|
||||||
|
.. class:: OpenLayersWidget
|
||||||
|
|
||||||
|
This is the default widget used by all GeoDjango form fields.
|
||||||
|
``template_name`` is ``gis/openlayers.html``.
|
||||||
|
|
||||||
|
``OSMWidget``
|
||||||
|
|
||||||
|
.. class:: OSMWidget
|
||||||
|
|
||||||
|
This widget uses an OpenStreetMap base layer (Mapnik) to display geographic
|
||||||
|
objects on.
|
||||||
|
``template_name`` is ``gis/openlayers-osm.html``.
|
|
@ -18,6 +18,7 @@ of spatially enabled data.
|
||||||
install/index
|
install/index
|
||||||
model-api
|
model-api
|
||||||
db-api
|
db-api
|
||||||
|
forms-api
|
||||||
geoquerysets
|
geoquerysets
|
||||||
measure
|
measure
|
||||||
geos
|
geos
|
||||||
|
|
|
@ -30,6 +30,8 @@ exception or returns the clean value::
|
||||||
...
|
...
|
||||||
ValidationError: [u'Enter a valid email address.']
|
ValidationError: [u'Enter a valid email address.']
|
||||||
|
|
||||||
|
.. _core-field-arguments:
|
||||||
|
|
||||||
Core field arguments
|
Core field arguments
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,13 @@ Django 1.6 adds support for savepoints in SQLite, with some :ref:`limitations
|
||||||
A new :class:`django.db.models.BinaryField` model field allows to store raw
|
A new :class:`django.db.models.BinaryField` model field allows to store raw
|
||||||
binary data in the database.
|
binary data in the database.
|
||||||
|
|
||||||
|
GeoDjango form widgets
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
GeoDjango now provides :ref:`form fields and widgets <ref-gis-forms-api>` for
|
||||||
|
its geo-specialized fields. They are OpenLayers-based by default, but they can
|
||||||
|
be customized to use any other JS framework.
|
||||||
|
|
||||||
Minor features
|
Minor features
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue