diff --git a/examples/draw-features.html b/examples/draw-features.html new file mode 100644 index 0000000000..11a34094d4 --- /dev/null +++ b/examples/draw-features.html @@ -0,0 +1,50 @@ + + + + + + + + + + + Draw features example + + + + + +
+ +
+
+
+
+
+ +
+ +
+

Draw features example

+

Example of using the Draw interaction.

+
+

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

+
+
draw, edit, vector
+
+ +
+ +
+ + + + + + diff --git a/examples/draw-features.js b/examples/draw-features.js new file mode 100644 index 0000000000..3eae7e65e4 --- /dev/null +++ b/examples/draw-features.js @@ -0,0 +1,92 @@ +goog.require('ol.Map'); +goog.require('ol.RendererHint'); +goog.require('ol.View2D'); +goog.require('ol.interaction'); +goog.require('ol.interaction.Draw'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.MapQuestOpenAerial'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Rule'); +goog.require('ol.style.Shape'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); + +var raster = new ol.layer.Tile({ + source: new ol.source.MapQuestOpenAerial() +}); + +var vector = new ol.layer.Vector({ + source: new ol.source.Vector({parser: null}), + style: new ol.style.Style({ + rules: [ + new ol.style.Rule({ + filter: 'renderIntent("selected")', + symbolizers: [ + new ol.style.Shape({ + fill: new ol.style.Fill({ + color: '#0099ff', + opacity: 1 + }), + size: 16 + }), + new ol.style.Fill({ + color: '#ffffff', + opacity: 0.5 + }), + new ol.style.Stroke({ + color: '#0099ff', + width: 3 + }) + ] + }), + new ol.style.Rule({ + filter: 'renderIntent("temporary")', + symbolizers: [ + new ol.style.Shape({ + fill: new ol.style.Fill({ + color: '#0033ff', + opacity: 1 + }), + size: 16, + zIndex: 1 + }) + ] + }) + ], + symbolizers: [ + new ol.style.Shape({ + fill: new ol.style.Fill({ + color: '#0033ff', + opacity: 1 + }), + size: 16 + }), + new ol.style.Fill({ + color: '#ffffff', + opacity: 0.2 + }), + new ol.style.Stroke({ + color: '#ffcc33', + width: 1.5 + }) + ] + }) +}); + +var draw = new ol.interaction.Draw({ + layer: vector, + mode: /** @type {ol.interaction.DrawMode} */ ('polygon') +}); + +var map = new ol.Map({ + interactions: ol.interaction.defaults().extend([draw]), + layers: [raster, vector], + renderer: ol.RendererHint.CANVAS, + target: 'map', + view: new ol.View2D({ + center: [-11000000, 4600000], + zoom: 4 + }) +}); diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 0c6c3b03fc..be482df52e 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -341,6 +341,14 @@ * @todo stability experimental */ +/** + * @typedef {Object} ol.interaction.DrawOptions + * @property {ol.layer.Vector} layer Destination layer for the features. + * @property {ol.interaction.DrawMode} mode Drawing mode ('point', 'linestring', + * or 'polygon'). + * @todo stability experimental + */ + /** * @typedef {Object} ol.interaction.KeyboardPanOptions * @property {ol.events.ConditionType|undefined} condition A conditional diff --git a/src/ol/interaction/drawinteraction.exports b/src/ol/interaction/drawinteraction.exports new file mode 100644 index 0000000000..c085ef69e2 --- /dev/null +++ b/src/ol/interaction/drawinteraction.exports @@ -0,0 +1 @@ +@exportClass ol.interaction.Draw ol.interaction.DrawOptions diff --git a/src/ol/interaction/drawinteraction.js b/src/ol/interaction/drawinteraction.js new file mode 100644 index 0000000000..8436d70769 --- /dev/null +++ b/src/ol/interaction/drawinteraction.js @@ -0,0 +1,286 @@ +goog.provide('ol.interaction.Draw'); +goog.provide('ol.interaction.DrawMode'); + +goog.require('goog.asserts'); + +goog.require('ol.Coordinate'); +goog.require('ol.Feature'); +goog.require('ol.Map'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.layer.Vector'); +goog.require('ol.layer.VectorLayerRenderIntent'); +goog.require('ol.source.Vector'); + + + +/** + * Interaction that allows drawing geometries. + * @param {ol.interaction.DrawOptions} options Options. + * @constructor + * @extends {ol.interaction.Interaction} + */ +ol.interaction.Draw = function(options) { + goog.base(this); + + /** + * Target layer for drawn features. + * @type {ol.layer.Vector} + * @private + */ + this.layer_ = options.layer; + + /** + * Temporary sketch layer. + * @type {ol.layer.Vector} + * @private + */ + this.sketchLayer_ = null; + + /** + * Pixel distance for snapping. + * @type {number} + * @private + */ + this.snapTolerance_ = 15; + + /** + * Draw mode. + * @type {ol.interaction.DrawMode} + * @private + */ + this.mode_ = options.mode; + + /** + * Start coordinate for the feature. + * @type {ol.Coordinate} + * @private + */ + this.startCoordinate_ = null; + + /** + * Sketch feature. + * @type {ol.Feature} + * @private + */ + this.sketchFeature_ = null; + +}; +goog.inherits(ol.interaction.Draw, ol.interaction.Interaction); + + +/** + * @inheritDoc + */ +ol.interaction.Draw.prototype.setMap = function(map) { + var oldMap = this.getMap(); + if (!goog.isNull(oldMap)) { + oldMap.removeLayer(this.sketchLayer_); + } + + if (!goog.isNull(map)) { + if (goog.isNull(this.sketchLayer_)) { + var layer = new ol.layer.Vector({ + source: new ol.source.Vector({parser: null}), + style: this.layer_.getStyle() + }); + layer.setTemporary(true); + this.sketchLayer_ = layer; + } + map.addLayer(this.sketchLayer_); + } else { + // removing from a map, clean up + this.sketchLayer_ = null; + this.startCoordinate_ = null; + this.sketchFeature_ = null; + } + + goog.base(this, 'setMap', map); +}; + + +/** + * @inheritDoc + */ +ol.interaction.Draw.prototype.handleMapBrowserEvent = function(event) { + var map = event.map; + if (!map.isDef()) { + return true; + } + var pass = true; + if (event.type === ol.MapBrowserEvent.EventType.CLICK) { + pass = this.handleClick_(event); + } else if (event.type === ol.MapBrowserEvent.EventType.MOUSEMOVE) { + pass = this.handleMove_(event); + } + return pass; +}; + + +/** + * Handle click events. + * @param {ol.MapBrowserEvent} event A click event. + * @return {boolean} Pass the event to other interactions. + * @private + */ +ol.interaction.Draw.prototype.handleClick_ = function(event) { + if (goog.isNull(this.startCoordinate_)) { + this.startDrawing_(event); + } else if (this.mode_ === ol.interaction.DrawMode.POINT || + this.atStart_(event)) { + this.finishDrawing_(event); + } else { + this.addToDrawing_(event); + } + return false; +}; + + +/** + * Handle mousemove events. + * @param {ol.MapBrowserEvent} event A mousemove event. + * @return {boolean} Pass the event to other interactions. + * @private + */ +ol.interaction.Draw.prototype.handleMove_ = function(event) { + if (this.mode_ === ol.interaction.DrawMode.POINT && + goog.isNull(this.startCoordinate_)) { + this.startDrawing_(event); + } else if (!goog.isNull(this.startCoordinate_)) { + this.modifyDrawing_(event); + } + return true; +}; + + +/** + * Determine if an event is within the snapping tolerance of the start coord. + * @param {ol.MapBrowserEvent} event Event. + * @return {boolean} The event is within the snapping tolerance of the start. + * @private + */ +ol.interaction.Draw.prototype.atStart_ = function(event) { + var map = event.map; + var startPixel = map.getPixelFromCoordinate(this.startCoordinate_); + var pixel = event.getPixel(); + var dx = pixel[0] - startPixel[0]; + var dy = pixel[1] - startPixel[1]; + return Math.sqrt(dx * dx + dy * dy) <= this.snapTolerance_; +}; + + +/** + * Start the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.startDrawing_ = function(event) { + var start = event.getCoordinate(); + this.startCoordinate_ = start; + var feature = new ol.Feature(); + feature.setRenderIntent(ol.layer.VectorLayerRenderIntent.SELECTED); + var geometry; + if (this.mode_ === ol.interaction.DrawMode.POINT) { + geometry = new ol.geom.Point(start.slice()); + } else if (this.mode_ === ol.interaction.DrawMode.LINESTRING) { + geometry = new ol.geom.LineString([start.slice(), start.slice()]); + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + geometry = new ol.geom.Polygon([[start.slice(), start.slice()]]); + } + goog.asserts.assert(goog.isDef(geometry)); + feature.setGeometry(geometry); + this.sketchFeature_ = feature; + this.sketchLayer_.addFeatures([feature]); +}; + + +/** + * Modify the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.modifyDrawing_ = function(event) { + var coordinate = event.getCoordinate(); + var geometry = this.sketchFeature_.getGeometry(); + var coordinates, last; + if (this.mode_ === ol.interaction.DrawMode.POINT) { + last = geometry.getCoordinates(); + last[0] = coordinate[0]; + last[1] = coordinate[1]; + geometry.setCoordinates(last); + } else if (this.mode_ === ol.interaction.DrawMode.LINESTRING) { + coordinates = geometry.getCoordinates(); + last = coordinates[coordinates.length - 1]; + last[0] = coordinate[0]; + last[1] = coordinate[1]; + geometry.setCoordinates(coordinates); + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + var ring = geometry.getRings()[0]; + coordinates = ring.getCoordinates(); + last = coordinates[coordinates.length - 1]; + last[0] = coordinate[0]; + last[1] = coordinate[1]; + ring.setCoordinates(coordinates); + } +}; + + +/** + * Add a new coordinate to the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.addToDrawing_ = function(event) { + var coordinate = event.getCoordinate(); + var geometry = this.sketchFeature_.getGeometry(); + var coordinates, last; + if (this.mode_ === ol.interaction.DrawMode.LINESTRING) { + coordinates = geometry.getCoordinates(); + coordinates.push(coordinate.slice()); + geometry.setCoordinates(coordinates); + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + var ring = geometry.getRings()[0]; + coordinates = ring.getCoordinates(); + coordinates.push(coordinate.slice()); + ring.setCoordinates(coordinates); + } +}; + + +/** + * Finish the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.finishDrawing_ = function(event) { + this.startCoordinate_ = null; + var feature = this.sketchFeature_; + this.sketchLayer_.removeFeatures([feature]); + feature.setRenderIntent(ol.layer.VectorLayerRenderIntent.DEFAULT); + this.layer_.addFeatures([feature]); +}; + + +/** + * Set the drawing mode. + * @param {ol.interaction.DrawMode} mode Draw mode ('point', 'linestring', or + * 'polygon'). + */ +ol.interaction.Draw.prototype.setMode = function(mode) { + this.mode_ = mode; +}; + + +/** + * Draw mode. + * @enum {string} + */ +ol.interaction.DrawMode = { + POINT: 'point', + LINESTRING: 'linestring', + POLYGON: 'polygon' +};