diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 7279cac26c..624e1a55f2 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -2,6 +2,10 @@ ### v3.6.0 +#### `ol.interaction.Draw` changes + +* The `minPointsPerRing` config option has been renamed to `minPoints`. It is now also available for linestring drawing, not only for polygons. + #### `ol.tilegrid` changes * The `ol.tilegrid.XYZ` constructor has been replaced by a static `ol.tilegrid.createXYZ()` function. The `ol.tilegrid.createXYZ()` function takes the same arguments as the previous `ol.tilegrid.XYZ` constructor, but returns an `ol.tilegrid.TileGrid` instance. diff --git a/examples/draw-features.html b/examples/draw-features.html index e9ec02e794..9c0c883333 100644 --- a/examples/draw-features.html +++ b/examples/draw-features.html @@ -7,7 +7,9 @@ docs: > dropdown above to start drawing. To finish drawing, click the last point. To activate freehand drawing for lines and polygons, hold the `Shift` key. Square drawing is achieved by using Circle mode with a `geometryFunction` - that creates a 4-sided regular polygon instead of a circle. + that creates a 4-sided regular polygon instead of a circle. Box drawing uses a + custom `geometryFunction` that takes start and end point of a line with 2 + points and creates a rectangular box. tags: "draw, edit, freehand, vector" ---
@@ -22,6 +24,7 @@ tags: "draw, edit, freehand, vector" +
diff --git a/examples/draw-features.js b/examples/draw-features.js index 70ef9935ac..b7cb4cec5f 100644 --- a/examples/draw-features.js +++ b/examples/draw-features.js @@ -1,5 +1,6 @@ goog.require('ol.Map'); goog.require('ol.View'); +goog.require('ol.geom.Polygon'); goog.require('ol.interaction.Draw'); goog.require('ol.layer.Tile'); goog.require('ol.layer.Vector'); @@ -51,16 +52,30 @@ var draw; // global so we can remove it later function addInteraction() { var value = typeSelect.value; if (value !== 'None') { - var geometryFunction; + var geometryFunction, maxPoints; if (value === 'Square') { value = 'Circle'; - geometryFunction = - ol.interaction.Draw.createRegularPolygon(4, Math.PI / 4); + geometryFunction = ol.interaction.Draw.createRegularPolygon(4); + } else if (value === 'Box') { + value = 'LineString'; + maxPoints = 2; + geometryFunction = function(coordinates, geometry) { + if (!geometry) { + geometry = new ol.geom.Polygon(null); + } + var start = coordinates[0]; + var end = coordinates[1]; + geometry.setCoordinates([ + [start, [start[0], end[1]], end, [end[0], start[1]], start] + ]); + return geometry; + }; } draw = new ol.interaction.Draw({ source: source, type: /** @type {ol.geom.GeometryType} */ (value), - geometryFunction: geometryFunction + geometryFunction: geometryFunction, + maxPoints: maxPoints }); map.addInteraction(draw); } diff --git a/externs/olx.js b/externs/olx.js index 1711191910..d6b21a87c4 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -2357,7 +2357,8 @@ olx.interaction.DragZoomOptions.prototype.style; * source: (ol.source.Vector|undefined), * snapTolerance: (number|undefined), * type: ol.geom.GeometryType, - * minPointsPerRing: (number|undefined), + * maxPoints: (number|undefined), + * minPoints: (number|undefined), * style: (ol.style.Style|Array.|ol.style.StyleFunction|undefined), * geometryFunction: (ol.interaction.Draw.GeometryFunctionType|undefined), * geometryName: (string|undefined), @@ -2394,7 +2395,7 @@ olx.interaction.DrawOptions.prototype.snapTolerance; /** * Drawing type ('Point', 'LineString', 'Polygon', 'MultiPoint', - * 'MultiLineString', or 'MultiPolygon'). + * 'MultiLineString', 'MultiPolygon' or 'Circle'). * @type {ol.geom.GeometryType} * @api */ @@ -2402,12 +2403,21 @@ olx.interaction.DrawOptions.prototype.type; /** - * The number of points that must be drawn before a polygon ring can be finished. - * Default is `3`. + * The number of points that can be drawn before a polygon ring or line string + * is finished. The default is no restriction. * @type {number|undefined} * @api */ -olx.interaction.DrawOptions.prototype.minPointsPerRing; +olx.interaction.DrawOptions.prototype.maxPoints; + + +/** + * The number of points that must be drawn before a polygon ring or line string + * can be finished. Default is `3` for polygon rings and `2` for line strings. + * @type {number|undefined} + * @api + */ +olx.interaction.DrawOptions.prototype.minPoints; /** diff --git a/src/ol/geom/polygon.js b/src/ol/geom/polygon.js index 4d113ed4e3..676f160dcd 100644 --- a/src/ol/geom/polygon.js +++ b/src/ol/geom/polygon.js @@ -432,6 +432,7 @@ ol.geom.Polygon.fromExtent = function(extent) { * @param {number=} opt_angle Start angle for the first vertex of the polygon in * radians. Default is 0. * @return {ol.geom.Polygon} Polygon geometry. + * @api */ ol.geom.Polygon.fromCircle = function(circle, opt_sides, opt_angle) { var sides = goog.isDef(opt_sides) ? opt_sides : 32; diff --git a/src/ol/interaction/drawinteraction.js b/src/ol/interaction/drawinteraction.js index 2142494328..cf3388a6d8 100644 --- a/src/ol/interaction/drawinteraction.js +++ b/src/ol/interaction/drawinteraction.js @@ -128,15 +128,6 @@ ol.interaction.Draw = function(options) { this.snapTolerance_ = goog.isDef(options.snapTolerance) ? options.snapTolerance : 12; - /** - * The number of points that must be drawn before a polygon ring can be - * finished. The default is 3. - * @type {number} - * @private - */ - this.minPointsPerRing_ = goog.isDef(options.minPointsPerRing) ? - options.minPointsPerRing : 3; - /** * Geometry type. * @type {ol.geom.GeometryType} @@ -151,6 +142,26 @@ ol.interaction.Draw = function(options) { */ this.mode_ = ol.interaction.Draw.getMode_(this.type_); + /** + * The number of points that must be drawn before a polygon ring or line + * string can be finished. The default is 3 for polygon rings and 2 for + * line strings. + * @type {number} + * @private + */ + this.minPoints_ = goog.isDef(options.minPoints) ? + options.minPoints : + (this.mode_ === ol.interaction.DrawMode.POLYGON ? 3 : 2); + + /** + * The number of points that can be drawn before a polygon ring or line string + * is finished. The default is no restriction. + * @type {number} + * @private + */ + this.maxPoints_ = goog.isDef(options.maxPoints) ? + options.maxPoints : Infinity; + var geometryFunction = options.geometryFunction; if (!goog.isDef(geometryFunction)) { if (this.type_ === ol.geom.GeometryType.CIRCLE) { @@ -224,6 +235,13 @@ ol.interaction.Draw = function(options) { */ this.sketchPoint_ = null; + /** + * Sketch coordinates. Used when drawing a line or polygon. + * @type {ol.Coordinate|Array.|Array.>} + * @private + */ + this.sketchCoords_ = null; + /** * Sketch line. Used when drawing polygon. * @type {ol.Feature} @@ -232,11 +250,11 @@ ol.interaction.Draw = function(options) { this.sketchLine_ = null; /** - * Sketch polygon. Used when drawing polygon. - * @type {Array.>} + * Sketch line coordinates. Used when drawing a polygon or circle. + * @type {Array.} * @private */ - this.sketchPolygonCoords_ = null; + this.sketchLineCoords_ = null; /** * Squared tolerance for handling up events. If the squared distance @@ -415,20 +433,15 @@ ol.interaction.Draw.prototype.handlePointerMove_ = function(event) { ol.interaction.Draw.prototype.atFinish_ = function(event) { var at = false; if (!goog.isNull(this.sketchFeature_)) { - var geometry = this.sketchFeature_.getGeometry(); var potentiallyDone = false; var potentiallyFinishCoordinates = [this.finishCoordinate_]; if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString, - 'geometry should be an ol.geom.LineString'); - potentiallyDone = geometry.getCoordinates().length > 2; + potentiallyDone = this.sketchCoords_.length > this.minPoints_; } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, - 'geometry should be an ol.geom.Polygon'); - potentiallyDone = geometry.getCoordinates()[0].length > - this.minPointsPerRing_; - potentiallyFinishCoordinates = [this.sketchPolygonCoords_[0][0], - this.sketchPolygonCoords_[0][this.sketchPolygonCoords_[0].length - 2]]; + potentiallyDone = this.sketchCoords_[0].length > + this.minPoints_; + potentiallyFinishCoordinates = [this.sketchCoords_[0][0], + this.sketchCoords_[0][this.sketchCoords_[0].length - 2]]; } if (potentiallyDone) { var map = event.map; @@ -478,23 +491,22 @@ ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) { ol.interaction.Draw.prototype.startDrawing_ = function(event) { var start = event.coordinate; this.finishCoordinate_ = start; - var geometry; if (this.mode_ === ol.interaction.DrawMode.POINT) { - geometry = this.geometryFunction_(start.slice()); + this.sketchCoords_ = start.slice(); + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + this.sketchCoords_ = [[start.slice(), start.slice()]]; + this.sketchLineCoords_ = this.sketchCoords_[0]; } else { - if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - geometry = this.geometryFunction_([start.slice(), start.slice()]); - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - this.sketchLine_ = new ol.Feature(new ol.geom.LineString([start.slice(), - start.slice()])); - this.sketchPolygonCoords_ = [[start.slice(), start.slice()]]; - geometry = this.geometryFunction_(this.sketchPolygonCoords_); - } else if (this.mode_ === ol.interaction.DrawMode.CIRCLE) { - var sketchCoords = [start.slice(), start.slice()]; - this.sketchLine_ = new ol.Feature(new ol.geom.LineString(sketchCoords)); - geometry = this.geometryFunction_(sketchCoords); + this.sketchCoords_ = [start.slice(), start.slice()]; + if (this.mode_ === ol.interaction.DrawMode.CIRCLE) { + this.sketchLineCoords_ = this.sketchCoords_; } } + if (!goog.isNull(this.sketchLineCoords_)) { + this.sketchLine_ = new ol.Feature( + new ol.geom.LineString(this.sketchLineCoords_)); + } + var geometry = this.geometryFunction_(this.sketchCoords_); goog.asserts.assert(goog.isDef(geometry), 'geometry should be defined'); this.sketchFeature_ = new ol.Feature(); if (goog.isDef(this.geometryName_)) { @@ -517,50 +529,48 @@ ol.interaction.Draw.prototype.modifyDrawing_ = function(event) { var geometry = this.sketchFeature_.getGeometry(); goog.asserts.assertInstanceof(geometry, ol.geom.SimpleGeometry, 'geometry should be ol.geom.SimpleGeometry or subclass'); - var coordinates, last, sketchLineGeom; + var coordinates, last; if (this.mode_ === ol.interaction.DrawMode.POINT) { - last = geometry.getCoordinates(); - last[0] = coordinate[0]; - last[1] = coordinate[1]; - this.geometryFunction_(last, geometry); - } else { - if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - coordinates = geometry.getCoordinates(); - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - coordinates = this.sketchPolygonCoords_[0]; - } else if (this.mode_ === ol.interaction.DrawMode.CIRCLE) { - coordinates = coordinate; - } + last = this.sketchCoords_; + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + coordinates = this.sketchCoords_[0]; + last = coordinates[coordinates.length - 1]; if (this.atFinish_(event)) { // snap to finish coordinate = this.finishCoordinate_.slice(); } + } else { + coordinates = this.sketchCoords_; + last = coordinates[coordinates.length - 1]; + } + last[0] = coordinate[0]; + last[1] = coordinate[1]; + goog.asserts.assert(!goog.isNull(this.sketchCoords_), + 'sketchCoords_ must not be null'); + this.geometryFunction_(this.sketchCoords_, geometry); + if (!goog.isNull(this.sketchPoint_)) { var sketchPointGeom = this.sketchPoint_.getGeometry(); goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point, 'sketchPointGeom should be an ol.geom.Point'); sketchPointGeom.setCoordinates(coordinate); - last = coordinates[coordinates.length - 1]; - last[0] = coordinate[0]; - last[1] = coordinate[1]; - if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - this.geometryFunction_(coordinates, geometry); - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - sketchLineGeom = this.sketchLine_.getGeometry(); - goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString, - 'sketchLineGeom should be an ol.geom.LineString'); - sketchLineGeom.setCoordinates(coordinates); - goog.asserts.assert(!goog.isNull(this.sketchPolygonCoords_), - 'sketchPolygonCoords_ must not be null'); - this.geometryFunction_(this.sketchPolygonCoords_, geometry); - } else if (this.mode_ === ol.interaction.DrawMode.CIRCLE) { - sketchLineGeom = this.sketchLine_.getGeometry(); - goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString, - 'sketchLineGeom should be an ol.geom.LineString'); - var center = sketchLineGeom.getFlatCoordinates().slice(0, 2); - coordinates = [center, coordinate]; - sketchLineGeom.setCoordinates(coordinates); - this.geometryFunction_(coordinates, geometry); + } + var sketchLineGeom; + if (geometry instanceof ol.geom.Polygon && + this.mode_ !== ol.interaction.DrawMode.POLYGON) { + if (goog.isNull(this.sketchLine_)) { + this.sketchLine_ = new ol.Feature(new ol.geom.LineString(null)); } + var ring = geometry.getLinearRing(0); + sketchLineGeom = this.sketchLine_.getGeometry(); + goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString, + 'sketchLineGeom must be an ol.geom.LineString'); + sketchLineGeom.setFlatCoordinates( + ring.getLayout(), ring.getFlatCoordinates()); + } else if (!goog.isNull(this.sketchLineCoords_)) { + sketchLineGeom = this.sketchLine_.getGeometry(); + goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString, + 'sketchLineGeom must be an ol.geom.LineString'); + sketchLineGeom.setCoordinates(this.sketchLineCoords_); } this.updateSketchFeatures_(); }; @@ -574,21 +584,29 @@ ol.interaction.Draw.prototype.modifyDrawing_ = function(event) { ol.interaction.Draw.prototype.addToDrawing_ = function(event) { var coordinate = event.coordinate; var geometry = this.sketchFeature_.getGeometry(); + goog.asserts.assertInstanceof(geometry, ol.geom.SimpleGeometry, + 'geometry must be an ol.geom.SimpleGeometry'); + var done; var coordinates; if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { this.finishCoordinate_ = coordinate.slice(); - goog.asserts.assertInstanceof(geometry, ol.geom.LineString, - 'geometry should be an ol.geom.LineString'); - coordinates = geometry.getCoordinates(); + coordinates = this.sketchCoords_; coordinates.push(coordinate.slice()); + done = coordinates.length > this.maxPoints_; this.geometryFunction_(coordinates, geometry); } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - this.sketchPolygonCoords_[0].push(coordinate.slice()); - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, - 'geometry should be an ol.geom.Polygon'); - this.geometryFunction_(this.sketchPolygonCoords_, geometry); + coordinates = this.sketchCoords_[0]; + coordinates.push(coordinate.slice()); + done = coordinates.length > this.maxPoints_; + if (done) { + this.finishCoordinate_ = coordinates[0]; + } + this.geometryFunction_(this.sketchCoords_, geometry); } this.updateSketchFeatures_(); + if (done) { + this.finishDrawing(); + } }; @@ -602,29 +620,21 @@ ol.interaction.Draw.prototype.finishDrawing = function() { var sketchFeature = this.abortDrawing_(); goog.asserts.assert(!goog.isNull(sketchFeature), 'sketchFeature should not be null'); - var coordinates; + var coordinates = this.sketchCoords_; var geometry = sketchFeature.getGeometry(); - if (this.mode_ === ol.interaction.DrawMode.POINT) { - goog.asserts.assertInstanceof(geometry, ol.geom.Point, - 'geometry should be an ol.geom.Point'); - coordinates = geometry.getCoordinates(); - } else if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString, - 'geometry should be an ol.geom.LineString'); - coordinates = geometry.getCoordinates(); + goog.asserts.assertInstanceof(geometry, ol.geom.SimpleGeometry, + 'geometry must be an ol.geom.SimpleGeometry'); + if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { // remove the redundant last point coordinates.pop(); this.geometryFunction_(coordinates, geometry); } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, - 'geometry should be an ol.geom.Polygon'); // When we finish drawing a polygon on the last point, // the last coordinate is duplicated as for LineString // we force the replacement by the first point - this.sketchPolygonCoords_[0].pop(); - this.sketchPolygonCoords_[0].push(this.sketchPolygonCoords_[0][0]); - this.geometryFunction_(this.sketchPolygonCoords_, geometry); - coordinates = geometry.getCoordinates(); + coordinates[0].pop(); + coordinates[0].push(coordinates[0][0]); + this.geometryFunction_(coordinates, geometry); } // cast multi-part geometries @@ -712,14 +722,13 @@ ol.interaction.Draw.prototype.updateState_ = function() { * @param {number=} opt_sides Number of sides of the regular polygon. Default is * 32. * @param {number=} opt_angle Angle of the first point in radians. 0 means East. - * Default is 0. + * Default is the angle defined by the heading from the center of the + * regular polygon to the current pointer position. * @return {ol.interaction.Draw.GeometryFunctionType} Function that draws a * polygon. * @api */ ol.interaction.Draw.createRegularPolygon = function(opt_sides, opt_angle) { - var sides = goog.isDef(opt_sides) ? opt_sides : 32; - var angle = goog.isDef(opt_angle) ? opt_angle : 0; return ( /** * @param {ol.Coordinate|Array.|Array.>} coordinates @@ -728,12 +737,15 @@ ol.interaction.Draw.createRegularPolygon = function(opt_sides, opt_angle) { */ function(coordinates, opt_geometry) { var center = coordinates[0]; + var end = coordinates[1]; var radius = Math.sqrt( - ol.coordinate.squaredDistance(coordinates[0], coordinates[1])); + ol.coordinate.squaredDistance(center, end)); var geometry = goog.isDef(opt_geometry) ? opt_geometry : - ol.geom.Polygon.fromCircle(new ol.geom.Circle(center), sides); + ol.geom.Polygon.fromCircle(new ol.geom.Circle(center), opt_sides); goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, 'geometry must be a polygon'); + var angle = goog.isDef(opt_angle) ? opt_angle : + Math.atan((end[1] - center[1]) / (end[0] - center[0])); ol.geom.Polygon.makeRegular(geometry, center, radius, angle); return geometry; }