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.
+
+
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'
+};