From 05b99228610cc7feaf6c8305ffca2ff9bdc0d29f Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 12 Nov 2013 15:49:03 -0700 Subject: [PATCH] Tests for draw interaction The tests revealed that polygons can be drawn with counter-clockwise exterior ring order. In the polygon constructor, we enforce clockwise winding for exterior rings. So after drawing, we pass polygon coordinates back to the constructor before adding feature to the target layer. --- src/ol/interaction/drawinteraction.js | 20 +- .../ol/interaction/drawinteraction.test.js | 344 ++++++++++++++++++ 2 files changed, 353 insertions(+), 11 deletions(-) create mode 100644 test/spec/ol/interaction/drawinteraction.test.js 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');