diff --git a/src/ol/interaction/drawinteraction.js b/src/ol/interaction/drawinteraction.js index f828a06eac..274fa87977 100644 --- a/src/ol/interaction/drawinteraction.js +++ b/src/ol/interaction/drawinteraction.js @@ -326,25 +326,23 @@ 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) { - var geometry = sketchFeature.getGeometry(); - var coordinates = geometry.getCoordinates(); + // 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( - [sketchFeature.getGeometry().getCoordinates()])); + sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates])); } else if (this.type_ === ol.geom.GeometryType.MULTILINESTRING) { - sketchFeature.setGeometry( - new ol.geom.MultiLineString( - [sketchFeature.getGeometry().getCoordinates()])); + sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates])); } else if (this.type_ === ol.geom.GeometryType.MULTIPOLYGON) { - sketchFeature.setGeometry( - new ol.geom.MultiPolygon( - [sketchFeature.getGeometry().getCoordinates()])); + sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates])); } this.layer_.addFeatures([sketchFeature]); }; 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');