Code

Ticket #9806: 9806.5.diff

File 9806.5.diff, 16.6 KB (added by slinkp, 3 years ago)

patch that allows adding as well as editing geometries in admin UI for GeometryField

Line 
1Index: django/contrib/gis/admin/options.py
2===================================================================
3--- django/contrib/gis/admin/options.py (revision 14851)
4+++ django/contrib/gis/admin/options.py (working copy)
5@@ -37,6 +37,8 @@
6     wms_url = 'http://labs.metacarta.com/wms/vmap0'
7     wms_layer = 'basic'
8     wms_name = 'OpenLayers WMS'
9+    wms_options = {'format': 'image/jpg'}
10+
11     debug = False
12     widget = OpenLayersWidget
13 
14@@ -67,47 +69,72 @@
15         in the `widget` attribute) using the settings from the attributes set
16         in this class.
17         """
18-        is_collection = db_field.geom_type in ('MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON', 'GEOMETRYCOLLECTION')
19-        if is_collection:
20-            if db_field.geom_type == 'GEOMETRYCOLLECTION': collection_type = 'Any'
21-            else: collection_type = OGRGeomType(db_field.geom_type.replace('MULTI', ''))
22+        is_unknown = db_field.geom_type in ('GEOMETRY',)
23+        if not is_unknown:
24+            #If it is not generic, get the parameters from the db_field
25+            is_collection = db_field.geom_type in ('MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON', 'GEOMETRYCOLLECTION')
26+            if is_collection:
27+                if db_field.geom_type == 'GEOMETRYCOLLECTION': collection_type = 'Any'
28+                else: collection_type = OGRGeomType(db_field.geom_type.upper().replace('MULTI', ''))
29+            else:
30+                collection_type = 'None'
31+            is_linestring = db_field.geom_type in ('LINESTRING', 'MULTILINESTRING')
32+            is_polygon = db_field.geom_type in ('POLYGON', 'MULTIPOLYGON')
33+            is_point = db_field.geom_type in ('POINT', 'MULTIPOINT')
34+            geom_type = OGRGeomType(db_field.geom_type)
35         else:
36+            #If it is generic, set sensible defaults
37+            is_collection = False
38             collection_type = 'None'
39+            is_linestring = False
40+            is_polygon = False
41+            is_point = False
42+            geom_type = OGRGeomType('Unknown')
43 
44         class OLMap(self.widget):
45             template = self.map_template
46             geom_type = db_field.geom_type
47+            wms_options = ''
48+            if self.wms_options:
49+                wms_options = ["%s: '%s'" % pair for pair in self.wms_options.items()]
50+                wms_options = ', '.join(wms_options)
51+                wms_options = ', ' + wms_options
52+
53             params = {'default_lon' : self.default_lon,
54+                      'collection_type' : collection_type,
55+                      'debug' : self.debug,
56                       'default_lat' : self.default_lat,
57                       'default_zoom' : self.default_zoom,
58+                      'display_srid' : self.display_srid,
59                       'display_wkt' : self.debug or self.display_wkt,
60-                      'geom_type' : OGRGeomType(db_field.geom_type),
61                       'field_name' : db_field.name,
62+                      'geom_type' : geom_type,
63                       'is_collection' : is_collection,
64-                      'scrollable' : self.scrollable,
65+                      'is_linestring' : is_linestring,
66+                      'is_point' : is_point,
67+                      'is_polygon' : is_polygon,
68+                      'is_unknown': is_unknown,
69                       'layerswitcher' : self.layerswitcher,
70-                      'collection_type' : collection_type,
71-                      'is_linestring' : db_field.geom_type in ('LINESTRING', 'MULTILINESTRING'),
72-                      'is_polygon' : db_field.geom_type in ('POLYGON', 'MULTIPOLYGON'),
73-                      'is_point' : db_field.geom_type in ('POINT', 'MULTIPOINT'),
74-                      'num_zoom' : self.num_zoom,
75+                      'map_height' : self.map_height,
76+                      'map_width' : self.map_width,
77+                      'max_extent' : self.max_extent,
78+                      'max_resolution' : self.max_resolution,
79                       'max_zoom' : self.max_zoom,
80                       'min_zoom' : self.min_zoom,
81-                      'units' : self.units, #likely shoud get from object
82-                      'max_resolution' : self.max_resolution,
83-                      'max_extent' : self.max_extent,
84                       'modifiable' : self.modifiable,
85                       'mouse_position' : self.mouse_position,
86+                      'num_zoom' : self.num_zoom,
87+                      'openlayers_url': self.openlayers_url,
88+                      'openlayers_img_path': self.openlayers_img_path,
89+                      'point_zoom' : self.point_zoom,
90                       'scale_text' : self.scale_text,
91-                      'map_width' : self.map_width,
92-                      'map_height' : self.map_height,
93-                      'point_zoom' : self.point_zoom,
94+                      'scrollable' : self.scrollable,
95                       'srid' : self.map_srid,
96-                      'display_srid' : self.display_srid,
97-                      'wms_url' : self.wms_url,
98+                      'units' : self.units, #likely shoud get from object
99                       'wms_layer' : self.wms_layer,
100                       'wms_name' : self.wms_name,
101-                      'debug' : self.debug,
102+                      'wms_options': wms_options,
103+                      'wms_url' : self.wms_url,
104                       }
105         return OLMap
106 
107Index: django/contrib/gis/admin/widgets.py
108===================================================================
109--- django/contrib/gis/admin/widgets.py (revision 14851)
110+++ django/contrib/gis/admin/widgets.py (working copy)
111@@ -1,5 +1,6 @@
112 from django.conf import settings
113 from django.contrib.gis.gdal import OGRException
114+from django.contrib.gis.gdal import OGRGeomType
115 from django.contrib.gis.geos import GEOSGeometry, GEOSException
116 from django.forms.widgets import Textarea
117 from django.template import loader, Context
118@@ -15,9 +16,12 @@
119     """
120     Renders an OpenLayers map using the WKT of the geometry.
121     """
122+
123     def render(self, name, value, attrs=None):
124         # Update the template parameters with any attributes passed in.
125-        if attrs: self.params.update(attrs)
126+        # If value is None, that means we haven't saved anything yet.
127+        if attrs:
128+            self.params.update(attrs)
129 
130         # Defaulting the WKT value to a blank string -- this
131         # will be tested in the JavaScript and the appropriate
132@@ -31,8 +35,7 @@
133                 value = GEOSGeometry(value)
134             except (GEOSException, ValueError):
135                 value = None
136-
137-        if value and value.geom_type.upper() != self.geom_type:
138+        if value and value.geom_type.upper() != self.geom_type and self.geom_type != 'GEOMETRY':
139             value = None
140 
141         # Constructing the dictionary of the map options.
142@@ -65,6 +68,37 @@
143             # geometry.
144             self.params['wkt'] = wkt
145 
146+            # Check if the field is generic so the proper values are overriden
147+            if self.params['is_unknown']:
148+                self.params['geom_type'] = OGRGeomType(value.geom_type)
149+                if value.geom_type.upper() in ('LINESTRING', 'MULTILINESTRING'):
150+                    self.params['is_linestring'] = True
151+                elif value.geom_type.upper() in ('POLYGON', 'MULTIPOLYGON'):
152+                    self.params['is_polygon'] = True
153+                elif value.geom_type.upper() in ('POINT', 'MULTIPOINT'):
154+                    self.params['is_point'] = True
155+                elif value.geom_type.upper() in ('MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON'):
156+                    self.params['is_collection'] = True
157+                    self.params['collection_type'] = OGRGeomType(value.geom_type.upper().replace('MULTI', ''))
158+                elif value.geom_type.upper() == 'GEOMETRYCOLLECTION':
159+                    self.params['is_collection'] = True
160+                    self.params['collection_type'] = 'Any'
161+                    self.params['geom_type'] = 'Collection'
162+
163+
164+        else:
165+            if self.params['is_unknown']:
166+                # If the geometry is unknown and the value is not set,
167+                # make it as flexible as possible.
168+                self.params['geom_type'] = 'Collection'
169+                self.params['is_collection']=True
170+                self.params['collection_type'] = 'Any'
171+
172+        # If we don't already have a camelcase geom_type,
173+        # make one using str(OGRGeomType).
174+        # Works for most things but not 'GeometryCollection'.
175+        if str(self.params['geom_type']) == str(self.params['geom_type']).upper():
176+            self.params['geom_type'] = str(OGRGeomType(self.params['geom_type']))
177         return loader.render_to_string(self.template, self.params,
178                                        context_instance=geo_context)
179 
180Index: django/contrib/gis/templates/gis/admin/openlayers.js
181===================================================================
182--- django/contrib/gis/templates/gis/admin/openlayers.js        (revision 14851)
183+++ django/contrib/gis/templates/gis/admin/openlayers.js        (working copy)
184@@ -1,5 +1,6 @@
185 {# Author: Justin Bronn, Travis Pinney & Dane Springmeyer #}
186 {% block vars %}var {{ module }} = {};
187+{% if openlayers_img_path %}OpenLayers.ImgPath = '{{ openlayers_img_path }}';{% endif %}
188 {{ module }}.map = null; {{ module }}.controls = null; {{ module }}.panel = null; {{ module }}.re = new RegExp("^SRID=\d+;(.+)", "i"); {{ module }}.layers = {};
189 {{ module }}.modifiable = {{ modifiable|yesno:"true,false" }};
190 {{ module }}.wkt_f = new OpenLayers.Format.WKT();
191@@ -9,19 +10,24 @@
192 {{ module }}.is_polygon = {{ is_polygon|yesno:"true,false" }};
193 {{ module }}.is_point = {{ is_point|yesno:"true,false" }};
194 {% endblock %}
195-{{ module }}.get_ewkt = function(feat){return 'SRID={{ srid }};' + {{ module }}.wkt_f.write(feat);}
196+{{ module }}.get_ewkt = function(feat){
197+  return 'SRID={{ srid }};' + {{ module }}.wkt_f.write(feat);
198+};
199 {{ module }}.read_wkt = function(wkt){
200   // OpenLayers cannot handle EWKT -- we make sure to strip it out.
201   // EWKT is only exposed to OL if there's a validation error in the admin.
202   var match = {{ module }}.re.exec(wkt);
203   if (match){wkt = match[1];}
204   return {{ module }}.wkt_f.read(wkt);
205-}
206+};
207 {{ module }}.write_wkt = function(feat){
208-  if ({{ module }}.is_collection){ {{ module }}.num_geom = feat.geometry.components.length;}
209-  else { {{ module }}.num_geom = 1;}
210+  if ({{ module }}.is_collection){
211+    {{ module }}.num_geom = feat.length;
212+  } else {
213+    {{ module }}.num_geom = 1;
214+  };
215   document.getElementById('{{ id }}').value = {{ module }}.get_ewkt(feat);
216-}
217+};
218 {{ module }}.add_wkt = function(event){
219   // This function will sync the contents of the `vector` layer with the
220   // WKT in the text field.
221@@ -40,7 +46,7 @@
222     }
223     {{ module }}.write_wkt(event.feature);
224   }
225-}
226+};
227 {{ module }}.modify_wkt = function(event){
228   if ({{ module }}.is_collection){
229     if ({{ module }}.is_point){
230@@ -58,7 +64,7 @@
231   } else {
232     {{ module }}.write_wkt(event.feature);
233   }
234-}
235+};
236 // Function to clear vector features and purge wkt from div
237 {{ module }}.deleteFeatures = function(){
238   {{ module }}.layers.vector.removeFeatures({{ module }}.layers.vector.features);
239@@ -68,29 +74,41 @@
240   {{ module }}.deleteFeatures();
241   document.getElementById('{{ id }}').value = '';
242   {{ module }}.map.setCenter(new OpenLayers.LonLat({{ default_lon }}, {{ default_lat }}), {{ default_zoom }});
243-}
244+};
245 // Add Select control
246 {{ module }}.addSelectControl = function(){
247   var select = new OpenLayers.Control.SelectFeature({{ module }}.layers.vector, {'toggle' : true, 'clickout' : true});
248   {{ module }}.map.addControl(select);
249   select.activate();
250-}
251-{{ module }}.enableDrawing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.DrawFeature')[0].activate();}
252-{{ module }}.enableEditing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.ModifyFeature')[0].activate();}
253+};
254+{{ module }}.enableDrawing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.DrawFeature')[0].activate();};
255+{{ module }}.enableEditing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.ModifyFeature')[0].activate();};
256 // Create an array of controls based on geometry type
257 {{ module }}.getControls = function(lyr){
258   {{ module }}.panel = new OpenLayers.Control.Panel({'displayClass': 'olControlEditingToolbar'});
259-  var nav = new OpenLayers.Control.Navigation();
260+  var nav = new OpenLayers.Control.Navigation({'title': 'Navigate'});
261   var draw_ctl;
262   if ({{ module }}.is_linestring){
263-    draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'});
264+    draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath', 'title': 'Draw Lines'});
265   } else if ({{ module }}.is_polygon){
266-    draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'});
267+    draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon', 'title': 'Draw Polygons'});
268   } else if ({{ module }}.is_point){
269-    draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'});
270-  }
271+    draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint', 'title': 'Draw Points'});
272+  };
273+  /* if is_collection==true and collection_type=='Any', we should
274+   * provide a widget that allows choosing the type of feature to
275+   * add... but that'd take a lot more work. Possible example to draw
276+   * on: http://openlayers.org/dev/examples/snapping.html
277+   * For now, just assume we want MultiPolygons.
278+   */
279+  if (draw_ctl == undefined) {
280+    draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon', 'title': 'Draw Polygons'});
281+  };
282+  if ({{module}}.is_collection )  {
283+      draw_ctl.multi = true;
284+  };
285   if ({{ module }}.modifiable){
286-    var mod = new OpenLayers.Control.ModifyFeature(lyr, {'displayClass': 'olControlModifyFeature'});
287+    var mod = new OpenLayers.Control.ModifyFeature(lyr, {'displayClass': 'olControlModifyFeature', 'title': 'Modify'});
288     {{ module }}.controls = [nav, draw_ctl, mod];
289   } else {
290     if(!lyr.features.length){
291@@ -99,7 +117,7 @@
292       {{ module }}.controls = [nav];
293     }
294   }
295-}
296+};
297 {{ module }}.init = function(){
298     {% block map_options %}// The options hash, w/ zoom, resolution, and projection settings.
299     var options = {
300@@ -108,7 +126,7 @@
301     // The admin map for this geometry field.
302     {{ module }}.map = new OpenLayers.Map('{{ id }}_map', options);
303     // Base Layer
304-    {{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS( "{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}'} );{% endblock %}
305+    {{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS( "{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}' {{ wms_options|safe }} }  );{% endblock %}
306     {{ module }}.map.addLayer({{ module }}.layers.base);
307     {% block extra_layers %}{% endblock %}
308     {% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %}
309@@ -121,20 +139,21 @@
310       // WKT <textarea> as EWKT (so that SRID is included).
311       var admin_geom = {{ module }}.read_wkt(wkt);
312       {{ module }}.write_wkt(admin_geom);
313-      if ({{ module }}.is_collection){
314-       // If geometry collection, add each component individually so they may be
315-       // edited individually.
316-       for (var i = 0; i < {{ module }}.num_geom; i++){
317-         {{ module }}.layers.vector.addFeatures([new OpenLayers.Feature.Vector(admin_geom.geometry.components[i].clone())]);
318-       }
319+      var bounds;
320+      if ({{ module }}.is_collection) {
321+        {{ module }}.layers.vector.addFeatures(admin_geom);
322+        bounds = admin_geom[0].geometry.getBounds();
323+        for (var i = 1; i < admin_geom.length; i++ ) {
324+          bounds.extend(admin_geom[i].geometry.getBounds());
325+        };
326       } else {
327        {{ module }}.layers.vector.addFeatures([admin_geom]);
328-      }
329-      // Zooming to the bounds.
330-      {{ module }}.map.zoomToExtent(admin_geom.geometry.getBounds());
331+       bounds = admin_geom.geometry.getBounds();
332+      };
333+      {{ module }}.map.zoomToExtent(bounds);
334       if ({{ module }}.is_point){
335           {{ module }}.map.zoomTo({{ point_zoom }});
336-      }
337+      };
338     } else {
339       {{ module }}.map.setCenter(new OpenLayers.LonLat({{ default_lon }}, {{ default_lat }}), {{ default_zoom }});
340     }
341@@ -164,4 +183,4 @@
342     } else {
343       {{ module }}.enableDrawing();
344     }
345-}
346+};