Merge pull request #6457 from tst-ppenev/add-circle-support

Add Circle Modification
This commit is contained in:
Andreas Hocevar
2017-02-06 21:14:49 +01:00
committed by GitHub
5 changed files with 260 additions and 35 deletions

View File

@@ -13,5 +13,6 @@ tags: "draw, edit, modify, vector, featureoverlay"
<option value="Point">Point</option>
<option value="LineString">LineString</option>
<option value="Polygon">Polygon</option>
<option value="Circle">Circle</option>
</select>
</form>

View File

@@ -250,6 +250,16 @@ ol.coordinate.squaredDistance = function(coord1, coord2) {
};
/**
* @param {ol.Coordinate} coord1 First coordinate.
* @param {ol.Coordinate} coord2 Second coordinate.
* @return {number} Distance between coord1 and coord2.
*/
ol.coordinate.distance = function(coord1, coord2) {
return Math.sqrt(ol.coordinate.squaredDistance(coord1, coord2));
};
/**
* Calculate the squared distance from a coordinate to a line segment.
*

View File

@@ -164,6 +164,7 @@ ol.interaction.Modify = function(options) {
'MultiPoint': this.writeMultiPointGeometry_,
'MultiLineString': this.writeMultiLineStringGeometry_,
'MultiPolygon': this.writeMultiPolygonGeometry_,
'Circle': this.writeCircleGeometry_,
'GeometryCollection': this.writeGeometryCollectionGeometry_
};
@@ -189,6 +190,19 @@ ol.interaction.Modify = function(options) {
ol.inherits(ol.interaction.Modify, ol.interaction.Pointer);
/**
* @define {number} The segment index assigned to a circle's center when
* breaking up a cicrle into ModifySegmentDataType segments.
*/
ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX = 0;
/**
* @define {number} The segment index assigned to a circle's circumference when
* breaking up a circle into ModifySegmentDataType segments.
*/
ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX = 1;
/**
* @param {ol.Feature} feature Feature.
* @private
@@ -449,6 +463,38 @@ ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ = function(feature, g
};
/**
* We convert a circle into two segments. The segment at index
* {@link ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX} is the
* circle's center (a point). The segment at index
* {@link ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX} is
* the circumference, and is not a line segment.
*
* @param {ol.Feature} feature Feature.
* @param {ol.geom.Circle} geometry Geometry.
* @private
*/
ol.interaction.Modify.prototype.writeCircleGeometry_ = function(feature, geometry) {
var coordinates = geometry.getCenter();
var centerSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
feature: feature,
geometry: geometry,
index: ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX,
segment: [coordinates, coordinates]
});
var circumferenceSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
feature: feature,
geometry: geometry,
index: ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX,
segment: [coordinates, coordinates]
});
var featureSegments = [centerSegmentData, circumferenceSegmentData];
centerSegmentData.featureSegments = circumferenceSegmentData.featureSegments = featureSegments;
this.rBush_.insert(ol.extent.createOrUpdateFromCoordinate(coordinates), centerSegmentData);
this.rBush_.insert(geometry.getExtent(), circumferenceSegmentData);
};
/**
* @param {ol.Feature} feature Feature
* @param {ol.geom.GeometryCollection} geometry Geometry.
@@ -526,7 +572,15 @@ ol.interaction.Modify.handleDownEvent_ = function(evt) {
if (!componentSegments[uid]) {
componentSegments[uid] = new Array(2);
}
if (ol.coordinate.equals(segment[0], vertex) &&
if (segmentDataMatch.geometry.getType() === ol.geom.GeometryType.CIRCLE &&
segmentDataMatch.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
var closestVertex = ol.interaction.Modify.closestOnSegmentData_(vertex, segmentDataMatch);
if (ol.coordinate.equals(closestVertex, vertex) && !componentSegments[uid][0]) {
this.dragSegments_.push([segmentDataMatch, 0]);
componentSegments[uid][0] = segmentDataMatch;
}
} else if (ol.coordinate.equals(segment[0], vertex) &&
!componentSegments[uid][0]) {
this.dragSegments_.push([segmentDataMatch, 0]);
componentSegments[uid][0] = segmentDataMatch;
@@ -576,7 +630,7 @@ ol.interaction.Modify.handleDragEvent_ = function(evt) {
var segmentData = dragSegment[0];
var depth = segmentData.depth;
var geometry = segmentData.geometry;
var coordinates = geometry.getCoordinates();
var coordinates;
var segment = segmentData.segment;
var index = dragSegment[1];
@@ -590,31 +644,50 @@ ol.interaction.Modify.handleDragEvent_ = function(evt) {
segment[0] = segment[1] = vertex;
break;
case ol.geom.GeometryType.MULTI_POINT:
coordinates = geometry.getCoordinates();
coordinates[segmentData.index] = vertex;
segment[0] = segment[1] = vertex;
break;
case ol.geom.GeometryType.LINE_STRING:
coordinates = geometry.getCoordinates();
coordinates[segmentData.index + index] = vertex;
segment[index] = vertex;
break;
case ol.geom.GeometryType.MULTI_LINE_STRING:
coordinates = geometry.getCoordinates();
coordinates[depth[0]][segmentData.index + index] = vertex;
segment[index] = vertex;
break;
case ol.geom.GeometryType.POLYGON:
coordinates = geometry.getCoordinates();
coordinates[depth[0]][segmentData.index + index] = vertex;
segment[index] = vertex;
break;
case ol.geom.GeometryType.MULTI_POLYGON:
coordinates = geometry.getCoordinates();
coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex;
segment[index] = vertex;
break;
case ol.geom.GeometryType.CIRCLE:
segment[0] = segment[1] = vertex;
if (segmentData.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX) {
this.changingFeature_ = true;
geometry.setCenter(vertex);
this.changingFeature_ = false;
} else { // We're dragging the circle's circumference:
this.changingFeature_ = true;
geometry.setRadius(ol.coordinate.distance(geometry.getCenter(), vertex));
this.changingFeature_ = false;
}
break;
default:
// pass
}
if (coordinates) {
this.setGeometryCoordinates_(geometry, coordinates);
}
}
this.createOrUpdateVertexFeature_(vertex);
};
@@ -627,11 +700,24 @@ ol.interaction.Modify.handleDragEvent_ = function(evt) {
*/
ol.interaction.Modify.handleUpEvent_ = function(evt) {
var segmentData;
var geometry;
for (var i = this.dragSegments_.length - 1; i >= 0; --i) {
segmentData = this.dragSegments_[i][0];
geometry = segmentData.geometry;
if (geometry.getType() === ol.geom.GeometryType.CIRCLE) {
// Update a circle object in the R* bush:
var coordinates = geometry.getCenter();
var centerSegmentData = segmentData.featureSegments[0];
var circumferenceSegmentData = segmentData.featureSegments[1];
centerSegmentData.segment[0] = centerSegmentData.segment[1] = coordinates;
circumferenceSegmentData.segment[0] = circumferenceSegmentData.segment[1] = coordinates;
this.rBush_.update(ol.extent.createOrUpdateFromCoordinate(coordinates), centerSegmentData);
this.rBush_.update(geometry.getExtent(), circumferenceSegmentData);
} else {
this.rBush_.update(ol.extent.boundingExtent(segmentData.segment),
segmentData);
}
}
if (this.modified_) {
this.dispatchEvent(new ol.interaction.Modify.Event(
ol.interaction.ModifyEventType.MODIFYEND, this.features_, evt));
@@ -697,8 +783,8 @@ ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) {
var pixelCoordinate = map.getCoordinateFromPixel(pixel);
var sortByDistance = function(a, b) {
return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) -
ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment);
return ol.interaction.Modify.pointDistanceToSegmentDataSquared_(pixelCoordinate, a) -
ol.interaction.Modify.pointDistanceToSegmentDataSquared_(pixelCoordinate, b);
};
var box = ol.extent.buffer(
@@ -711,24 +797,29 @@ ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) {
nodes.sort(sortByDistance);
var node = nodes[0];
var closestSegment = node.segment;
var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
closestSegment));
var vertex = ol.interaction.Modify.closestOnSegmentData_(pixelCoordinate, node);
var vertexPixel = map.getPixelFromCoordinate(vertex);
if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
this.pixelTolerance_) {
var dist = ol.coordinate.distance(pixel, vertexPixel);
if (dist <= this.pixelTolerance_) {
var vertexSegments = {};
if (node.geometry.getType() === ol.geom.GeometryType.CIRCLE &&
node.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
this.snappedToVertex_ = true;
this.createOrUpdateVertexFeature_(vertex);
} else {
var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
this.snappedToVertex_ = dist <= this.pixelTolerance_;
if (this.snappedToVertex_) {
vertex = squaredDist1 > squaredDist2 ?
closestSegment[1] : closestSegment[0];
}
this.createOrUpdateVertexFeature_(vertex);
var vertexSegments = {};
vertexSegments[ol.getUid(closestSegment)] = true;
var segment;
for (var i = 1, ii = nodes.length; i < ii; ++i) {
segment = nodes[i].segment;
@@ -741,6 +832,9 @@ ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) {
break;
}
}
}
vertexSegments[ol.getUid(closestSegment)] = true;
this.vertexSegments_ = vertexSegments;
return;
}
@@ -752,6 +846,52 @@ ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) {
};
/**
* Returns the distance from a point to a line segment.
*
* @param {ol.Coordinate} pointCoordinates The coordinates of the point from
* which to calculate the distance.
* @param {ol.ModifySegmentDataType} segmentData The object describing the line
* segment we are calculating the distance to.
* @return {number} The square of the distance between a point and a line segment.
*/
ol.interaction.Modify.pointDistanceToSegmentDataSquared_ = function(pointCoordinates, segmentData) {
var geometry = segmentData.geometry;
if (geometry.getType() === ol.geom.GeometryType.CIRCLE) {
var circleGeometry = /** @type {ol.geom.Circle} */ (geometry);
if (segmentData.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
var distanceToCenterSquared =
ol.coordinate.squaredDistance(circleGeometry.getCenter(), pointCoordinates);
var distanceToCircumference =
Math.sqrt(distanceToCenterSquared) - circleGeometry.getRadius();
return distanceToCircumference * distanceToCircumference;
}
}
return ol.coordinate.squaredDistanceToSegment(pointCoordinates, segmentData.segment);
};
/**
* Returns the point closest to a given line segment.
*
* @param {ol.Coordinate} pointCoordinates The point to which a closest point
* should be found.
* @param {ol.ModifySegmentDataType} segmentData The object describing the line
* segment which should contain the closest point.
* @return {ol.Coordinate} The point closest to the specified line segment.
*/
ol.interaction.Modify.closestOnSegmentData_ = function(pointCoordinates, segmentData) {
var geometry = segmentData.geometry;
if (geometry.getType() === ol.geom.GeometryType.CIRCLE &&
segmentData.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
return geometry.getClosestPoint(pointCoordinates);
}
return ol.coordinate.closestOnSegment(pointCoordinates, segmentData.segment);
};
/**
* @param {ol.ModifySegmentDataType} segmentData Segment data.
* @param {ol.Coordinate} vertex Vertex.

View File

@@ -370,7 +370,8 @@ ol.Transform;
* feature: ol.Feature,
* geometry: ol.geom.SimpleGeometry,
* index: (number),
* segment: Array.<ol.Extent>}}
* segment: Array.<ol.Extent>,
* featureSegments: (Array.<ol.ModifySegmentDataType>|undefined)}}
*/
ol.ModifySegmentDataType;

View File

@@ -370,6 +370,39 @@ describe('ol.interaction.Modify', function() {
});
describe('circle modification', function() {
it('changes the circle radius and center', function() {
var circleFeature = new ol.Feature(new ol.geom.Circle([10, 10], 20));
features.length = 0;
features.push(circleFeature);
var modify = new ol.interaction.Modify({
features: new ol.Collection(features)
});
map.addInteraction(modify);
// Change center
simulateEvent('pointermove', 10, -10, false, 0);
simulateEvent('pointerdown', 10, -10, false, 0);
simulateEvent('pointermove', 5, -5, false, 0);
simulateEvent('pointerdrag', 5, -5, false, 0);
simulateEvent('pointerup', 5, -5, false, 0);
expect(circleFeature.getGeometry().getRadius()).to.equal(20);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
// Increase radius
simulateEvent('pointermove', 25, -5, false, 0);
simulateEvent('pointerdown', 25, -5, false, 0);
simulateEvent('pointermove', 30, -5, false, 0);
simulateEvent('pointerdrag', 30, -5, false, 0);
simulateEvent('pointerup', 30, -5, false, 0);
expect(circleFeature.getGeometry().getRadius()).to.equal(25);
expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]);
});
});
describe('boundary modification', function() {
var modify, feature, events;
@@ -539,7 +572,47 @@ describe('ol.interaction.Modify', function() {
};
});
it('updates the segment data', function() {
it('updates circle segment data', function() {
var feature = new ol.Feature(new ol.geom.Circle([10, 10], 20));
features.length = 0;
features.push(feature);
var modify = new ol.interaction.Modify({
features: new ol.Collection(features)
});
map.addInteraction(modify);
var listeners;
listeners = getListeners(feature, modify);
expect(listeners).to.have.length(1);
var firstSegmentData;
firstSegmentData = modify.rBush_.forEachInExtent([0, 0, 5, 5],
function(node) {
return node;
});
expect(firstSegmentData.segment[0]).to.eql([10, 10]);
expect(firstSegmentData.segment[1]).to.eql([10, 10]);
var center = feature.getGeometry().getCenter();
center[0] = 1;
center[1] = 1;
feature.getGeometry().setCenter(center);
firstSegmentData = modify.rBush_.forEachInExtent([0, 0, 5, 5],
function(node) {
return node;
});
expect(firstSegmentData.segment[0]).to.eql([1, 1]);
expect(firstSegmentData.segment[1]).to.eql([1, 1]);
listeners = getListeners(feature, modify);
expect(listeners).to.have.length(1);
});
it('updates polygon segment data', function() {
var modify = new ol.interaction.Modify({
features: new ol.Collection(features)
});