From 2995f4e50947df269efc785fc1136e6e9db5e415 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 19 Jul 2008 14:17:24 +0000 Subject: [PATCH] gis: Added the geographic-enabled forms and admin modules. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7980 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin/media/img/gis/move_vertex_off.png | Bin 0 -> 711 bytes .../admin/media/img/gis/move_vertex_on.png | Bin 0 -> 506 bytes django/contrib/gis/admin/__init__.py | 9 + django/contrib/gis/admin/options.py | 128 ++++++++++++++ django/contrib/gis/admin/sites.py | 37 +++++ django/contrib/gis/admin/widgets.py | 92 ++++++++++ .../contrib/gis/db/models/fields/__init__.py | 11 +- django/contrib/gis/forms/__init__.py | 1 + django/contrib/gis/forms/fields.py | 37 +++++ .../gis/templates/gis/admin/openlayers.html | 37 +++++ .../gis/templates/gis/admin/openlayers.js | 157 ++++++++++++++++++ .../contrib/gis/templates/gis/admin/osm.html | 2 + django/contrib/gis/templates/gis/admin/osm.js | 2 + 13 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 django/contrib/admin/media/img/gis/move_vertex_off.png create mode 100644 django/contrib/admin/media/img/gis/move_vertex_on.png create mode 100644 django/contrib/gis/admin/__init__.py create mode 100644 django/contrib/gis/admin/options.py create mode 100644 django/contrib/gis/admin/sites.py create mode 100644 django/contrib/gis/admin/widgets.py create mode 100644 django/contrib/gis/forms/__init__.py create mode 100644 django/contrib/gis/forms/fields.py create mode 100644 django/contrib/gis/templates/gis/admin/openlayers.html create mode 100644 django/contrib/gis/templates/gis/admin/openlayers.js create mode 100644 django/contrib/gis/templates/gis/admin/osm.html create mode 100644 django/contrib/gis/templates/gis/admin/osm.js diff --git a/django/contrib/admin/media/img/gis/move_vertex_off.png b/django/contrib/admin/media/img/gis/move_vertex_off.png new file mode 100644 index 0000000000000000000000000000000000000000..296b2e29c970fca4222db7cb86e91deb56c5a7d6 GIT binary patch literal 711 zcmV;&0yzDNP)z~BMB}|@Q2`r zz!0M(GL*z*R@l?k*F|^NbkFEGAeD6Lr>o96_tdQ_$ug(M$DcUs_2^&rxr;JCKhN^= zGOMd=ghxm3IRAD|6i2K*dHVn8s@NajIqUTR2v1HTb!>_nqp27>`XK0!QyAbG8d0CA^F9Cx|8`Ux^7w#1hTk>)Z> z(jcFpXfa(;R1{={7MV5+3lFf?Vy&HYrCcdFs;KyOm=EBs5ygXBYq_}i>Vb?getdik z1axF^S%p;#RdeO?k9{10j>wELh)9l6DFO8@^|iIO1~iS#7=yKzIPTKvJS0t1(27OX zED#B9m7S%S$g~L?jj;#{HVN{sy_`*bwOnuSu)F(;!=n#WF@q^-d?WC=txYt`YI`}d z2t^e-aTlPLIkSRK-WXXWf($fM@8R|K&W*wM-t4jdY`u~wQq3Z~a_`|Sm6w7`<7)(N zm;x}jwK+A4Y2bAdf6y|d;Bv#1Ml-w-cwE`LQsme{%Sn7BN%F@irYVn1n{=2=V1=U# zEAT(0AgQYR^5Xdfcsfi#;r#0t!86r@QjICV%6p{_bOc97ED*c7B?{Ut#9L~0h(`w!0soRBa=!hZ`i3C(tMK-$w zq0zX*pg%xHYGQBoG7^Fp?f}pI6#xKNE*G6GrH2hC8gPGHzny?LP4i#C0l+j(@bfMu za~&je9lU;SE=0n!TuLi4l;uYlmIlMpLJ2Es7&hpF|OoMDi!f?u@=39aU~C_P=#aL^dh_Y z^LGv_IP*V9D>BBFd}#2i;VJgl&*=_EHTCD=-<4BqAQh@q7p&k5eY7C(@7f)D8DQ$7 zAQv>{)BVrv!0oKw@zG51AmyF|Q4~RQ;VNUzxj#fv1Xr)$IQmdWMruYncy8a%peRXj wVTV2;1d|B?Ap|@JEC~W)u~^{H-_Gvh3u9NM*1xcED*ylh07*qoM6N<$g2#E+6951J literal 0 HcmV?d00001 diff --git a/django/contrib/gis/admin/__init__.py b/django/contrib/gis/admin/__init__.py new file mode 100644 index 0000000000..1d80396165 --- /dev/null +++ b/django/contrib/gis/admin/__init__.py @@ -0,0 +1,9 @@ +from django.contrib.gis.admin.options import GeoModelAdmin +from django.contrib.gis.admin.sites import GeoAdminSite, site +from django.contrib.gis.admin.widgets import OpenLayersWidget + +try: + from django.contrib.gis.admin.options import OSMGeoAdmin + HAS_OSM = True +except ImportError: + HAS_OSM = False diff --git a/django/contrib/gis/admin/options.py b/django/contrib/gis/admin/options.py new file mode 100644 index 0000000000..71fb87bf09 --- /dev/null +++ b/django/contrib/gis/admin/options.py @@ -0,0 +1,128 @@ +from django.conf import settings +from django.contrib.admin import ModelAdmin +from django.contrib.gis.admin.widgets import OpenLayersWidget +from django.contrib.gis.gdal import OGRGeomType +from django.contrib.gis.db import models + +class GeoModelAdmin(ModelAdmin): + """ + The administration options class for Geographic models. Map settings + may be overloaded from their defaults to create custom maps. + """ + # The default map settings that may be overloaded -- still subject + # to API changes. + default_lon = 0 + default_lat = 0 + default_zoom = 4 + display_wkt = False + display_srid = False + extra_js = [] + num_zoom = 18 + max_zoom = False + min_zoom = False + units = False + max_resolution = False + max_extent = False + modifiable = True + mouse_position = True + scale_text = True + layerswitcher = True + scrollable = True + admin_media_prefix = settings.ADMIN_MEDIA_PREFIX + map_width = 600 + map_height = 400 + map_srid = 4326 + map_template = 'gis/admin/openlayers.html' + openlayers_url = 'http://openlayers.org/api/2.6/OpenLayers.js' + wms_url = 'http://labs.metacarta.com/wms/vmap0' + wms_layer = 'basic' + wms_name = 'OpenLayers WMS' + debug = False + widget = OpenLayersWidget + + def _media(self): + "Injects OpenLayers JavaScript into the admin." + media = super(GeoModelAdmin, self)._media() + media.add_js([self.openlayers_url]) + media.add_js(self.extra_js) + return media + media = property(_media) + + def formfield_for_dbfield(self, db_field, **kwargs): + """ + Overloaded from ModelAdmin so that an OpenLayersWidget is used + for viewing/editing GeometryFields. + """ + if isinstance(db_field, models.GeometryField): + # Setting the widget with the newly defined widget. + kwargs['widget'] = self.get_map_widget(db_field) + return db_field.formfield(**kwargs) + else: + return super(GeoModelAdmin, self).formfield_for_dbfield(db_field, **kwargs) + + def get_map_widget(self, db_field): + """ + Returns a subclass of the OpenLayersWidget (or whatever was specified + in the `widget` attribute) using the settings from the attributes set + in this class. + """ + is_collection = db_field._geom in ('MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON', 'GEOMETRYCOLLECTION') + if is_collection: + if db_field._geom == 'GEOMETRYCOLLECTION': collection_type = 'Any' + else: collection_type = OGRGeomType(db_field._geom.replace('MULTI', '')) + else: + collection_type = 'None' + + class OLMap(self.widget): + template = self.map_template + geom_type = db_field._geom + params = {'admin_media_prefix' : self.admin_media_prefix, + 'default_lon' : self.default_lon, + 'default_lat' : self.default_lat, + 'default_zoom' : self.default_zoom, + 'display_wkt' : self.debug or self.display_wkt, + 'geom_type' : OGRGeomType(db_field._geom), + 'field_name' : db_field.name, + 'is_collection' : is_collection, + 'scrollable' : self.scrollable, + 'layerswitcher' : self.layerswitcher, + 'collection_type' : collection_type, + 'is_linestring' : db_field._geom in ('LINESTRING', 'MULTILINESTRING'), + 'is_polygon' : db_field._geom in ('POLYGON', 'MULTIPOLYGON'), + 'is_point' : db_field._geom in ('POINT', 'MULTIPOINT'), + 'num_zoom' : self.num_zoom, + 'max_zoom' : self.max_zoom, + 'min_zoom' : self.min_zoom, + 'units' : self.units, #likely shoud get from object + 'max_resolution' : self.max_resolution, + 'max_extent' : self.max_extent, + 'modifiable' : self.modifiable, + 'mouse_position' : self.mouse_position, + 'scale_text' : self.scale_text, + 'map_width' : self.map_width, + 'map_height' : self.map_height, + 'srid' : self.map_srid, + 'display_srid' : self.display_srid, + 'wms_url' : self.wms_url, + 'wms_layer' : self.wms_layer, + 'wms_name' : self.wms_name, + 'debug' : self.debug, + } + return OLMap + +# Using the Beta OSM in the admin requires the following: +# (1) The Google Maps Mercator projection needs to be added +# to your `spatial_ref_sys` table. You'll need at least GDAL 1.5: +# >>> from django.contrib.gis.gdal import SpatialReference +# >>> from django.contrib.gis.utils import add_postgis_srs +# >>> add_postgis_srs(SpatialReference(900913)) # Adding the Google Projection +from django.contrib.gis import gdal +if gdal.HAS_GDAL: + class OSMGeoAdmin(GeoModelAdmin): + map_template = 'gis/admin/osm.html' + extra_js = ['http://openstreetmap.org/openlayers/OpenStreetMap.js'] + num_zoom = 20 + map_srid = 900913 + max_extent = '-20037508,-20037508,20037508,20037508' + max_resolution = 156543.0339 + units = 'm' diff --git a/django/contrib/gis/admin/sites.py b/django/contrib/gis/admin/sites.py new file mode 100644 index 0000000000..56821d9fb5 --- /dev/null +++ b/django/contrib/gis/admin/sites.py @@ -0,0 +1,37 @@ +from django.contrib.admin import sites +from django.contrib.gis.admin.options import GeoModelAdmin +from django.db.models.loading import get_apps + +class GeoAdminSite(sites.AdminSite): + """ + The GeoAdminSite is overloaded from the AdminSite to provide facilities + for editing geographic fields (using the GeoModelAdmin for the options + class instead of ModelAdmin). + """ + def register(self, model_or_iterable, admin_class=None, **options): + "Overloaded register method that uses GeoModelAdmin." + admin_class = admin_class or GeoModelAdmin + try: + return super(GeoAdminSite, self).register(model_or_iterable, admin_class, **options) + except sites.AlreadyRegistered: + # Unlike the default behavior in newforms-admin we won't + # raise this exception. + pass + +# `site` is an instance of GeoAdminSite +site = GeoAdminSite() + +# Re-registering models that appear normally in AdminSite with the +# GeoAdminSite (if the user has these installed). +APPS = get_apps() + +# Registering the `auth` Group & User models. +from django.contrib.auth import models, admin +if models in APPS: + site.register(models.Group, admin.GroupAdmin) + site.register(models.User, admin.UserAdmin) + +# Registering the `sites` Site model. +from django.contrib.sites import models, admin +if models in APPS: + site.register(models.Site, admin.SiteAdmin) diff --git a/django/contrib/gis/admin/widgets.py b/django/contrib/gis/admin/widgets.py new file mode 100644 index 0000000000..27abc8f59b --- /dev/null +++ b/django/contrib/gis/admin/widgets.py @@ -0,0 +1,92 @@ +from django.contrib.gis.gdal import OGRException +from django.contrib.gis.geos import GEOSGeometry, GEOSException +from django.forms.widgets import Textarea +from django.template.loader import render_to_string + +class OpenLayersWidget(Textarea): + """ + Renders an OpenLayers map using the WKT of the geometry. + """ + def render(self, name, value, attrs=None): + # Update the template parameters with any attributes passed in. + if attrs: self.params.update(attrs) + + # Defaulting the WKT value to a blank string -- this + # will be tested in the JavaScript and the appropriate + # interfaace will be constructed. + self.params['wkt'] = '' + + # If a string reaches here (via a validation error on another + # field) then just reconstruct the Geometry. + if isinstance(value, basestring): + try: + value = GEOSGeometry(value) + except (GEOSException, ValueError): + value = None + + if value and value.geom_type.upper() != self.geom_type: + value = None + + # Constructing the dictionary of the map options. + self.params['map_options'] = self.map_options() + + # Constructing the JavaScript module name using the ID of + # the GeometryField (passed in via the `attrs` keyword). + self.params['module'] = 'geodjango_%s' % self.params['field_name'] + + if value: + # Transforming the geometry to the projection used on the + # OpenLayers map. + srid = self.params['srid'] + if value.srid != srid: + try: + value.transform(srid) + wkt = value.wkt + except OGRException: + wkt = '' + else: + wkt = value.wkt + + # Setting the parameter WKT with that of the transformed + # geometry. + self.params['wkt'] = wkt + + return render_to_string(self.template, self.params) + + def map_options(self): + "Builds the map options hash for the OpenLayers template." + + # JavaScript construction utilities for the Bounds and Projection. + def ol_bounds(extent): + return 'new OpenLayers.Bounds(%s)' % str(extent) + def ol_projection(srid): + return 'new OpenLayers.Projection("EPSG:%s")' % srid + + # An array of the parameter name, the name of their OpenLayers + # counterpart, and the type of variable they are. + map_types = [('srid', 'projection', 'srid'), + ('display_srid', 'displayProjection', 'srid'), + ('units', 'units', str), + ('max_resolution', 'maxResolution', float), + ('max_extent', 'maxExtent', 'bounds'), + ('num_zoom', 'numZoomLevels', int), + ('max_zoom', 'maxZoomLevels', int), + ('min_zoom', 'minZoomLevel', int), + ] + + # Building the map options hash. + map_options = {} + for param_name, js_name, option_type in map_types: + if self.params.get(param_name, False): + if option_type == 'srid': + value = ol_projection(self.params[param_name]) + elif option_type == 'bounds': + value = ol_bounds(self.params[param_name]) + elif option_type in (float, int): + value = self.params[param_name] + elif option_type in (str,): + value = '"%s"' % self.params[param_name] + else: + raise TypeError + map_options[js_name] = value + return map_options diff --git a/django/contrib/gis/db/models/fields/__init__.py b/django/contrib/gis/db/models/fields/__init__.py index 7acdc6d2f9..d32f1b118e 100644 --- a/django/contrib/gis/db/models/fields/__init__.py +++ b/django/contrib/gis/db/models/fields/__init__.py @@ -1,3 +1,4 @@ +from django.contrib.gis import forms from django.db import connection # Getting the SpatialBackend container and the geographic quoting method. from django.contrib.gis.db.backend import SpatialBackend, gqn @@ -111,7 +112,7 @@ class GeometryField(SpatialBackend.Field): except SpatialBackend.GeometryException: raise ValueError('Could not create geometry from lookup value: %s' % str(value)) else: - raise TypeError('Cannot use parameter of `%s` type as a geometry lookup parameter.' % type(value)) + raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value)) # Assigning the SRID value. geom.srid = self.get_srid(geom) @@ -137,6 +138,14 @@ class GeometryField(SpatialBackend.Field): # Setup for lazy-instantiated Geometry object. setattr(cls, self.attname, GeometryProxy(SpatialBackend.Geometry, self)) + def formfield(self, **kwargs): + defaults = {'form_class' : forms.GeometryField, + 'geom_type' : self._geom, + 'null' : self.null, + } + defaults.update(kwargs) + return super(GeometryField, self).formfield(**defaults) + def get_db_prep_lookup(self, lookup_type, value): """ Returns the spatial WHERE clause and associated parameters for the diff --git a/django/contrib/gis/forms/__init__.py b/django/contrib/gis/forms/__init__.py new file mode 100644 index 0000000000..5441e6078d --- /dev/null +++ b/django/contrib/gis/forms/__init__.py @@ -0,0 +1 @@ +from django.contrib.gis.forms.fields import GeometryField diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py new file mode 100644 index 0000000000..a65e76d5d4 --- /dev/null +++ b/django/contrib/gis/forms/fields.py @@ -0,0 +1,37 @@ +from django import forms +from django.contrib.gis.geos import GEOSGeometry, GEOSException +from django.utils.translation import ugettext_lazy as _ + +class GeometryField(forms.Field): + # By default a Textarea widget is used. + widget = forms.Textarea + + default_error_messages = { + 'no_geom' : _(u'No geometry value provided.'), + 'invalid_geom' : _(u'Invalid Geometry value.'), + 'invalid_geom_type' : _(u'Invalid Geometry type.'), + } + def __init__(self, **kwargs): + self.null = kwargs.pop('null') + self.geom_type = kwargs.pop('geom_type') + super(GeometryField, self).__init__(**kwargs) + + def clean(self, value): + """ + Validates that the input value can be converted to a Geometry + object (which is returned). A ValidationError is raised if + the value cannot be instantiated as a Geometry. + """ + if not value: + if self.null: + # The geometry column allows NULL, return None. + return None + else: + raise forms.ValidationError(self.error_messages['no_geom']) + try: + geom = GEOSGeometry(value) + if geom.geom_type.upper() != self.geom_type: + raise forms.ValidationError(self.error_messages['invalid_geom_type']) + return geom + except GEOSException: + raise forms.ValidationError(self.error_messages['invalid_geom']) diff --git a/django/contrib/gis/templates/gis/admin/openlayers.html b/django/contrib/gis/templates/gis/admin/openlayers.html new file mode 100644 index 0000000000..acf82b284e --- /dev/null +++ b/django/contrib/gis/templates/gis/admin/openlayers.html @@ -0,0 +1,37 @@ +{% block extrastyle %} + + +{% endblock %} + + +
+Delete all Features +{% if display_wkt %}

WKT debugging window:

{% endif %} + + +
diff --git a/django/contrib/gis/templates/gis/admin/openlayers.js b/django/contrib/gis/templates/gis/admin/openlayers.js new file mode 100644 index 0000000000..719426127c --- /dev/null +++ b/django/contrib/gis/templates/gis/admin/openlayers.js @@ -0,0 +1,157 @@ +{# Author: Justin Bronn, Travis Pinney & Dane Springmeyer #} +{% block vars %}var {{ module }} = {}; +{{ module }}.map = null; {{ module }}.controls = null; {{ module }}.panel = null; {{ module }}.re = new RegExp("^SRID=\d+;(.+)", "i"); {{ module }}.layers = {}; +{{ module }}.wkt_f = new OpenLayers.Format.WKT(); +{{ module }}.is_collection = {% if is_collection %}true{% else %}false{% endif %}; +{{ module }}.collection_type = '{{ collection_type }}'; +{{ module }}.is_linestring = {% if is_linestring %}true{% else %}false{% endif %}; +{{ module }}.is_polygon = {% if is_polygon %}true{% else %}false{% endif %}; +{{ module }}.is_point = {% if is_point %}true{% else %}false{% endif %}; +{% endblock %} +{{ module }}.get_ewkt = function(feat){return 'SRID={{ srid }};' + {{ module }}.wkt_f.write(feat);} +{{ module }}.read_wkt = function(wkt){ + // OpenLayers cannot handle EWKT -- we make sure to strip it out. + // EWKT is only exposed to OL if there's a validation error in the admin. + var match = {{ module }}.re.exec(wkt); + if (match){wkt = match[1];} + return {{ module }}.wkt_f.read(wkt); +} +{{ module }}.write_wkt = function(feat){ + if ({{ module }}.is_collection){ {{ module }}.num_geom = feat.geometry.components.length;} + else { {{ module }}.num_geom = 1;} + document.getElementById('{{ id }}').value = {{ module }}.get_ewkt(feat); +} +{{ module }}.add_wkt = function(event){ + // This function will sync the contents of the `vector` layer with the + // WKT in the text field. + if ({{ module }}.is_collection){ + var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.{{ geom_type }}()); + for (var i = 0; i < {{ module }}.layers.vector.features.length; i++){ + feat.geometry.addComponents([{{ module }}.layers.vector.features[i].geometry]); + } + {{ module }}.write_wkt(feat); + } else { + // Make sure to remove any previously added features. + if ({{ module }}.layers.vector.features.length > 1){ + old_feats = [{{ module }}.layers.vector.features[0]]; + {{ module }}.layers.vector.removeFeatures(old_feats); + {{ module }}.layers.vector.destroyFeatures(old_feats); + } + {{ module }}.write_wkt(event.feature); + } +} +{{ module }}.modify_wkt = function(event){ + if ({{ module }}.is_collection){ + if ({{ module }}.is_point){ + {{ module }}.add_wkt(event); + return; + } else { + // When modifying the selected components are added to the + // vector layer so we only increment to the `num_geom` value. + var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.{{ geom_type }}()); + for (var i = 0; i < {{ module }}.num_geom; i++){ + feat.geometry.addComponents([{{ module }}.layers.vector.features[i].geometry]); + } + {{ module }}.write_wkt(feat); + } + } else { + {{ module }}.write_wkt(event.feature); + } +} +// Function to clear vector features and purge wkt from div +{{ module }}.deleteFeatures = function(){ + {{ module }}.layers.vector.removeFeatures({{ module }}.layers.vector.features); + {{ module }}.layers.vector.destroyFeatures(); +} +{{ module }}.clearFeatures = function (){ + {{ module }}.deleteFeatures(); + document.getElementById('{{ id }}').value = ''; + {{ module }}.map.setCenter(new OpenLayers.LonLat({{ default_lon }}, {{ default_lat }}), {{ default_zoom }}); +} +// Add Select control +{{ module }}.addSelectControl = function(){ + var select = new OpenLayers.Control.SelectFeature({{ module }}.layers.vector, {'toggle' : true, 'clickout' : true}); + {{ module }}.map.addControl(select); + select.activate(); +} +{{ module }}.enableDrawing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.DrawFeature')[0].activate();} +{{ module }}.enableEditing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.ModifyFeature')[0].activate();} +// Create an array of controls based on geometry type +{{ module }}.getControls = function(lyr){ + {{ module }}.panel = new OpenLayers.Control.Panel({'displayClass': 'olControlEditingToolbar'}); + var nav = new OpenLayers.Control.Navigation(); + var draw_ctl; + if ({{ module }}.is_linestring){ + draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'}); + } else if ({{ module }}.is_polygon){ + draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'}); + } else if ({{ module }}.is_point){ + draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'}); + } + {% if modifiable %} + var mod = new OpenLayers.Control.ModifyFeature(lyr, {'displayClass': 'olControlModifyFeature'}); + {{ module }}.controls = [nav, draw_ctl, mod]; + {% else %} + {{ module }}.controls = [nav, darw_ctl]; + {% endif %} +} +{{ module }}.init = function(){ + {% block map_options %}// The options hash, w/ zoom, resolution, and projection settings. + var options = { +{% autoescape off %}{% for item in map_options.items %} '{{ item.0 }}' : {{ item.1 }}{% if not forloop.last %},{% endif %} +{% endfor %}{% endautoescape %} };{% endblock %} + // The admin map for this geometry field. + {{ module }}.map = new OpenLayers.Map('{{ id }}_map', options); + // Base Layer + {{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS( "{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}'} );{% endblock %} + {{ module }}.map.addLayer({{ module }}.layers.base); + {% block extra_layers %}{% endblock %} + {% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %} + {{ module }}.layers.vector = new OpenLayers.Layer.Vector(" {{ field_name }}"); + {{ module }}.map.addLayer({{ module }}.layers.vector); + // Read WKT from the text field. + var wkt = document.getElementById('{{ id }}').value; + if (wkt){ + // After reading into geometry, immediately write back to + // WKT