Skip to content

Commit

Permalink
Merge pull request #6845 from tchandelle/snap-circle
Browse files Browse the repository at this point in the history
Snap on circles
  • Loading branch information
ahocevar committed May 24, 2017
2 parents 0e4d2b5 + 074fdeb commit b6f446e
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 4 deletions.
1 change: 1 addition & 0 deletions examples/snap.html
Expand Up @@ -29,6 +29,7 @@
<option value="Point">Point</option>
<option value="LineString">LineString</option>
<option value="Polygon">Polygon</option>
<option value="Circle">Circle</option>
</select>
</div>
</form>
6 changes: 6 additions & 0 deletions examples/snap.js
Expand Up @@ -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(),
Expand All @@ -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;
},
Expand Down
31 changes: 31 additions & 0 deletions src/ol/coordinate.js
Expand Up @@ -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
Expand Down
47 changes: 43 additions & 4 deletions src/ol/interaction/snap.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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]);
Expand All @@ -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);
Expand Down Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions 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() {
Expand Down Expand Up @@ -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() {
Expand Down
22 changes: 22 additions & 0 deletions test/spec/ol/interaction/snap.test.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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({
Expand Down

0 comments on commit b6f446e

Please sign in to comment.