diff --git a/examples/draw-features.html b/examples/draw-features.html
new file mode 100644
index 0000000000..5459365499
--- /dev/null
+++ b/examples/draw-features.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+ 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..637035f756
--- /dev/null
+++ b/examples/draw-features.js
@@ -0,0 +1,122 @@
+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
+ }),
+ stroke: new ol.style.Stroke({
+ color: 'white',
+ opacity: 0.75
+ }),
+ size: 14
+ }),
+ new ol.style.Fill({
+ color: '#ffffff',
+ opacity: 0.5
+ }),
+ new ol.style.Stroke({
+ color: 'white',
+ width: 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: '#0099ff',
+ opacity: 1
+ }),
+ stroke: new ol.style.Stroke({
+ color: 'white',
+ opacity: 0.75
+ }),
+ size: 14,
+ zIndex: 1
+ })
+ ]
+ })
+ ],
+ symbolizers: [
+ new ol.style.Shape({
+ fill: new ol.style.Fill({
+ color: '#ffcc33',
+ opacity: 1
+ }),
+ size: 14
+ }),
+ new ol.style.Fill({
+ color: 'white',
+ opacity: 0.2
+ }),
+ new ol.style.Stroke({
+ color: '#ffcc33',
+ width: 2
+ })
+ ]
+ })
+});
+
+var map = new ol.Map({
+ layers: [raster, vector],
+ renderer: ol.RendererHint.CANVAS,
+ target: 'map',
+ view: new ol.View2D({
+ center: [-11000000, 4600000],
+ zoom: 4
+ })
+});
+
+var typeSelect = document.getElementById('type');
+
+var draw; // global so we can remove it later
+function addInteraction() {
+ draw = new ol.interaction.Draw({
+ layer: vector,
+ type: /** @type {ol.geom.GeometryType} */
+ (typeSelect.options[typeSelect.selectedIndex].value)
+ });
+ map.addInteraction(draw);
+}
+
+
+/**
+ * Let user change the geometry type.
+ * @param {Event} e Change event.
+ */
+typeSelect.onchange = function(e) {
+ map.removeInteraction(draw);
+ addInteraction();
+};
+
+addInteraction();
diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index 0c6c3b03fc..e39f3b5156 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -341,6 +341,16 @@
* @todo stability experimental
*/
+/**
+ * @typedef {Object} ol.interaction.DrawOptions
+ * @property {ol.layer.Vector} layer Destination layer for the features.
+ * @property {number|undefined} snapTolerance Pixel distance for snapping to the
+ * drawing finish (default is 12).
+ * @property {ol.geom.GeometryType} type Drawing type ('point', 'linestring',
+ * 'polygon', 'multipoint', 'multilinestring', or 'multipolygon').
+ * @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..274fa87977
--- /dev/null
+++ b/src/ol/interaction/drawinteraction.js
@@ -0,0 +1,405 @@
+goog.provide('ol.interaction.Draw');
+
+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.GeometryType');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
+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_ = goog.isDef(options.snapTolerance) ?
+ options.snapTolerance : 12;
+
+ /**
+ * Geometry type.
+ * @type {ol.geom.GeometryType}
+ * @private
+ */
+ this.type_ = options.type;
+
+ /**
+ * Drawing mode (derived from geometry type.
+ * @type {ol.interaction.DrawMode}
+ * @private
+ */
+ this.mode_ = ol.interaction.Draw.getMode_(this.type_);
+
+ /**
+ * Finish coordinate for the feature (first point for polygons, last point for
+ * linestrings).
+ * @type {ol.Coordinate}
+ * @private
+ */
+ this.finishCoordinate_ = null;
+
+ /**
+ * Sketch feature.
+ * @type {ol.Feature}
+ * @private
+ */
+ this.sketchFeature_ = null;
+
+ /**
+ * Sketch point.
+ * @type {ol.Feature}
+ * @private
+ */
+ this.sketchPoint_ = null;
+
+ /**
+ * Squared tolerance for handling click events. If the squared distance
+ * between a down and click event is greater than this tolerance, click events
+ * will not be handled.
+ * @type {number}
+ * @private
+ */
+ this.squaredClickTolerance_ = 4;
+
+};
+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.abortDrawing_();
+ this.sketchLayer_ = 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);
+ } else if (event.type === ol.MapBrowserEvent.EventType.DBLCLICK) {
+ pass = false;
+ }
+ 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) {
+ var downPx = event.map.getEventPixel(event.target.getDown());
+ var clickPx = event.getPixel();
+ var dx = downPx[0] - clickPx[0];
+ var dy = downPx[1] - clickPx[1];
+ var squaredDistance = dx * dx + dy * dy;
+ var pass = true;
+ if (squaredDistance <= this.squaredClickTolerance_) {
+ if (goog.isNull(this.finishCoordinate_)) {
+ this.startDrawing_(event);
+ } else if (this.mode_ === ol.interaction.DrawMode.POINT ||
+ this.atFinish_(event)) {
+ this.finishDrawing_(event);
+ } else {
+ this.addToDrawing_(event);
+ }
+ pass = false;
+ }
+ return pass;
+};
+
+
+/**
+ * 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.finishCoordinate_)) {
+ this.startDrawing_(event);
+ } else if (!goog.isNull(this.finishCoordinate_)) {
+ 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.atFinish_ = function(event) {
+ var at = false;
+ if (this.sketchFeature_) {
+ var geometry = this.sketchFeature_.getGeometry();
+ var potentiallyDone = false;
+ if (this.mode_ === ol.interaction.DrawMode.LINESTRING) {
+ potentiallyDone = geometry.getCoordinates().length > 2;
+ } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+ potentiallyDone = geometry.getRings()[0].getCoordinates().length > 3;
+ }
+ if (potentiallyDone) {
+ var map = event.map;
+ var finishPixel = map.getPixelFromCoordinate(this.finishCoordinate_);
+ var pixel = event.getPixel();
+ var dx = pixel[0] - finishPixel[0];
+ var dy = pixel[1] - finishPixel[1];
+ at = Math.sqrt(dx * dx + dy * dy) <= this.snapTolerance_;
+ }
+ }
+ return at;
+};
+
+
+/**
+ * Start the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
+ */
+ol.interaction.Draw.prototype.startDrawing_ = function(event) {
+ var start = event.getCoordinate();
+ this.finishCoordinate_ = start;
+ var sketchFeature = new ol.Feature();
+ sketchFeature.setRenderIntent(ol.layer.VectorLayerRenderIntent.SELECTED);
+ var features = [sketchFeature];
+ var geometry;
+ if (this.mode_ === ol.interaction.DrawMode.POINT) {
+ geometry = new ol.geom.Point(start.slice());
+ } else {
+ var sketchPoint = new ol.Feature({
+ geom: new ol.geom.Point(start.slice())
+ });
+ sketchPoint.setRenderIntent(ol.layer.VectorLayerRenderIntent.TEMPORARY);
+ this.sketchPoint_ = sketchPoint;
+ features.push(sketchPoint);
+
+ 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));
+ sketchFeature.setGeometry(geometry);
+ this.sketchFeature_ = sketchFeature;
+
+ this.sketchLayer_.addFeatures(features);
+};
+
+
+/**
+ * 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();
+ } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+ geometry = geometry.getRings()[0];
+ coordinates = geometry.getCoordinates();
+ }
+ if (this.atFinish_(event)) {
+ // snap to finish
+ coordinate = this.finishCoordinate_.slice();
+ }
+ this.sketchPoint_.getGeometry().setCoordinates(coordinate);
+ last = coordinates[coordinates.length - 1];
+ last[0] = coordinate[0];
+ last[1] = coordinate[1];
+ geometry.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) {
+ this.finishCoordinate_ = coordinate.slice();
+ 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);
+ }
+};
+
+
+/**
+ * Stop drawing and add the sketch feature to the target layer.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
+ */
+ol.interaction.Draw.prototype.finishDrawing_ = function(event) {
+ var sketchFeature = this.abortDrawing_();
+ goog.asserts.assert(!goog.isNull(sketchFeature));
+ sketchFeature.setRenderIntent(ol.layer.VectorLayerRenderIntent.DEFAULT);
+ var geometry = sketchFeature.getGeometry();
+ var coordinates = geometry.getCoordinates();
+ if (this.mode_ === ol.interaction.DrawMode.LINESTRING) {
+ // remove the redundant last point
+ coordinates.pop();
+ geometry.setCoordinates(coordinates);
+ } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+ // force clockwise order for exterior ring
+ sketchFeature.setGeometry(new ol.geom.Polygon(coordinates));
+ }
+ // cast multi-part geometries
+ if (this.type_ === ol.geom.GeometryType.MULTIPOINT) {
+ sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates]));
+ } else if (this.type_ === ol.geom.GeometryType.MULTILINESTRING) {
+ sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates]));
+ } else if (this.type_ === ol.geom.GeometryType.MULTIPOLYGON) {
+ sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates]));
+ }
+ this.layer_.addFeatures([sketchFeature]);
+};
+
+
+/**
+ * Stop drawing without adding the sketch feature to the target layer.
+ * @return {ol.Feature} The sketch feature (or null if none).
+ * @private
+ */
+ol.interaction.Draw.prototype.abortDrawing_ = function() {
+ this.finishCoordinate_ = null;
+ var sketchFeature = this.sketchFeature_;
+ if (!goog.isNull(sketchFeature)) {
+ var features = [sketchFeature];
+ this.sketchFeature_ = null;
+ if (this.mode_ !== ol.interaction.DrawMode.POINT) {
+ features.push(this.sketchPoint_);
+ this.sketchPoint_ = null;
+ }
+ this.sketchLayer_.removeFeatures(features);
+ }
+ return sketchFeature;
+};
+
+
+/**
+ * Get the drawing mode. The mode for mult-part geometries is the same as for
+ * their single-part cousins.
+ * @param {ol.geom.GeometryType} type Geometry type.
+ * @return {ol.interaction.DrawMode} Drawing mode.
+ * @private
+ */
+ol.interaction.Draw.getMode_ = function(type) {
+ var mode;
+ if (type === ol.geom.GeometryType.POINT ||
+ type === ol.geom.GeometryType.MULTIPOINT) {
+ mode = ol.interaction.DrawMode.POINT;
+ } else if (type === ol.geom.GeometryType.LINESTRING ||
+ type === ol.geom.GeometryType.MULTILINESTRING) {
+ mode = ol.interaction.DrawMode.LINESTRING;
+ } else if (type === ol.geom.GeometryType.POLYGON ||
+ type === ol.geom.GeometryType.MULTIPOLYGON) {
+ mode = ol.interaction.DrawMode.POLYGON;
+ }
+ goog.asserts.assert(goog.isDef(mode));
+ return mode;
+};
+
+
+/**
+ * Draw mode. This collapses multi-part geometry types with their single-part
+ * cousins.
+ * @enum {string}
+ */
+ol.interaction.DrawMode = {
+ POINT: 'point',
+ LINESTRING: 'linestring',
+ POLYGON: 'polygon'
+};
diff --git a/src/ol/layer/vectorlayerrenderintent.js b/src/ol/layer/vectorlayerrenderintent.js
index 757fef5aba..f877dc1732 100644
--- a/src/ol/layer/vectorlayerrenderintent.js
+++ b/src/ol/layer/vectorlayerrenderintent.js
@@ -7,5 +7,6 @@ goog.provide('ol.layer.VectorLayerRenderIntent');
ol.layer.VectorLayerRenderIntent = {
DEFAULT: 'default',
HIDDEN: 'hidden',
- SELECTED: 'selected'
+ SELECTED: 'selected',
+ TEMPORARY: 'temporary'
};
diff --git a/src/ol/map.exports b/src/ol/map.exports
index d6dd963a75..68ff166ae7 100644
--- a/src/ol/map.exports
+++ b/src/ol/map.exports
@@ -1,5 +1,6 @@
@exportClass ol.Map ol.MapOptions
@exportProperty ol.Map.prototype.addControl
+@exportProperty ol.Map.prototype.addInteraction
@exportProperty ol.Map.prototype.addLayer
@exportProperty ol.Map.prototype.addOverlay
@exportProperty ol.Map.prototype.beforeRender
@@ -14,6 +15,7 @@
@exportProperty ol.Map.prototype.getRenderer
@exportProperty ol.Map.prototype.getViewport
@exportProperty ol.Map.prototype.removeControl
+@exportProperty ol.Map.prototype.removeInteraction
@exportProperty ol.Map.prototype.removeLayer
@exportProperty ol.Map.prototype.removeOverlay
@exportProperty ol.Map.prototype.updateSize
diff --git a/test/spec/ol/interaction/drawinteraction.test.js b/test/spec/ol/interaction/drawinteraction.test.js
new file mode 100644
index 0000000000..f4c27f6b42
--- /dev/null
+++ b/test/spec/ol/interaction/drawinteraction.test.js
@@ -0,0 +1,344 @@
+goog.provide('ol.test.interaction.Draw');
+
+describe('ol.interaction.Draw', function() {
+ var target, map, vector;
+
+ var width = 360;
+ var height = 180;
+
+ beforeEach(function() {
+ target = document.createElement('div');
+ var style = target.style;
+ style.position = 'absolute';
+ style.left = '-1000px';
+ style.top = '-1000px';
+ style.width = width + 'px';
+ style.height = height + 'px';
+ document.body.appendChild(target);
+ vector = new ol.layer.Vector({source: new ol.source.Vector({})});
+ map = new ol.Map({
+ target: target,
+ renderer: ol.RendererHint.CANVAS,
+ layers: [vector],
+ view: new ol.View2D({
+ projection: 'EPSG:4326',
+ center: [0, 0],
+ resolution: 1
+ })
+ });
+ });
+
+ afterEach(function() {
+ goog.dispose(map);
+ document.body.removeChild(target);
+ });
+
+ /**
+ * Simulates a browser event on the map viewport. The client x/y location
+ * will be adjusted as if the map were centered at 0,0.
+ * @param {string} type Event type.
+ * @param {number} x Horizontal offset from map center.
+ * @param {number} y Vertical offset from map center.
+ */
+ function simulateEvent(type, x, y) {
+ var viewport = map.getViewport();
+ // calculated in case body has top < 0 (test runner with small window)
+ var position = goog.style.getClientPosition(viewport);
+ var event = new goog.events.BrowserEvent({
+ type: type,
+ clientX: position.x + x + width / 2,
+ clientY: position.y + y + height / 2
+ });
+ goog.events.fireListeners(viewport, type, false, event);
+ }
+
+ describe('constructor', function() {
+
+ it('creates a new interaction', function() {
+ var draw = new ol.interaction.Draw({
+ layer: vector,
+ type: ol.geom.GeometryType.POINT
+ });
+ expect(draw).to.be.a(ol.interaction.Draw);
+ expect(draw).to.be.a(ol.interaction.Interaction);
+ });
+
+ });
+
+ describe('drawing points', function() {
+
+ beforeEach(function() {
+ map.addInteraction(new ol.interaction.Draw({
+ layer: vector,
+ type: ol.geom.GeometryType.POINT
+ }));
+ });
+
+ it('draws a point on click', function() {
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mouseup', 10, 20);
+ simulateEvent('click', 10, 20);
+ var features = vector.getFeatures();
+ expect(features).to.have.length(1);
+ var geometry = features[0].getGeometry();
+ expect(geometry).to.be.a(ol.geom.Point);
+ expect(geometry.getCoordinates()).to.eql([10, -20]);
+ });
+
+ it('does not draw a point with a significant drag', function() {
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mousemove', 15, 20);
+ simulateEvent('mouseup', 15, 20);
+ simulateEvent('click', 15, 20);
+ var features = vector.getFeatures();
+ expect(features).to.have.length(0);
+ });
+
+ });
+
+ describe('drawing multipoints', function() {
+
+ beforeEach(function() {
+ map.addInteraction(new ol.interaction.Draw({
+ layer: vector,
+ type: ol.geom.GeometryType.MULTIPOINT
+ }));
+ });
+
+ it('draws multipoint on click', function() {
+ simulateEvent('mousemove', 30, 15);
+ simulateEvent('mousedown', 30, 15);
+ simulateEvent('mouseup', 30, 15);
+ simulateEvent('click', 30, 15);
+ var features = vector.getFeatures();
+ expect(features).to.have.length(1);
+ var geometry = features[0].getGeometry();
+ expect(geometry).to.be.a(ol.geom.MultiPoint);
+ expect(geometry.getCoordinates()).to.eql([[30, -15]]);
+ });
+
+ });
+
+ describe('drawing linestrings', function() {
+
+ beforeEach(function() {
+ map.addInteraction(new ol.interaction.Draw({
+ layer: vector,
+ type: ol.geom.GeometryType.LINESTRING
+ }));
+ });
+
+ it('draws linestring with clicks, finishing on last point', function() {
+ // first point
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mouseup', 10, 20);
+ simulateEvent('click', 10, 20);
+
+ // second point
+ simulateEvent('mousemove', 30, 20);
+ simulateEvent('mousedown', 30, 20);
+ simulateEvent('mouseup', 30, 20);
+ simulateEvent('click', 30, 20);
+
+ // finish on second point
+ simulateEvent('mousedown', 30, 20);
+ simulateEvent('mouseup', 30, 20);
+ simulateEvent('click', 30, 20);
+
+ var features = vector.getFeatures();
+ expect(features).to.have.length(1);
+ var geometry = features[0].getGeometry();
+ expect(geometry).to.be.a(ol.geom.LineString);
+ expect(geometry.getCoordinates()).to.eql([[10, -20], [30, -20]]);
+ });
+
+ it('does not add a point with a significant drag', function() {
+ // first point
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mouseup', 10, 20);
+ simulateEvent('click', 10, 20);
+
+ // drag map
+ simulateEvent('mousemove', 15, 20);
+ simulateEvent('mousedown', 15, 20);
+ simulateEvent('mousemove', 20, 20);
+ simulateEvent('mouseup', 20, 20);
+ simulateEvent('click', 20, 20);
+
+
+ // second point
+ simulateEvent('mousemove', 30, 20);
+ simulateEvent('mousedown', 30, 20);
+ simulateEvent('mouseup', 30, 20);
+ simulateEvent('click', 30, 20);
+
+ // finish on second point
+ simulateEvent('mousedown', 30, 20);
+ simulateEvent('mouseup', 30, 20);
+ simulateEvent('click', 30, 20);
+
+ var features = vector.getFeatures();
+ expect(features).to.have.length(1);
+ var geometry = features[0].getGeometry();
+ expect(geometry).to.be.a(ol.geom.LineString);
+ expect(geometry.getCoordinates()).to.eql([[10, -20], [30, -20]]);
+ });
+
+ });
+
+ describe('drawing multi-linestrings', function() {
+
+ beforeEach(function() {
+ map.addInteraction(new ol.interaction.Draw({
+ layer: vector,
+ type: ol.geom.GeometryType.MULTILINESTRING
+ }));
+ });
+
+ it('draws multi with clicks, finishing on last point', function() {
+ // first point
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mouseup', 10, 20);
+ simulateEvent('click', 10, 20);
+
+ // second point
+ simulateEvent('mousemove', 30, 20);
+ simulateEvent('mousedown', 30, 20);
+ simulateEvent('mouseup', 30, 20);
+ simulateEvent('click', 30, 20);
+
+ // finish on second point
+ simulateEvent('mousedown', 30, 20);
+ simulateEvent('mouseup', 30, 20);
+ simulateEvent('click', 30, 20);
+
+ var features = vector.getFeatures();
+ expect(features).to.have.length(1);
+ var geometry = features[0].getGeometry();
+ expect(geometry).to.be.a(ol.geom.MultiLineString);
+ expect(geometry.getCoordinates()).to.eql([[[10, -20], [30, -20]]]);
+ });
+
+ });
+
+ describe('drawing polygons', function() {
+
+ beforeEach(function() {
+ map.addInteraction(new ol.interaction.Draw({
+ layer: vector,
+ type: ol.geom.GeometryType.POLYGON
+ }));
+ });
+
+ it('draws polygon with clicks, finishing on first point', function() {
+ // first point
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mouseup', 10, 20);
+ simulateEvent('click', 10, 20);
+
+ // second point
+ simulateEvent('mousemove', 30, 20);
+ simulateEvent('mousedown', 30, 20);
+ simulateEvent('mouseup', 30, 20);
+ simulateEvent('click', 30, 20);
+
+ // third point
+ simulateEvent('mousemove', 30, 10);
+ simulateEvent('mousedown', 30, 10);
+ simulateEvent('mouseup', 30, 10);
+ simulateEvent('click', 30, 10);
+
+ // finish on first point
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mouseup', 10, 20);
+ simulateEvent('click', 10, 20);
+
+ var features = vector.getFeatures();
+ expect(features).to.have.length(1);
+ var geometry = features[0].getGeometry();
+ expect(geometry).to.be.a(ol.geom.Polygon);
+
+ // note that order is forced clockwise (despite drawing counter-clockwise)
+ expect(geometry.getCoordinates()).to.eql([
+ [[10, -20], [30, -10], [30, -20], [10, -20]]
+ ]);
+ });
+
+ });
+
+ describe('drawing multi-polygons', function() {
+
+ beforeEach(function() {
+ map.addInteraction(new ol.interaction.Draw({
+ layer: vector,
+ type: ol.geom.GeometryType.MULTIPOLYGON
+ }));
+ });
+
+ it('draws multi with clicks, finishing on first point', function() {
+ // first point
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mouseup', 10, 20);
+ simulateEvent('click', 10, 20);
+
+ // second point
+ simulateEvent('mousemove', 30, 20);
+ simulateEvent('mousedown', 30, 20);
+ simulateEvent('mouseup', 30, 20);
+ simulateEvent('click', 30, 20);
+
+ // third point
+ simulateEvent('mousemove', 30, 10);
+ simulateEvent('mousedown', 30, 10);
+ simulateEvent('mouseup', 30, 10);
+ simulateEvent('click', 30, 10);
+
+ // finish on first point
+ simulateEvent('mousemove', 10, 20);
+ simulateEvent('mousedown', 10, 20);
+ simulateEvent('mouseup', 10, 20);
+ simulateEvent('click', 10, 20);
+
+ var features = vector.getFeatures();
+ expect(features).to.have.length(1);
+ var geometry = features[0].getGeometry();
+ expect(geometry).to.be.a(ol.geom.MultiPolygon);
+ var coordinates = geometry.getCoordinates();
+ expect(coordinates).to.have.length(1);
+
+ // note that order is forced clockwise (despite drawing counter-clockwise)
+ expect(coordinates[0]).to.eql([
+ [[10, -20], [30, -10], [30, -20], [10, -20]]
+ ]);
+ });
+
+ });
+
+});
+
+goog.require('goog.dispose');
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.style');
+goog.require('ol.Map');
+goog.require('ol.RendererHint');
+goog.require('ol.View2D');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.interaction.Draw');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');