From dd8d89546062753f47d3091fc179a6be2627daa9 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 13 Sep 2007 23:22:06 +0000 Subject: [PATCH] Modify away! This was a long time coming. Thanks all for contributions. The ModifyFeature control lets you select a feature for modification, drag around vertices (or the whole feature in the case of a point), delete vertices, and add new vertices (closes #941). git-svn-id: http://svn.openlayers.org/trunk/openlayers@4272 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/modify-feature.html | 112 ++++++ lib/OpenLayers.js | 1 + lib/OpenLayers/Control/ModifyFeature.js | 462 ++++++++++++++++++++++++ tests/Control/test_ModifyFeature.html | 448 +++++++++++++++++++++++ tests/list-tests.html | 1 + 5 files changed, 1024 insertions(+) create mode 100644 examples/modify-feature.html create mode 100644 lib/OpenLayers/Control/ModifyFeature.js create mode 100644 tests/Control/test_ModifyFeature.html diff --git a/examples/modify-feature.html b/examples/modify-feature.html new file mode 100644 index 0000000000..3835a2764f --- /dev/null +++ b/examples/modify-feature.html @@ -0,0 +1,112 @@ + + + Modify Feature + + + + + + +

OpenLayers Modify Feature Example

+
+
+ +
+ + diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 7d10370d7d..cdc53dd9db 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -142,6 +142,7 @@ "OpenLayers/Control/LayerSwitcher.js", "OpenLayers/Control/DrawFeature.js", "OpenLayers/Control/DragFeature.js", + "OpenLayers/Control/ModifyFeature.js", "OpenLayers/Control/Panel.js", "OpenLayers/Control/SelectFeature.js", "OpenLayers/Geometry.js", diff --git a/lib/OpenLayers/Control/ModifyFeature.js b/lib/OpenLayers/Control/ModifyFeature.js new file mode 100644 index 0000000000..963dceb4ad --- /dev/null +++ b/lib/OpenLayers/Control/ModifyFeature.js @@ -0,0 +1,462 @@ +/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license. + * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt + * for the full text of the license. */ + + +/** + * @requires OpenLayers/Control/DragFeature.js + * @requires OpenLayers/Control/SelectFeature.js + * @requires OpenLayers/Handler/Keyboard.js + * + * Class: OpenLayers.Control.ModifyFeature + * Control to modify features. When activated, a click renders the vertices + * of a feature - these vertices can then be dragged. By default, the + * delete key will delete the vertex under the mouse. New features are + * added by dragging "virtual vertices" between vertices. Create a new + * control with the constructor. + * + * Inherits From: + * - + */ +OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: geometryTypes + * {Array(String)} To restrict modification to a limited set of geometry + * types, send a list of strings corresponding to the geometry class + * names. + */ + geometryTypes: null, + + /** + * Property: layer + * {OpenLayers.Layer.Vector} + */ + layer: null, + + /** + * Property: feature + * {} Feature currently available for modification. + */ + feature: null, + + /** + * Property: vertices + * {Array()} Verticies currently available + * for dragging. + */ + vertices: null, + + /** + * Property: virtualVertices + * {Array()} Virtual vertices in the middle + * of each edge. + */ + virtualVertices: null, + + /** + * Property: selectControl + * {} + */ + selectControl: null, + + /** + * Property: dragControl + * {} + */ + dragControl: null, + + /** + * Property: keyboardHandler + * {} + */ + keyboardHandler: null, + + /** + * APIProperty: deleteCodes + * {Array(Integer)} Keycodes for deleting verticies. Set to null to disable + * vertex deltion by keypress. If non-null, keypresses with codes + * in this array will delete vertices under the mouse. Default + * is 46 and 100, the 'delete' and lowercase 'd' keys. + */ + deleteCodes: null, + + /** + * APIProperty: virtualStyle + * {} + */ + virtualStyle: null, + + /** + * APIProperty: onModificationStart + * {Function} Optional function to be called when a feature is selected + * to be modified. The function should expect to be called with a + * feature. This could be used for example to allow to lock the + * feature on server-side. + */ + onModificationStart: function() {}, + + /** + * APIProperty: onModification + * {Function} Optional function to be called when a feature has been + * modified. The function should expect to be called with a feature. + */ + onModification: function() {}, + + /** + * APIProperty: onModificationEnd + * {Function} Optional function to be called when a feature is finished + * being modified. The function should expect to be called with a + * feature. + */ + onModificationEnd: function() {}, + + /** + * Constructor: OpenLayers.Control.ModifyFeature + * Create a new modify feature control. + * + * Parameters: + * layer - {OpenLayers.Layer.Vector} Layer that contains features that + * will be modified. + * options - {Object} Optional object whose properties will be set on the + * control. + */ + initialize: function(layer, options) { + this.layer = layer; + this.vertices = []; + this.virtualVertices = []; + this.styleVirtual = OpenLayers.Util.extend({}, this.layer.style); + this.styleVirtual.fillOpacity = 0.3; + this.styleVirtual.strokeOpacity = 0.3; + this.deleteCodes = [46, 100]; + OpenLayers.Control.prototype.initialize.apply(this, [options]); + if(!(this.deleteCodes instanceof Array)) { + this.deleteCodes = [this.deleteCodes]; + } + var control = this; + + // configure the select control + var selectOptions = { + geometryTypes: this.geometryTypes, + onSelect: function(feature) { + control.selectFeature.apply(control, [feature]); + }, + onUnselect: function(feature) { + control.unselectFeature.apply(control, [feature]); + } + }; + this.selectControl = new OpenLayers.Control.SelectFeature( + layer, selectOptions + ); + + // configure the drag control + var dragOptions = { + geometryTypes: ["OpenLayers.Geometry.Point"], + snappingOptions: this.snappingOptions, + onStart: function(feature, pixel) { + control.dragStart.apply(control, [feature, pixel]); + }, + onDrag: function(feature) { + control.dragVertex.apply(control, [feature]); + }, + onComplete: function(feature) { + control.dragComplete.apply(control, [feature]); + } + }; + this.dragControl = new OpenLayers.Control.DragFeature( + layer, dragOptions + ); + + // configure the keyboard handler + var keyboardOptions = { + keypress: this.handleKeypress + }; + this.keyboardHandler = new OpenLayers.Handler.Keyboard( + this, keyboardOptions + ); + }, + + /** + * APIMethod: destroy + * Take care of things that are not handled in superclass. + */ + destroy: function() { + this.layer = null; + this.selectControl.destroy(); + this.dragControl.destroy(); + this.keyboardHandler.destroy(); + OpenLayers.Control.prototype.destroy.apply(this, []); + }, + + /** + * APIMethod: activate + * Activate the control and the feature handler. + * + * Returns: + * {Boolean} Successfully activated the control and feature handler. + */ + activate: function() { + return (this.selectControl.activate() && + this.keyboardHandler.activate() && + OpenLayers.Control.prototype.activate.apply(this, arguments)); + }, + + /** + * APIMethod: deactivate + * Deactivate the controls. + * + * Returns: + * {Boolean} Successfully deactivated the control. + */ + deactivate: function() { + var deactivated = false; + // the return from the controls is unimportant in this case + if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) { + this.layer.removeFeatures(this.vertices); + this.layer.removeFeatures(this.virtualVertices); + this.vertices = []; + this.dragControl.deactivate(); + if(this.feature) { + this.selectControl.unselect.apply(this.selectControl, + [this.feature]); + } + this.selectControl.deactivate(); + this.keyboardHandler.deactivate(); + deactivated = true; + } + return deactivated; + }, + + /** + * Method: selectFeature + * Called when the select feature control selects a feature. + * + * Parameters: + * feature - {} The selected feature. + */ + selectFeature: function(feature) { + this.feature = feature; + this.resetVertices(); + this.dragControl.activate(); + this.onModificationStart(this.feature); + }, + + /** + * Method: unselectFeature + * Called when the select feature control unselects a feature. + * + * Parameters: + * feature - {} The unselected feature. + */ + unselectFeature: function(feature) { + this.layer.removeFeatures(this.vertices); + this.layer.removeFeatures(this.virtualVertices); + this.vertices = []; + this.virtualVertices = []; + this.feature = null; + this.dragControl.deactivate(); + this.onModificationEnd(feature); + }, + + /** + * Method: dragStart + * Called by the drag feature control with before a feature is dragged. + * This method is used to differentiate between points and vertices + * of higher order geometries. This respects the + * property and forces a select of points when the drag control is + * already active (and stops events from propagating to the select + * control). + * + * Parameters: + * feature - {} The point or vertex about to be + * dragged. + * pixel - {} Pixel location of the mouse event. + */ + dragStart: function(feature, pixel) { + // only change behavior if the feature is not in the vertices array + if(feature != this.feature && + OpenLayers.Util.indexOf(this.vertices, feature) == -1 && + OpenLayers.Util.indexOf(this.virtualVertices, feature) == -1) { + if(this.feature) { + // unselect the currently selected feature + this.selectControl.clickFeature.apply(this.selectControl, + [this.feature]); + } + // check any constraints on the geometry type + if(this.geometryTypes == null || + OpenLayers.Util.indexOf(this.geometryTypes, + feature.geometry.CLASS_NAME) != -1) { + // select the point + this.selectControl.clickFeature.apply(this.selectControl, + [feature]); + /** + * TBD: These lines improve workflow by letting the user + * immediately start dragging after the mouse down. + * However, it is very ugly to be messing with controls + * and their handlers in this way. I'd like a better + * solution if the workflow change is necessary. + */ + // prepare the point for dragging + this.dragControl.overFeature.apply(this.dragControl, + [feature]); + this.dragControl.lastPixel = pixel; + this.dragControl.dragHandler.started = true; + this.dragControl.dragHandler.start = pixel; + this.dragControl.dragHandler.last = pixel; + } + } + }, + + /** + * Method: dragVertex + * Called by the drag feature control with each drag move of a vertex. + * + * Parameters: + * vertex - {} The vertex being dragged. + */ + dragVertex: function(vertex) { + if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + if(this.feature != vertex) { + this.feature = vertex; + } + } else { + if(OpenLayers.Util.indexOf(this.virtualVertices, vertex) != -1) { + vertex.geometry.parent.addComponent(vertex.geometry, + vertex._index); + delete vertex._index; + OpenLayers.Util.removeItem(this.virtualVertices, vertex); + this.layer.removeFeatures(vertex); + } + } + this.layer.drawFeature(this.feature, this.selectControl.selectStyle); + this.layer.removeFeatures(this.virtualVertices); + // keep the vertex on top so it gets the mouseout after dragging + // this should be removed in favor of an option to draw under or + // maintain node z-index + this.layer.drawFeature(vertex); + }, + + /** + * Method: dragComplete + * Called by the drag feature control when the feature dragging is complete. + * + * Parameters: + * vertex - {} The vertex being dragged. + */ + dragComplete: function(vertex) { + this.resetVertices(); + this.onModification(this.feature); + }, + + /** + * Method: resetVertices + */ + resetVertices: function() { + if(this.vertices.length > 0) { + this.layer.removeFeatures(this.vertices); + this.vertices = []; + } + if(this.virtualVertices.length > 0) { + this.layer.removeFeatures(this.virtualVertices); + this.virtualVertices = []; + } + if(this.feature && + this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") { + this.collectVertices(this.feature.geometry); + this.layer.addFeatures(this.vertices); + this.layer.addFeatures(this.virtualVertices); + } + }, + + /** + * Method: handleKeypress + * Called by the feature handler on keypress. This is used to delete + * vertices and point features. If the property is set, + * vertices and points will be deleted when a feature is selected + * for modification and the mouse is over a vertex. + * + * Parameters: + * {Integer} Key code corresponding to the keypress event. + */ + handleKeypress: function(code) { + // check for delete key + if(this.feature && + OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) { + var vertex = this.dragControl.feature; + if(vertex && + OpenLayers.Util.indexOf(this.vertices, vertex) != -1) { + // remove the vertex + vertex.geometry.parent.removeComponent(vertex.geometry); + this.layer.drawFeature(this.feature, + this.selectControl.selectStyle); + this.resetVertices(); + this.onModification(this.feature); + } + } + }, + + /** + * Method: collectVertices + * Collect the vertices from the modifiable feature's geometry and push + * them on to the control's vertices array. + */ + collectVertices: function() { + this.vertices = []; + this.virtualVirtices = []; + var control = this; + function collectComponentVertices(geometry) { + var i, vertex, component; + if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + vertex = new OpenLayers.Feature.Vector(geometry); + control.vertices.push(vertex); + } else { + for(i=0; i + + + + + +
+ + diff --git a/tests/list-tests.html b/tests/list-tests.html index 834a4cc5ea..b13a295001 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -66,6 +66,7 @@
  • Control/test_Attribution.html
  • Control/test_SelectFeature.html
  • Control/test_DragFeature.html
  • +
  • Control/test_ModifyFeature.html
  • Control/test_DragPan.html
  • Control/test_OverviewMap.html
  • Control/test_NavToolbar.html