From 10cf5bfefc707f54a80274ac14968fb25686df3c Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 30 Nov 2007 17:09:23 +0000 Subject: [PATCH] drag, rotate, resize, and reshape with the ModifyFeature control - thanks pvalsecc and elemoine for the inspiration, tests, and code - good partenering with you guys (closes #1150). git-svn-id: http://svn.openlayers.org/trunk/openlayers@5301 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/modify-feature.html | 47 +++++++ lib/OpenLayers/Control/ModifyFeature.js | 160 +++++++++++++++++++++--- lib/OpenLayers/Layer/Vector.js | 25 +++- tests/Control/test_ModifyFeature.html | 39 ++++-- tests/Layer/test_Vector.html | 13 +- 5 files changed, 255 insertions(+), 29 deletions(-) diff --git a/examples/modify-feature.html b/examples/modify-feature.html index 3835a2764f..3338cd7ec7 100644 --- a/examples/modify-feature.html +++ b/examples/modify-feature.html @@ -53,6 +53,9 @@ OpenLayers.Handler.Path), polygon: new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.Polygon), + regular: new OpenLayers.Control.DrawFeature(vectors, + OpenLayers.Handler.RegularPolygon, + {handlerOptions: {sides: 5}}), modify: new OpenLayers.Control.ModifyFeature(vectors, modifyOptions) }; @@ -65,6 +68,19 @@ document.getElementById('noneToggle').checked = true; } + function update() { + var rotate = document.getElementById("rotate").checked; + controls.modify.rotate = rotate; + var resize = document.getElementById("resize").checked; + controls.modify.resize = resize; + var drag = document.getElementById("drag").checked; + controls.modify.drag = drag; + var sides = parseInt(document.getElementById("sides").value); + sides = Math.max(3, isNaN(sides) ? 0 : sides); + controls.regular.handler.sides = sides; + var irregular = document.getElementById("irregular").checked; + controls.regular.handler.irregular = irregular; + } function toggleControl(element) { for(key in controls) { @@ -101,10 +117,41 @@ +
  • + + + + + +
  • +
  • diff --git a/lib/OpenLayers/Control/ModifyFeature.js b/lib/OpenLayers/Control/ModifyFeature.js index 8f46d3e1ec..5df3eb8066 100644 --- a/lib/OpenLayers/Control/ModifyFeature.js +++ b/lib/OpenLayers/Control/ModifyFeature.js @@ -86,7 +86,37 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { * {} */ virtualStyle: null, + + /** + * APIProperty: rotate + * {Boolean} Allow rotation of feature instead of vertex modification. + */ + rotate: false, + + /** + * APIProperty: resize + * {Boolean} Allow resizing of feature instead of vertex modification. + */ + resize: false, + + /** + * Property: radiusHandle + * {} A handle for rotating/resizing a feature. + */ + radiusHandle: null, + + /** + * APIProperty: drag + * {Boolean} Allow dragging of feature with a drag handle. + */ + drag: false, + /** + * Property: dragHandle + * {} A handle for dragging a feature. + */ + dragHandle: null, + /** * APIProperty: onModificationStart * {Function} Optional function to be called when a feature is selected @@ -250,9 +280,17 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { */ unselectFeature: function(feature) { this.layer.removeFeatures(this.vertices); - this.layer.removeFeatures(this.virtualVertices); this.vertices = []; + this.layer.destroyFeatures(this.virtualVertices); this.virtualVertices = []; + if(this.dragHandle) { + this.layer.destroyFeatures([this.dragHandle]); + delete this.dragHandle; + } + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle]); + delete this.radiusHandle; + } this.feature = null; this.dragControl.deactivate(); this.onModificationEnd(feature); @@ -274,9 +312,8 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { */ 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(feature != this.feature && !feature.geometry.parent && + feature != this.dragHandle && feature != this.radiusHandle) { if(this.feature) { // unselect the currently selected feature this.selectControl.clickFeature.apply(this.selectControl, @@ -315,21 +352,41 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { * vertex - {} The vertex being dragged. */ dragVertex: function(vertex) { + /** + * Five cases: + * 1) dragging a simple point + * 2) dragging a virtual vertex + * 3) dragging a drag handle + * 4) dragging a radius handle + * 5) dragging a real vertex + */ if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + // dragging a simple point if(this.feature != vertex) { this.feature = vertex; } } else { - if(OpenLayers.Util.indexOf(this.virtualVertices, vertex) != -1) { + if(vertex._index) { + // dragging a virtual vertex vertex.geometry.parent.addComponent(vertex.geometry, vertex._index); delete vertex._index; OpenLayers.Util.removeItem(this.virtualVertices, vertex); - this.layer.removeFeatures(vertex); + } else if(vertex == this.dragHandle) { + // dragging a drag handle + this.layer.removeFeatures(this.vertices); + this.vertices = []; + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle]); + this.radiusHandle = null; + } } + // dragging a radius handle - no special treatment + // dragging a real vertex - no special treatment + this.layer.destroyFeatures(this.virtualVertices); + this.virtualVertices = []; + this.layer.drawFeature(this.feature, this.selectControl.selectStyle); } - 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 @@ -360,11 +417,24 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { this.layer.removeFeatures(this.virtualVertices); this.virtualVertices = []; } + if(this.dragHandle) { + this.layer.destroyFeatures([this.dragHandle]); + this.dragHandle = null; + } + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle]); + this.radiusHandle = null; + } 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); + if(this.drag) { + this.collectDragHandle(); + } + if(this.rotate || this.resize) { + this.collectRadiusHandle(); + } else { + this.collectVertices(); + } } }, @@ -385,7 +455,8 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { var vertex = this.dragControl.feature; if(vertex && OpenLayers.Util.indexOf(this.vertices, vertex) != -1 && - !this.dragControl.dragHandler.dragging) { + !this.dragControl.dragHandler.dragging && + vertex.geometry.parent) { // remove the vertex vertex.geometry.parent.removeComponent(vertex.geometry); this.layer.drawFeature(this.feature, @@ -442,8 +513,69 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { } } } - } - collectComponentVertices(this.feature.geometry); + } + collectComponentVertices.call(this, this.feature.geometry); + this.layer.addFeatures(this.vertices); + this.layer.addFeatures(this.virtualVertices); + }, + + /** + * Method: collectDragHandle + * Collect the drag handle for the selected geometry. + */ + collectDragHandle: function() { + var geometry = this.feature.geometry; + var center = geometry.getBounds().getCenterLonLat(); + var originGeometry = new OpenLayers.Geometry.Point( + center.lon, center.lat + ); + var origin = new OpenLayers.Feature.Vector(originGeometry); + originGeometry.move = function(x, y) { + OpenLayers.Geometry.Point.prototype.move.call(this, x, y); + geometry.move(x, y); + } + this.dragHandle = origin; + this.layer.addFeatures([this.dragHandle]); + }, + + /** + * Method: collectRadiusHandle + * Collect the radius handle for the selected geometry. + */ + collectRadiusHandle: function() { + var geometry = this.feature.geometry; + var bounds = geometry.getBounds(); + var center = bounds.getCenterLonLat(); + var originGeometry = new OpenLayers.Geometry.Point( + center.lon, center.lat + ); + var radiusGeometry = new OpenLayers.Geometry.Point( + bounds.right, bounds.bottom + ); + var radius = new OpenLayers.Feature.Vector(radiusGeometry); + var resize = this.resize; + var rotate = this.rotate; + radiusGeometry.move = function(x, y) { + OpenLayers.Geometry.Point.prototype.move.call(this, x, y); + var dx1 = this.x - originGeometry.x; + var dy1 = this.y - originGeometry.y; + var dx0 = dx1 - x; + var dy0 = dy1 - y; + if(rotate) { + var a0 = Math.atan2(dy0, dx0); + var a1 = Math.atan2(dy1, dx1); + var angle = a1 - a0; + angle *= 180 / Math.PI; + geometry.rotate(angle, originGeometry); + } + if(resize) { + var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0)); + var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1)); + geometry.resize(l1 / l0, originGeometry); + } + } + this.radiusHandle = radius; + this.layer.addFeatures([this.radiusHandle]); }, /** diff --git a/lib/OpenLayers/Layer/Vector.js b/lib/OpenLayers/Layer/Vector.js index 8c93d9e44f..12fe0778fd 100644 --- a/lib/OpenLayers/Layer/Vector.js +++ b/lib/OpenLayers/Layer/Vector.js @@ -307,12 +307,27 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { /** * APIMethod: destroyFeatures - * Destroy all features on the layer and empty the selected features array. + * Erase and estroy features on the layer. + * + * Parameters: + * features - {Array()} An optional array of + * features to destroy. If not supplied, all features on the layer + * will be destroyed. */ - destroyFeatures: function () { - this.selectedFeatures = []; - for (var i = this.features.length - 1; i >= 0; i--) { - this.features[i].destroy(); + destroyFeatures: function(features) { + var all = (features == undefined); + if(all) { + features = this.features; + this.selectedFeatures = []; + } + this.eraseFeatures(features); + var feature; + for(var i=features.length-1; i>=0; i--) { + feature = features[i]; + if(!all) { + OpenLayers.Util.removeItem(this.selectedFeatures, feature); + } + feature.destroy(); } }, diff --git a/tests/Control/test_ModifyFeature.html b/tests/Control/test_ModifyFeature.html index 5f7ee9e262..4c55e5ea14 100644 --- a/tests/Control/test_ModifyFeature.html +++ b/tests/Control/test_ModifyFeature.html @@ -190,7 +190,10 @@ control.dragControl.deactivate = function() { t.ok(true, "Deactivate called on drag control"); } control.onModificationEnd = function (feature) { t.eq(feature.id, fakeFeature.id, "onModificationEnd got feature.") } layer.removeFeatures = function(verts) { - t.ok(verts == 'a' || verts == 'b', "Verts removed correctly") + t.ok(verts == 'a', "Normal verts removed correctly"); + } + layer.destroyFeatures = function(verts) { + t.ok(verts == 'b', "Virtual verts destroyed correctly"); } control.unselectFeature(fakeFeature); t.eq(control.feature, null, "feature is set to null"); @@ -214,9 +217,11 @@ control.selectFeature(fakeFeature); control.collectVertices = function() { + t.ok(true, "collectVertices called"); this.vertices = 'a'; this.virtualVertices = 'd'; - t.ok(true, "collectVertices called"); + layer.addFeatures(this.vertices); + layer.addFeatures(this.virtualVertices); } layer.addFeatures = function(features) { @@ -242,7 +247,7 @@ } function test_ModifyFeature_resetVertices(t) { - t.plan(9); + t.plan(15); var layer = new OpenLayers.Layer.Vector(); var control = new OpenLayers.Control.ModifyFeature(layer); var point = new OpenLayers.Geometry.Point(5,6); @@ -253,7 +258,7 @@ control.resetVertices(); t.eq(control.vertices.length, 0, "Correct vertices length"); t.eq(control.virtualVertices.length, 0, "Correct virtual vertices length."); - + var multiPoint = new OpenLayers.Geometry.MultiPoint([point, point2]); control.feature = new OpenLayers.Feature.Vector(multiPoint); control.resetVertices(); @@ -273,6 +278,20 @@ t.eq(control.vertices[0].geometry.id, control.vertices[3].geometry.id, "First and last vertices are the same"); t.eq(control.virtualVertices.length, 3, "Correct virtual vertices length (polygon)."); + control.drag = true; + control.resetVertices(); + t.ok(control.dragHandle != null, "Drag handle is set"); + t.eq(control.vertices.length, 4, "Correct vertices length with polygon (drag)"); + + control.rotate = true; + control.resetVertices(); + t.ok(control.radiusHandle != null, "Radius handle is set"); + t.eq(control.vertices.length, 0, "Correct vertices length with polygon (rotate)"); + + control.rotate = false; + control.resize = true; + t.ok(control.radiusHandle != null, "Radius handle is set"); + t.eq(control.vertices.length, 0, "Correct vertices length with polygon (resize)"); } function test_ModifyFeature_onDrag(t) { @@ -289,11 +308,6 @@ 'geometry': { 'id':'myGeom'}, 'id': 'fakeFeature' }; - control.collectVertices = function(geom) { - t.eq(geom.id, 'myGeom', "collect geom called"); - this.vertices = 'normal'; - this.virtualVertices = 'virtual'; - } layer.addFeatures = function (verts) { t.ok(verts == 'virtual' || verts == 'normal', verts + " verts correct"); } @@ -303,6 +317,13 @@ control.onModification = function(feat) { t.eq(feat.id, fakeFeature.id, "onModification gets correct feat"); } + control.collectVertices = function() { + t.ok(true, "collectVertices called"); + this.vertices = 'normal'; + this.virtualVertices = 'virtual'; + layer.addFeatures(this.vertices); + layer.addFeatures(this.virtualVertices); + } control.feature = fakeFeature; control.vertices = 'previous normal'; control.virtualVertices = 'previous virtual'; diff --git a/tests/Layer/test_Vector.html b/tests/Layer/test_Vector.html index 3e1aab251d..87bc441a94 100644 --- a/tests/Layer/test_Vector.html +++ b/tests/Layer/test_Vector.html @@ -130,7 +130,7 @@ } function test_Layer_Vector_destroyFeatures (t) { - t.plan(3); + t.plan(5); layer = new OpenLayers.Layer.Vector(name); var map = new OpenLayers.Map('map'); map.addLayer(layer); @@ -145,6 +145,17 @@ layer.destroyFeatures(); t.eq(layer.features.length, 0, "destroyFeatures triggers removal"); t.eq(layer.selectedFeatures, [], "Destroy features removes selected features"); + features = []; + for (var i = 0; i < 5; i++) { + features.push(new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(0,0))); + } + layer.addFeatures(features); + layer.selectedFeatures.push(features[0]); + layer.selectedFeatures.push(features[1]); + layer.destroyFeatures([features[0], features[1]]); + t.eq(layer.features.length, 3, "destroyFeatures removes appropriate features"); + t.eq(layer.selectedFeatures, [], "destroyFeatures removes appropriate selected features"); } function test_99_Layer_Vector_destroy (t) {