mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed #25004 -- Updated OpenLayers-based widget to OpenLayers 3
Thanks Tim Graham for the review.
This commit is contained in:
		| @@ -80,13 +80,22 @@ class OpenLayersWidget(BaseGeometryWidget): | ||||
|     template_name = 'gis/openlayers.html' | ||||
|  | ||||
|     class Media: | ||||
|         css = { | ||||
|             'all': ( | ||||
|                 'https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.1/ol.css', | ||||
|                 'gis/css/ol3.css', | ||||
|             ) | ||||
|         } | ||||
|         js = ( | ||||
|             'https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.13.1/OpenLayers.js', | ||||
|             'https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.1/ol.js', | ||||
|             'gis/js/OLMapWidget.js', | ||||
|         ) | ||||
|  | ||||
|     def serialize(self, value): | ||||
|         return value.json if value else '' | ||||
|  | ||||
| class OSMWidget(BaseGeometryWidget): | ||||
|  | ||||
| class OSMWidget(OpenLayersWidget): | ||||
|     """ | ||||
|     An OpenLayers/OpenStreetMap-based widget. | ||||
|     """ | ||||
| @@ -95,12 +104,6 @@ class OSMWidget(BaseGeometryWidget): | ||||
|     default_lat = 47 | ||||
|     map_srid = 3857 | ||||
|  | ||||
|     class Media: | ||||
|         js = ( | ||||
|             'https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.13.1/OpenLayers.js', | ||||
|             'gis/js/OLMapWidget.js', | ||||
|         ) | ||||
|  | ||||
|     def __init__(self, attrs=None): | ||||
|         super(OSMWidget, self).__init__() | ||||
|         for key in ('default_lon', 'default_lat'): | ||||
|   | ||||
							
								
								
									
										31
									
								
								django/contrib/gis/static/gis/css/ol3.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								django/contrib/gis/static/gis/css/ol3.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| .switch-type { | ||||
|     background-repeat: no-repeat; | ||||
|     cursor: pointer; | ||||
|     top: 0.5em; | ||||
|     width: 22px; | ||||
|     height: 20px; | ||||
| } | ||||
|  | ||||
| .type-Point { | ||||
|     background-image: url("../img/draw_point_off.png"); | ||||
|     right: 5px; | ||||
| } | ||||
| .type-Point.type-active { | ||||
|     background-image: url("../img/draw_point_on.png"); | ||||
| } | ||||
|  | ||||
| .type-LineString { | ||||
|     background-image: url("../img/draw_line_off.png"); | ||||
|     right: 30px; | ||||
| } | ||||
| .type-LineString.type-active { | ||||
|     background-image: url("../img/draw_line_on.png"); | ||||
| } | ||||
|  | ||||
| .type-Polygon { | ||||
|     background-image: url("../img/draw_polygon_off.png"); | ||||
|     right: 55px; | ||||
| } | ||||
| .type-Polygon.type-active { | ||||
|     background-image: url("../img/draw_polygon_on.png"); | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_line_off.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_line_off.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_line_on.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_line_on.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_point_off.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_point_off.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_point_on.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_point_on.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_polygon_off.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_polygon_off.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_polygon_on.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								django/contrib/gis/static/gis/img/draw_polygon_on.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.4 KiB | 
| @@ -1,199 +1,59 @@ | ||||
| /*global OpenLayers*/ | ||||
| (function() { | ||||
| /* global ol */ | ||||
|  | ||||
| var GeometryTypeControl = function(opt_options) { | ||||
|     'use strict'; | ||||
|     /** | ||||
|      * 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); | ||||
|     // Map control to switch type when geometry type is unknown | ||||
|     var options = opt_options || {}; | ||||
|  | ||||
|     var element = document.createElement('div'); | ||||
|     element.className = 'switch-type type-' + options.type + ' ol-control ol-unselectable'; | ||||
|     if (options.active) { | ||||
|         element.className += " type-active"; | ||||
|     } | ||||
|  | ||||
|     var self = this; | ||||
|     var switchType = function(e) { | ||||
|         e.preventDefault(); | ||||
|         if (options.widget.currentGeometryType !== self) { | ||||
|             options.widget.map.removeInteraction(options.widget.interactions.draw); | ||||
|             options.widget.interactions.draw = new ol.interaction.Draw({ | ||||
|                 features: options.widget.featureCollection, | ||||
|                 type: options.type | ||||
|             }); | ||||
|             options.widget.map.addInteraction(options.widget.interactions.draw); | ||||
|             var className = options.widget.currentGeometryType.element.className.replace(/ type-active/g, ''); | ||||
|             options.widget.currentGeometryType.element.className = className; | ||||
|             options.widget.currentGeometryType = self; | ||||
|             element.className += " type-active"; | ||||
|         } | ||||
|         return features; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @requires OpenLayers/Format/WKT.js | ||||
|      */ | ||||
|     element.addEventListener('click', switchType, false); | ||||
|     element.addEventListener('touchstart', switchType, false); | ||||
|  | ||||
|     /** | ||||
|      * Class: OpenLayers.Format.DjangoWKT | ||||
|      * Class for reading Well-Known Text, with workarounds to successfully parse | ||||
|      * geometries and collections as returned 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, 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" | ||||
|     ol.control.Control.call(this, { | ||||
|         element: element | ||||
|     }); | ||||
| }; | ||||
| ol.inherits(GeometryTypeControl, ol.control.Control); | ||||
|  | ||||
| // TODO: allow deleting individual features (#8972) | ||||
| (function() { | ||||
|     'use strict'; | ||||
|     var jsonFormat = new ol.format.GeoJSON(); | ||||
|  | ||||
|     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 = OpenLayers.Geometry[options.geom_name]; | ||||
|         } | ||||
|         this.interactions = {draw: null, modify: null}; | ||||
|         this.typeChoices = false; | ||||
|         this.ready = false; | ||||
|  | ||||
|         // Default options | ||||
|         this.options = { | ||||
|             color: 'ee9900', | ||||
|             default_lat: 0, | ||||
|             default_lon: 0, | ||||
|             default_zoom: 4, | ||||
|             is_collection: options.geom_name.indexOf('Multi') > -1 || options.geom_name.indexOf('Collection') > -1, | ||||
|             layerswitcher: false, | ||||
|             map_options: {}, | ||||
|             map_srid: 4326, | ||||
|             modifiable: true, | ||||
|             mouse_position: false, | ||||
|             opacity: 0.4, | ||||
|             point_zoom: 12, | ||||
|             scale_text: false, | ||||
|             scrollable: true | ||||
|             default_zoom: 12, | ||||
|             is_collection: options.geom_name.indexOf('Multi') > -1 || options.geom_name.indexOf('Collection') > -1 | ||||
|         }; | ||||
|  | ||||
|         // Altering using user-provided options | ||||
| @@ -202,185 +62,170 @@ | ||||
|                 this.options[property] = options[property]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this.map = this.create_map(); | ||||
|  | ||||
|         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; | ||||
|         if (!options.base_layer) { | ||||
|             this.options.base_layer = new ol.layer.Tile({source: new ol.source.OSM()}); | ||||
|         } | ||||
|         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); | ||||
|         var 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())]); | ||||
|  | ||||
|         this.map = this.createMap(); | ||||
|         this.featureCollection = new ol.Collection(); | ||||
|         this.featureOverlay = new ol.layer.Vector({ | ||||
|             map: this.map, | ||||
|             source: new ol.source.Vector({ | ||||
|                 features: this.featureCollection, | ||||
|                 useSpatialIndex: false // improve performance | ||||
|             }), | ||||
|             updateWhileAnimating: true, // optional, for instant visual feedback | ||||
|             updateWhileInteracting: true // optional, for instant visual feedback | ||||
|         }); | ||||
|  | ||||
|         // Populate and set handlers for the feature container | ||||
|         var self = this; | ||||
|         this.featureCollection.on('add', function(event) { | ||||
|             var feature = event.element; | ||||
|             feature.on('change', function() { | ||||
|                 self.serializeFeatures(); | ||||
|             }); | ||||
|             if (self.ready) { | ||||
|                 self.serializeFeatures(); | ||||
|                 if (!self.options.is_collection) { | ||||
|                     self.disableDrawing(); // Only allow one feature at a time | ||||
|                 } | ||||
|             } 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(); | ||||
|             } | ||||
|         var initial_value = document.getElementById(this.options.id).value; | ||||
|         if (initial_value) { | ||||
|             var features = jsonFormat.readFeatures('{"type": "Feature", "geometry": ' + initial_value + '}'); | ||||
|             var extent = ol.extent.createEmpty(); | ||||
|             features.forEach(function(feature) { | ||||
|                 this.featureOverlay.getSource().addFeature(feature); | ||||
|                 ol.extent.extend(extent, feature.getGeometry().getExtent()); | ||||
|             }, this); | ||||
|             // Center/zoom the map | ||||
|             this.map.getView().fit(extent, this.map.getSize(), {maxZoom: this.options.default_zoom}); | ||||
|         } else { | ||||
|             this.enableDrawing(); | ||||
|             this.map.getView().setCenter(this.defaultCenter()); | ||||
|         } | ||||
|         this.createInteractions(); | ||||
|         if (initial_value && !this.options.is_collection) { | ||||
|             this.disableDrawing(); | ||||
|         } | ||||
|         this.ready = true; | ||||
|     } | ||||
|  | ||||
|     MapWidget.prototype.create_map = function() { | ||||
|         var 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'}); | ||||
|         } | ||||
|         map.addLayer(this.layers.base); | ||||
|     MapWidget.prototype.createMap = function() { | ||||
|         var map = new ol.Map({ | ||||
|             target: this.options.map_id, | ||||
|             layers: [this.options.base_layer], | ||||
|             view: new ol.View({ | ||||
|                 zoom: this.options.default_zoom | ||||
|             }) | ||||
|         }); | ||||
|         return map; | ||||
|     }; | ||||
|  | ||||
|     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]); | ||||
|     MapWidget.prototype.createInteractions = function() { | ||||
|         // Initialize the modify interaction | ||||
|         this.interactions.modify = new ol.interaction.Modify({ | ||||
|             features: this.featureCollection, | ||||
|             deleteCondition: function(event) { | ||||
|                 return ol.events.condition.shiftKeyOnly(event) && | ||||
|                     ol.events.condition.singleClick(event); | ||||
|             } | ||||
|             this.write_wkt(feat); | ||||
|         } else { | ||||
|             if (this.layers.vector.features.length > 1) { | ||||
|                 var old_feats = [this.layers.vector.features[0]]; | ||||
|                 this.layers.vector.removeFeatures(old_feats); | ||||
|                 this.layers.vector.destroyFeatures(old_feats); | ||||
|             } | ||||
|             this.write_wkt(event.feature); | ||||
|         }); | ||||
|  | ||||
|         // Initialize the draw interaction | ||||
|         var geomType = this.options.geom_name; | ||||
|         if (geomType === "Unknown" || geomType === "GeometryCollection") { | ||||
|             // Default to Point, but create icons to switch type | ||||
|             geomType = "Point"; | ||||
|             this.currentGeometryType = new GeometryTypeControl({widget: this, type: "Point", active: true}); | ||||
|             this.map.addControl(this.currentGeometryType); | ||||
|             this.map.addControl(new GeometryTypeControl({widget: this, type: "LineString", active: false})); | ||||
|             this.map.addControl(new GeometryTypeControl({widget: this, type: "Polygon", active: false})); | ||||
|             this.typeChoices = true; | ||||
|         } | ||||
|     }; | ||||
|         this.interactions.draw = new ol.interaction.Draw({ | ||||
|             features: this.featureCollection, | ||||
|             type: geomType | ||||
|         }); | ||||
|  | ||||
|     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); | ||||
|         this.map.addInteraction(this.interactions.draw); | ||||
|         this.map.addInteraction(this.interactions.modify); | ||||
|     }; | ||||
|  | ||||
|     MapWidget.prototype.defaultCenter = function() { | ||||
|         var center = new OpenLayers.LonLat(this.options.default_lon, this.options.default_lat); | ||||
|         var center = [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 ol.proj.transform(center, 'EPSG:4326', this.map.getView().getProjection()); | ||||
|         } | ||||
|         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(); | ||||
|         this.interactions.draw.setActive(true); | ||||
|         if (this.typeChoices) { | ||||
|             // Show geometry type icons | ||||
|             var divs = document.getElementsByClassName("switch-type"); | ||||
|             for (var i = 0; i !== divs.length; i++) { | ||||
|                 divs[i].style.visibility = "visible"; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     MapWidget.prototype.enableEditing = function() { | ||||
|         this.map.getControlsByClass('OpenLayers.Control.ModifyFeature')[0].activate(); | ||||
|     MapWidget.prototype.disableDrawing = function() { | ||||
|         if (this.interactions.draw) { | ||||
|             this.interactions.draw.setActive(false); | ||||
|             if (this.typeChoices) { | ||||
|                 // Hide geometry type icons | ||||
|                 var divs = document.getElementsByClassName("switch-type"); | ||||
|                 for (var i = 0; i !== divs.length; i++) { | ||||
|                     divs[i].style.visibility = "hidden"; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     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.indexOf('LineString') >= 0 || this.options.geom_name === 'GeometryCollection' || this.options.geom_name === 'Unknown') { | ||||
|             this.controls.push(new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'})); | ||||
|         } | ||||
|         if (this.options.geom_name.indexOf('Polygon') >= 0 || this.options.geom_name === 'GeometryCollection' || this.options.geom_name === 'Unknown') { | ||||
|             this.controls.push(new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'})); | ||||
|         } | ||||
|         if (this.options.geom_name.indexOf('Point') >= 0 || this.options.geom_name === 'GeometryCollection' || 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'})); | ||||
|         } | ||||
|     MapWidget.prototype.clearFeatures = function() { | ||||
|         this.featureCollection.clear(); | ||||
|         // Empty textarea widget | ||||
|         document.getElementById(this.options.id).value = ''; | ||||
|         this.enableDrawing(); | ||||
|     }; | ||||
|  | ||||
|     MapWidget.prototype.serializeFeatures = function() { | ||||
|         // Three use cases: GeometryCollection, multigeometries, and single geometry | ||||
|         var geometry = null; | ||||
|         var features = this.featureOverlay.getSource().getFeatures(); | ||||
|         if (this.options.is_collection) { | ||||
|             if (this.options.geom_name === "GeometryCollection") { | ||||
|                 var geometries = []; | ||||
|                 for (var i = 0; i < features.length; i++) { | ||||
|                     geometries.push(features[i].getGeometry()); | ||||
|                 } | ||||
|                 geometry = new ol.geom.GeometryCollection(geometries); | ||||
|             } else { | ||||
|                 geometry = features[0].getGeometry().clone(); | ||||
|                 for (var j = 1; j < features.length; j++) { | ||||
|                     switch(geometry.getType()) { | ||||
|                         case "MultiPoint": | ||||
|                             geometry.appendPoint(features[j].getGeometry().getPoint(0)); | ||||
|                             break; | ||||
|                         case "MultiLineString": | ||||
|                             geometry.appendLineString(features[j].getGeometry().getLineString(0)); | ||||
|                             break; | ||||
|                         case "MultiPolygon": | ||||
|                             geometry.appendPolygon(features[j].getGeometry().getPolygon(0)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             if (features[0]) { | ||||
|                 geometry = features[0].getGeometry(); | ||||
|             } | ||||
|         } | ||||
|         document.getElementById(this.options.id).value = jsonFormat.writeGeometry(geometry); | ||||
|     }; | ||||
|  | ||||
|     window.MapWidget = MapWidget; | ||||
| })(); | ||||
|   | ||||
| @@ -1,17 +1,11 @@ | ||||
| {% 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("OpenStreetMap (Mapnik)"); | ||||
| {% endblock %} | ||||
|  | ||||
| {% block base_layer %} | ||||
| var base_layer = new ol.layer.Tile({source: new ol.source.OSM()}); | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -1,27 +1,32 @@ | ||||
| {% load i18n l10n static %} | ||||
| {% load i18n l10n %} | ||||
| <style type="text/css">{% block map_css %}{% get_current_language_bidi as LANGUAGE_BIDI %} | ||||
|     #{{ 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_raw %}#{{ id }} { display: none; }{% endif %} | ||||
|     .olControlEditingToolbar .olControlModifyFeatureItemActive { | ||||
|         background-image: url("{% static "admin/img/gis/move_vertex_on.svg" %}"); | ||||
|         background-repeat: no-repeat; | ||||
|     } | ||||
|     .olControlEditingToolbar .olControlModifyFeatureItemInactive { | ||||
|         background-image: url("{% static "admin/img/gis/move_vertex_off.svg" %}"); | ||||
|         background-repeat: no-repeat; | ||||
|     }{% endblock %} | ||||
|     {% endblock %} | ||||
| </style> | ||||
|  | ||||
| <div id="{{ id }}_div_map"> | ||||
|     <div id="{{ id }}_map"></div> | ||||
|     <span class="clear_features"><a href="javascript:{{ module }}.clearFeatures()">{% trans "Delete all Features" %}</a></span> | ||||
|     {% if not disabled %}<span class="clear_features"><a href="javascript:{{ module }}.clearFeatures()">{% trans "Delete all Features" %}</a></span>{% endif %} | ||||
|     {% if display_raw %}<p>{% trans "Debugging window (serialized value)" %}</p>{% endif %} | ||||
|     <textarea id="{{ id }}" class="vSerializedField required" cols="150" rows="10" name="{{ name }}">{{ serialized }}</textarea> | ||||
|     <script type="text/javascript"> | ||||
|         {% block map_options %}var map_options = {};{% endblock %} | ||||
|         {% block base_layer %} | ||||
|             var base_layer = new ol.layer.Tile({ | ||||
|                 source: new ol.source.XYZ({ | ||||
|                     attributions: "NASA Worldview", | ||||
|                     maxZoom: 8, | ||||
|                     url: "https://map1{a-c}.vis.earthdata.nasa.gov/wmts-webmerc/" + | ||||
|                          "BlueMarble_ShadedRelief_Bathymetry/default/%7BTime%7D/" + | ||||
|                          "GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpg" | ||||
|                 }) | ||||
|             }); | ||||
|         {% endblock %} | ||||
|         {% block options %}var options = { | ||||
|             base_layer: base_layer, | ||||
|             geom_name: '{{ geom_type }}', | ||||
|             id: '{{ id }}', | ||||
|             map_id: '{{ id }}_map', | ||||
|   | ||||
| @@ -164,18 +164,25 @@ Widget classes | ||||
|         isn't suitable for production use since it offers no guaranteed uptime | ||||
|         and runs on a slow server. | ||||
|  | ||||
|     .. _tailored to your needs: http://docs.openlayers.org/library/deploying.html | ||||
|         Also, the widget nows uses OpenLayers 3 instead of OpenLayers 2. | ||||
|  | ||||
|     .. _tailored to your needs: http://openlayers.org/en/latest/doc/tutorials/custom-builds.html | ||||
|  | ||||
| ``OSMWidget`` | ||||
|  | ||||
| .. class:: OSMWidget | ||||
|  | ||||
|     This widget uses an OpenStreetMap base layer (Mapnik) to display geographic | ||||
|     objects on. | ||||
|     ``template_name`` is ``gis/openlayers-osm.html``. | ||||
|     This widget uses an OpenStreetMap base layer to display geographic objects | ||||
|     on. ``template_name`` is ``gis/openlayers-osm.html``. | ||||
|  | ||||
|     The :class:`OpenLayersWidget` note about JavaScript file hosting above also | ||||
|     applies here. See also this `FAQ answer`_ about ``https`` access to map | ||||
|     tiles. | ||||
|  | ||||
|     .. versionchanged:: 1.11 | ||||
|  | ||||
|         OpenLayers 2.x has been dropped in favor of OpenLayers 3. If you extend | ||||
|         the ``gis/openlayers-osm.html`` template, please review your custom | ||||
|         template. | ||||
|  | ||||
|     .. _FAQ answer: https://help.openstreetmap.org/questions/10920/how-to-embed-a-map-in-my-https-site | ||||
|   | ||||
| @@ -165,7 +165,8 @@ Minor features | ||||
|  | ||||
| * The OpenLayers-based form widgets now use ``OpenLayers.js`` from | ||||
|   ``https://cdnjs.cloudflare.com`` which is more suitable for production use | ||||
|   than the the old ``http://openlayers.org`` source. | ||||
|   than the the old ``http://openlayers.org`` source. They are also updated to | ||||
|   use OpenLayers 3. | ||||
|  | ||||
| * PostGIS migrations can now change field dimensions. | ||||
|  | ||||
| @@ -469,6 +470,11 @@ Backwards incompatible changes in 1.11 | ||||
|  | ||||
| * The ``GEOSGeometry`` equality operator now also compares SRID. | ||||
|  | ||||
| * The OpenLayers-based form widgets now use OpenLayers 3, and the | ||||
|   ``gis/openlayers.html`` and ``gis/openlayers-osm.html`` templates have been | ||||
|   updated. Check your project if you subclass these widgets or extend the | ||||
|   templates. | ||||
|  | ||||
| Database backend API | ||||
| -------------------- | ||||
|  | ||||
|   | ||||
| @@ -7,42 +7,58 @@ QUnit.module('gis.OLMapWidget'); | ||||
| QUnit.test('MapWidget.featureAdded', function(assert) { | ||||
|     var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; | ||||
|     var widget = new MapWidget(options); | ||||
|     assert.equal(widget.layers.vector.features.length, 1); | ||||
|     assert.equal(widget.featureCollection.getLength(), 1); | ||||
|     widget.serializeFeatures(); | ||||
|     assert.equal( | ||||
|         widget.layers.vector.features[0].geometry.toString(), | ||||
|         'POINT(7.8177 47.397)', | ||||
|         'Point addded to vector layer' | ||||
|         document.getElementById('id_point').value, | ||||
|         '{"type":"Point","coordinates":[7.8177,47.397]}', | ||||
|         'Point added to vector layer' | ||||
|     ); | ||||
| }); | ||||
|  | ||||
| QUnit.test('MapWidget.map_srid', function(assert) { | ||||
|     var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; | ||||
|     var widget = new MapWidget(options); | ||||
|     assert.equal(widget.options.map_srid, 4326, 'SRID 4326'); | ||||
|     assert.equal(widget.map.getView().getProjection().getCode(), 'EPSG:3857', 'SRID 3857'); | ||||
| }); | ||||
|  | ||||
| QUnit.test('MapWidget.defaultCenter', function(assert) { | ||||
|     var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; | ||||
|     var widget = new MapWidget(options); | ||||
|     assert.equal(widget.defaultCenter().toString(), 'lon=0,lat=0', 'Default center at 0, 0'); | ||||
|     assert.equal(widget.defaultCenter().toString(), '0,0', 'Default center at 0, 0'); | ||||
|     options.default_lat = 47.08; | ||||
|     options.default_lon = 6.81; | ||||
|     widget = new MapWidget(options); | ||||
|     assert.equal( | ||||
|         widget.defaultCenter().toString(), | ||||
|         'lon=6.81,lat=47.08', | ||||
|         '6.81,47.08', | ||||
|         'Default center at 6.81, 47.08' | ||||
|     ); | ||||
|     assert.equal(widget.map.getView().getZoom(), 12); | ||||
| }); | ||||
|  | ||||
| QUnit.test('MapWidget.getControls', function(assert) { | ||||
| QUnit.test('MapWidget.interactions', function(assert) { | ||||
|     var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; | ||||
|     var widget = new MapWidget(options); | ||||
|     widget.getControls(widget.layers.vector); | ||||
|     assert.equal(widget.controls.length, 3); | ||||
|     assert.equal(widget.controls[0].displayClass, 'olControlNavigation', 'Navigation control'); | ||||
|     assert.equal(widget.controls[1].displayClass, 'olControlDrawFeaturePoint', 'Draw control'); | ||||
|     assert.equal(widget.controls[2].displayClass, 'olControlModifyFeature', 'Modify control'); | ||||
|     assert.equal(Object.keys(widget.interactions).length, 2); | ||||
|     assert.equal(widget.interactions.draw.getActive(), false, "Draw is inactive with an existing point"); | ||||
|     assert.equal(widget.interactions.modify.getActive(), true, "Modify is active with an existing point"); | ||||
| }); | ||||
|  | ||||
| QUnit.test('MapWidget.clearFeatures', function(assert) { | ||||
|     var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; | ||||
|     var widget = new MapWidget(options); | ||||
|     var initial_value = document.getElementById('id_point').value; | ||||
|     widget.clearFeatures(); | ||||
|     assert.equal(document.getElementById('id_point').value, ""); | ||||
|     document.getElementById('id_point').value = initial_value; | ||||
| }); | ||||
|  | ||||
| QUnit.test('MapWidget.multipolygon', function(assert) { | ||||
|     var options = {id: 'id_multipolygon', map_id: 'id_multipolygon_map', geom_name: 'MultiPolygon'}; | ||||
|     var widget = new MapWidget(options); | ||||
|     assert.ok(widget.options.is_collection); | ||||
|     assert.equal(widget.interactions.draw.getActive(), true, "Draw is active with no existing content"); | ||||
| }); | ||||
|  | ||||
| QUnit.test('MapWidget.IsCollection', function(assert) { | ||||
|   | ||||
| @@ -77,12 +77,16 @@ | ||||
|     <script src='../django/contrib/admin/static/admin/js/prepopulate.js' data-cover></script> | ||||
|     <script src='../django/contrib/admin/static/admin/js/urlify.js' data-cover></script> | ||||
|  | ||||
|     <div id="id_point_map"> | ||||
|         <textarea id="id_point" name="point" | ||||
|                   class="vSerializedField required" style="display:none;" | ||||
|                   rows="10" cols="150">POINT (7.8177 47.397)</textarea> | ||||
|     <div id="id_point_map" style="display:none;"> | ||||
|         <textarea id="id_point" name="point" class="vSerializedField required" | ||||
|                   style="display:none;" rows="10" cols="150" | ||||
|         >{"type": "Point", "coordinates": [7.8177, 47.397]}</textarea> | ||||
|     </div> | ||||
|     <script src='https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.13.1/OpenLayers.js'></script> | ||||
|     <div id="id_multipolygon_map" style="display:none;"> | ||||
|         <textarea id="id_multipolygon" name="multipolygon" class="vSerializedField required" | ||||
|                   style="display:none;" rows="10" cols="150"></textarea> | ||||
|     </div> | ||||
|     <script src='https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.0/ol.js'></script> | ||||
|     <script src='../django/contrib/gis/static/gis/js/OLMapWidget.js' data-cover></script> | ||||
|     <script src='./gis/mapwidget.test.js'></script> | ||||
|  | ||||
|   | ||||
| @@ -204,7 +204,7 @@ class SpecializedFieldTest(SimpleTestCase): | ||||
|  | ||||
|         self.assertIn('<textarea ', rendered) | ||||
|         self.assertIn('required', rendered) | ||||
|         self.assertIn(geom.wkt, rendered) | ||||
|         self.assertIn(escape(geom.json), rendered) | ||||
|  | ||||
|     # map_srid in operlayers.html template must not be localized. | ||||
|     @override_settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True) | ||||
| @@ -318,7 +318,7 @@ class OSMWidgetTest(SimpleTestCase): | ||||
|         form = PointForm(data={'p': geom}) | ||||
|         rendered = form.as_p() | ||||
|  | ||||
|         self.assertIn("OpenStreetMap (Mapnik)", rendered) | ||||
|         self.assertIn("ol.source.OSM()", rendered) | ||||
|         self.assertIn("id: 'id_p',", rendered) | ||||
|  | ||||
|     def test_default_lat_lon(self): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user