From a1355ee7668d9b0bb1db2dfbd5d6e16d801f7b84 Mon Sep 17 00:00:00 2001 From: Thomas Chandelle Date: Tue, 23 May 2017 11:04:46 +0200 Subject: [PATCH 1/2] Changes snap example to allow Circle creations --- examples/snap.html | 1 + examples/snap.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/examples/snap.html b/examples/snap.html index 087335a3b5..c47b279255 100644 --- a/examples/snap.html +++ b/examples/snap.html @@ -29,6 +29,7 @@ tags: "draw, edit, modify, vector, snap" + diff --git a/examples/snap.js b/examples/snap.js index a439333c4e..cd1764266d 100644 --- a/examples/snap.js +++ b/examples/snap.js @@ -81,6 +81,8 @@ var Draw = { this.LineString.setActive(false); map.addInteraction(this.Polygon); this.Polygon.setActive(false); + map.addInteraction(this.Circle); + this.Circle.setActive(false); }, Point: new ol.interaction.Draw({ source: vector.getSource(), @@ -94,6 +96,10 @@ var Draw = { source: vector.getSource(), type: /** @type {ol.geom.GeometryType} */ ('Polygon') }), + Circle: new ol.interaction.Draw({ + source: vector.getSource(), + type: /** @type {ol.geom.GeometryType} */ ('Circle') + }), getActive: function() { return this.activeType ? this[this.activeType].getActive() : false; }, From 074fdeb21235ba12881be6e6a8471a11e876541b Mon Sep 17 00:00:00 2001 From: Thomas Chandelle Date: Tue, 23 May 2017 13:11:34 +0200 Subject: [PATCH 2/2] Add snapping abilities on circles --- src/ol/coordinate.js | 31 ++++++++++++++++++ src/ol/interaction/snap.js | 47 ++++++++++++++++++++++++--- test/spec/ol/coordinate.test.js | 14 ++++++++ test/spec/ol/interaction/snap.test.js | 22 +++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/ol/coordinate.js b/src/ol/coordinate.js index eea13ca6c9..e1c7a7bc2c 100644 --- a/src/ol/coordinate.js +++ b/src/ol/coordinate.js @@ -26,6 +26,37 @@ ol.coordinate.add = function(coordinate, delta) { }; +/** + * Calculates the point closest to the passed coordinate on the passed circle. + * + * @param {ol.Coordinate} coordinate The coordinate. + * @param {ol.geom.Circle} circle The circle. + * @return {ol.Coordinate} Closest point on the circumference + */ +ol.coordinate.closestOnCircle = function(coordinate, circle) { + var r = circle.getRadius(); + var center = circle.getCenter(); + var x0 = center[0]; + var y0 = center[1]; + var x1 = coordinate[0]; + var y1 = coordinate[1]; + + var dx = x1 - x0; + var dy = y1 - y0; + if (dx === 0 && dy === 0) { + dx = 1; + } + var d = Math.sqrt(dx * dx + dy * dy); + + var x, y; + + x = x0 + r * dx / d; + y = y0 + r * dy / d; + + return [x, y]; +}; + + /** * Calculates the point closest to the passed coordinate on the passed segment. * This is the foot of the perpendicular of the coordinate to the segment when diff --git a/src/ol/interaction/snap.js b/src/ol/interaction/snap.js index a623d48f77..50344ee36d 100644 --- a/src/ol/interaction/snap.js +++ b/src/ol/interaction/snap.js @@ -8,6 +8,8 @@ goog.require('ol.events'); goog.require('ol.events.EventType'); goog.require('ol.extent'); goog.require('ol.functions'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.Polygon'); goog.require('ol.interaction.Pointer'); goog.require('ol.obj'); goog.require('ol.source.Vector'); @@ -142,7 +144,8 @@ ol.interaction.Snap = function(opt_options) { 'MultiPoint': this.writeMultiPointGeometry_, 'MultiLineString': this.writeMultiLineStringGeometry_, 'MultiPolygon': this.writeMultiPolygonGeometry_, - 'GeometryCollection': this.writeGeometryCollectionGeometry_ + 'GeometryCollection': this.writeGeometryCollectionGeometry_, + 'Circle': this.writeCircleGeometry_ }; }; ol.inherits(ol.interaction.Snap, ol.interaction.Pointer); @@ -345,6 +348,15 @@ ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) { var box = ol.extent.boundingExtent([lowerLeft, upperRight]); var segments = this.rBush_.getInExtent(box); + + // If snapping on vertices only, don't consider circles + if (this.vertex_ && !this.edge_) { + segments = segments.filter(function(segment) { + return segment.feature.getGeometry().getType() !== + ol.geom.GeometryType.CIRCLE; + }); + } + var snappedToVertex = false; var snapped = false; var vertex = null; @@ -354,6 +366,8 @@ ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) { this.pixelCoordinate_ = pixelCoordinate; segments.sort(this.sortByDistance_); var closestSegment = segments[0].segment; + var isCircle = segments[0].feature.getGeometry().getType() === + ol.geom.GeometryType.CIRCLE; if (this.vertex_ && !this.edge_) { pixel1 = map.getPixelFromCoordinate(closestSegment[0]); pixel2 = map.getPixelFromCoordinate(closestSegment[1]); @@ -368,12 +382,17 @@ ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) { vertexPixel = map.getPixelFromCoordinate(vertex); } } else if (this.edge_) { - vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, - closestSegment)); + if (isCircle) { + vertex = ol.coordinate.closestOnCircle(pixelCoordinate, + /** @type {ol.geom.Circle} */ (segments[0].feature.getGeometry())); + } else { + vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + } vertexPixel = map.getPixelFromCoordinate(vertex); if (ol.coordinate.distance(pixel, vertexPixel) <= this.pixelTolerance_) { snapped = true; - if (this.vertex_) { + if (this.vertex_ && !isCircle) { pixel1 = map.getPixelFromCoordinate(closestSegment[0]); pixel2 = map.getPixelFromCoordinate(closestSegment[1]); squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); @@ -410,6 +429,26 @@ ol.interaction.Snap.prototype.updateFeature_ = function(feature) { }; +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Circle} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeCircleGeometry_ = function(feature, geometry) { + var polygon = ol.geom.Polygon.fromCircle(geometry); + var coordinates = polygon.getCoordinates()[0]; + var i, ii, segment, segmentData; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } +}; + + /** * @param {ol.Feature} feature Feature * @param {ol.geom.GeometryCollection} geometry Geometry. diff --git a/test/spec/ol/coordinate.test.js b/test/spec/ol/coordinate.test.js index 526ab837c4..7cf496fb6e 100644 --- a/test/spec/ol/coordinate.test.js +++ b/test/spec/ol/coordinate.test.js @@ -1,6 +1,7 @@ goog.provide('ol.test.coordinate'); goog.require('ol.coordinate'); +goog.require('ol.geom.Circle'); describe('ol.coordinate', function() { @@ -89,6 +90,19 @@ describe('ol.coordinate', function() { }); }); + describe('#closestOnCircle', function() { + var center = [5, 10]; + var circle = new ol.geom.Circle(center, 10); + it('can find the closest point on circle', function() { + expect(ol.coordinate.closestOnCircle([-20, 10], circle)) + .to.eql([-5, 10]); + }); + it('can handle coordinate equal circle center', function() { + expect(ol.coordinate.closestOnCircle(center, circle)) + .to.eql([15, 10]); + }); + }); + describe('#closestOnSegment', function() { it('can handle points where the foot of the perpendicular is closest', function() { diff --git a/test/spec/ol/interaction/snap.test.js b/test/spec/ol/interaction/snap.test.js index 891687585f..e28fa1f67f 100644 --- a/test/spec/ol/interaction/snap.test.js +++ b/test/spec/ol/interaction/snap.test.js @@ -4,6 +4,7 @@ goog.require('ol.Collection'); goog.require('ol.Feature'); goog.require('ol.Map'); goog.require('ol.View'); +goog.require('ol.geom.Circle'); goog.require('ol.geom.Point'); goog.require('ol.geom.LineString'); goog.require('ol.interaction.Snap'); @@ -109,6 +110,27 @@ describe('ol.interaction.Snap', function() { expect(event.coordinate).to.eql([10, 0]); }); + it('snaps to circle', function() { + var circle = new ol.Feature(new ol.geom.Circle([0, 0], 10)); + var snapInteraction = new ol.interaction.Snap({ + features: new ol.Collection([circle]), + pixelTolerance: 5 + }); + snapInteraction.setMap(map); + + var event = { + pixel: [5 + width / 2, height / 2 - 5], + coordinate: [5, 5], + map: map + }; + ol.interaction.Snap.handleEvent_.call(snapInteraction, event); + + expect(event.coordinate).to.eql([ + Math.sin(Math.PI / 4) * 10, + Math.sin(Math.PI / 4) * 10 + ]); + }); + it('handle feature without geometry', function() { var feature = new ol.Feature(); var snapInteraction = new ol.interaction.Snap({