diff --git a/examples/draw-and-modify-features.html b/examples/draw-and-modify-features.html new file mode 100644 index 0000000000..18c075fa89 --- /dev/null +++ b/examples/draw-and-modify-features.html @@ -0,0 +1,61 @@ + + + + + + + + + + + Draw and modify features example + + + + + +
+ +
+
+
+
+
+ +
+ +
+

Draw and modify features example

+

Example of using the ol.interaction.Draw interaction together with + the ol.interaction.Modify interaction.

+
+ + +
+ +
+

See the draw-and-modify-features.js source to see how this is done.

+
+
draw, edit, modify, vector, featureoverlay
+
+ +
+ +
+ + + + + + + diff --git a/examples/draw-and-modify-features.js b/examples/draw-and-modify-features.js new file mode 100644 index 0000000000..e75f2049fd --- /dev/null +++ b/examples/draw-and-modify-features.js @@ -0,0 +1,84 @@ +goog.require('ol.FeatureOverlay'); +goog.require('ol.Map'); +goog.require('ol.View2D'); +goog.require('ol.events.condition'); +goog.require('ol.interaction'); +goog.require('ol.interaction.Draw'); +goog.require('ol.interaction.Modify'); +goog.require('ol.layer.Tile'); +goog.require('ol.source.MapQuest'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); + +var raster = new ol.layer.Tile({ + source: new ol.source.MapQuest({layer: 'sat'}) +}); + +var map = new ol.Map({ + layers: [raster], + target: 'map', + view: new ol.View2D({ + center: [-11000000, 4600000], + zoom: 4 + }) +}); + +// The features are not added to a regular vector layer/source, +// but to a feature overlay which holds a collection of features. +// This collection is passed to the modify and also the draw +// interaction, so that both can add or modify features. +var featureOverlay = new ol.FeatureOverlay({ + style: new ol.style.Style({ + fill: new ol.style.Fill({ + color: 'rgba(255, 255, 255, 0.2)' + }), + stroke: new ol.style.Stroke({ + color: '#ffcc33', + width: 2 + }), + image: new ol.style.Circle({ + radius: 7, + fill: new ol.style.Fill({ + color: '#ffcc33' + }) + }) + }) +}); +featureOverlay.setMap(map); + +var modify = new ol.interaction.Modify({ + features: featureOverlay.getFeatures(), + // the SHIFT key must be pressed to delete vertices, so + // that new vertices can be drawn at the same position + // of existing vertices + deleteCondition: function(event) { + return ol.events.condition.shiftKeyOnly(event) && + ol.events.condition.singleClick(event); + } +}); +map.addInteraction(modify); + +var draw; // global so we can remove it later +function addInteraction() { + draw = new ol.interaction.Draw({ + features: featureOverlay.getFeatures(), + type: /** @type {ol.geom.GeometryType} */ (typeSelect.value) + }); + map.addInteraction(draw); +} + +var typeSelect = document.getElementById('type'); + + +/** + * Let user change the geometry type. + * @param {Event} e Change event. + */ +typeSelect.onchange = function(e) { + map.removeInteraction(draw); + addInteraction(); +}; + +addInteraction(); diff --git a/externs/olx.js b/externs/olx.js index 8bdc71b0d1..a49a05194c 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -1647,7 +1647,8 @@ olx.interaction.DragZoomOptions.prototype.style; * type: ol.geom.GeometryType, * minPointsPerRing: (number|undefined), * style: (ol.style.Style|Array.|ol.feature.StyleFunction|undefined), - * geometryName: (string|undefined)}} + * geometryName: (string|undefined), + * condition: (ol.events.ConditionType|undefined)}} * @todo api */ olx.interaction.DrawOptions; @@ -1704,6 +1705,15 @@ olx.interaction.DrawOptions.prototype.style; olx.interaction.DrawOptions.prototype.geometryName; +/** + * A conditional modifier (e.g. shift key) that determines if the interaction is + * active (i.e. a click adds a vertex) or not. By default, a click with no modifier + * keys adds a vertex. + * @type {ol.events.ConditionType|undefined} + */ +olx.interaction.DrawOptions.prototype.condition; + + /** * @typedef {{condition: (ol.events.ConditionType|undefined), * pixelDelta: (number|undefined)}} diff --git a/src/ol/events/condition.js b/src/ol/events/condition.js index 8a77526919..a1c61faf7d 100644 --- a/src/ol/events/condition.js +++ b/src/ol/events/condition.js @@ -67,6 +67,7 @@ ol.events.condition.never = goog.functions.FALSE; /** * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. * @return {boolean} True if the event is a `singleclick` event. + * @todo api */ ol.events.condition.singleClick = function(mapBrowserEvent) { return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK; @@ -134,6 +135,7 @@ ol.events.condition.targetNotEditable = function(mapBrowserEvent) { /** * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. * @return {boolean} True if the event originates from a mouse device. + * @todo api */ ol.events.condition.mouseOnly = function(mapBrowserEvent) { goog.asserts.assertInstanceof(mapBrowserEvent, ol.MapBrowserPointerEvent); diff --git a/src/ol/interaction/drawinteraction.js b/src/ol/interaction/drawinteraction.js index e60f457dd5..72defc63dd 100644 --- a/src/ol/interaction/drawinteraction.js +++ b/src/ol/interaction/drawinteraction.js @@ -10,6 +10,7 @@ goog.require('ol.FeatureOverlay'); goog.require('ol.Map'); goog.require('ol.MapBrowserEvent'); goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.events.condition'); goog.require('ol.feature'); goog.require('ol.geom.GeometryType'); goog.require('ol.geom.LineString'); @@ -192,6 +193,13 @@ ol.interaction.Draw = function(options) { */ this.geometryName_ = options.geometryName; + /** + * @private + * @type {ol.events.ConditionType} + */ + this.condition_ = goog.isDef(options.condition) ? + options.condition : ol.events.condition.noModifierKeys; + }; goog.inherits(ol.interaction.Draw, ol.interaction.Pointer); @@ -244,8 +252,12 @@ ol.interaction.Draw.prototype.handleMapBrowserEvent = function(event) { * @return {boolean} Pass the event to other interactions. */ ol.interaction.Draw.prototype.handlePointerDown = function(event) { - this.downPx_ = event.pixel; - return true; + if (this.condition_(event)) { + this.downPx_ = event.pixel; + return true; + } else { + return false; + } }; diff --git a/test/spec/ol/interaction/drawinteraction.test.js b/test/spec/ol/interaction/drawinteraction.test.js index 14fa5deaae..b11b5074d0 100644 --- a/test/spec/ol/interaction/drawinteraction.test.js +++ b/test/spec/ol/interaction/drawinteraction.test.js @@ -42,16 +42,19 @@ describe('ol.interaction.Draw', function() { * @param {string} type Event type. * @param {number} x Horizontal offset from map center. * @param {number} y Vertical offset from map center. + * @param {boolean=} opt_shiftKey Shift key is pressed. */ - function simulateEvent(type, x, y) { + function simulateEvent(type, x, y, opt_shiftKey) { var viewport = map.getViewport(); // calculated in case body has top < 0 (test runner with small window) var position = goog.style.getClientPosition(viewport); + var shiftKey = goog.isDef(opt_shiftKey) ? opt_shiftKey : false; var event = new ol.MapBrowserPointerEvent(type, map, new ol.pointer.PointerEvent(type, new goog.events.BrowserEvent({ clientX: position.x + x + width / 2, - clientY: position.y + y + height / 2 + clientY: position.y + y + height / 2, + shiftKey: shiftKey }))); map.handleMapBrowserEvent(event); } @@ -122,6 +125,14 @@ describe('ol.interaction.Draw', function() { expect(features).to.have.length(0); }); + it('does not draw a point when modifier key is pressed', function() { + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20, true); + simulateEvent('pointerup', 10, 20); + var features = source.getFeatures(); + expect(features).to.have.length(0); + }); + it('triggers draw events', function() { var ds = sinon.spy(); var de = sinon.spy(); @@ -134,7 +145,6 @@ describe('ol.interaction.Draw', function() { expect(ds).to.be.called(2); expect(de).to.be.called(1); }); - }); describe('drawing multipoints', function() {