The RTree cannot work with NaN coordinates. So instead of preparing a vertexFeature before we know a coordinate, we now lazily create the vertexFeature.
404 lines
14 KiB
JavaScript
404 lines
14 KiB
JavaScript
goog.provide('ol.interaction.Modify');
|
|
|
|
goog.require('goog.array');
|
|
goog.require('goog.asserts');
|
|
goog.require('goog.events');
|
|
goog.require('goog.object');
|
|
goog.require('ol.Feature');
|
|
goog.require('ol.MapBrowserEvent.EventType');
|
|
goog.require('ol.ViewHint');
|
|
goog.require('ol.coordinate');
|
|
goog.require('ol.extent');
|
|
goog.require('ol.geom.AbstractCollection');
|
|
goog.require('ol.geom.LineString');
|
|
goog.require('ol.geom.LinearRing');
|
|
goog.require('ol.geom.Point');
|
|
goog.require('ol.geom.Polygon');
|
|
goog.require('ol.interaction.Drag');
|
|
goog.require('ol.layer.Vector');
|
|
goog.require('ol.layer.VectorEventType');
|
|
goog.require('ol.layer.VectorLayerRenderIntent');
|
|
goog.require('ol.structs.RTree');
|
|
|
|
|
|
|
|
/**
|
|
* @constructor
|
|
* @extends {ol.interaction.Drag}
|
|
* @param {ol.interaction.ModifyOptions=} opt_options Options.
|
|
*/
|
|
ol.interaction.Modify = function(opt_options) {
|
|
goog.base(this);
|
|
|
|
var options = goog.isDef(opt_options) ? opt_options : {};
|
|
|
|
/**
|
|
* @type {null|function(ol.layer.Layer):boolean}
|
|
* @private
|
|
*/
|
|
this.layerFilter_ = goog.isDef(options.layerFilter) ?
|
|
options.layerFilter : null;
|
|
|
|
/**
|
|
* @type {Array.<ol.layer.Layer>}
|
|
* @private
|
|
*/
|
|
this.layers_ = null;
|
|
|
|
/**
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this.modifiable_ = false;
|
|
|
|
/**
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ?
|
|
options.pixelTolerance : 20;
|
|
|
|
/**
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this.dragSegments_ = null;
|
|
|
|
this.interactingHint = 0;
|
|
};
|
|
goog.inherits(ol.interaction.Modify, ol.interaction.Drag);
|
|
|
|
|
|
/**
|
|
* @param {ol.layer.Vector} layer The vector layer.
|
|
* @param {Array.<ol.Feature>} features Array of features.
|
|
* @private
|
|
*/
|
|
ol.interaction.Modify.prototype.addIndex_ = function(layer, features) {
|
|
for (var i = 0, ii = features.length; i < ii; ++i) {
|
|
var feature = features[i];
|
|
var geometry = feature.getGeometry();
|
|
if (geometry instanceof ol.geom.AbstractCollection) {
|
|
for (var j = 0, jj = geometry.components.length; j < jj; ++j) {
|
|
this.addSegments_(layer, feature, geometry.components[j]);
|
|
}
|
|
} else {
|
|
this.addSegments_(layer, feature, geometry);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Listen for feature additions.
|
|
* @param {ol.layer.VectorEvent} evt Event object.
|
|
* @private
|
|
*/
|
|
ol.interaction.Modify.prototype.handleFeaturesAdded_ = function(evt) {
|
|
goog.asserts.assertInstanceof(evt.target, ol.layer.Vector);
|
|
this.addIndex_(/** @type {ol.layer.Vector} */ (evt.target), evt.features);
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {ol.layer.Layer} layer Layer.
|
|
*/
|
|
ol.interaction.Modify.prototype.addLayer = function(layer) {
|
|
var selectionData = layer.getSelectionData();
|
|
var selectionLayer = selectionData.layer;
|
|
var editData = selectionLayer.getEditData();
|
|
if (goog.isNull(editData.rTree)) {
|
|
editData.rTree = new ol.structs.RTree();
|
|
}
|
|
this.addIndex_(selectionLayer,
|
|
goog.object.getValues(selectionData.selectedFeaturesByFeatureUid));
|
|
|
|
goog.events.listen(selectionLayer, ol.layer.VectorEventType.ADD,
|
|
this.handleFeaturesAdded_, false, this);
|
|
goog.events.listen(selectionLayer, ol.layer.VectorEventType.REMOVE,
|
|
this.handleFeaturesRemoved_, false, this);
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {ol.layer.Vector} selectionLayer Selection layer.
|
|
* @param {ol.Feature} feature Feature to add segments for.
|
|
* @param {ol.geom.Geometry} geometry Geometry to add segments for.
|
|
* @private
|
|
*/
|
|
ol.interaction.Modify.prototype.addSegments_ =
|
|
function(selectionLayer, feature, geometry) {
|
|
var uid = goog.getUid(feature);
|
|
var rTree = selectionLayer.getEditData().rTree;
|
|
var vertex, segment, segmentData, coordinates;
|
|
if (geometry instanceof ol.geom.Point) {
|
|
vertex = geometry.getCoordinates();
|
|
segmentData = [[vertex, vertex], feature, geometry, NaN];
|
|
rTree.insert(geometry.getBounds(), segmentData, uid);
|
|
} else if (geometry instanceof ol.geom.LineString ||
|
|
geometry instanceof ol.geom.LinearRing) {
|
|
coordinates = geometry.getCoordinates();
|
|
for (var i = 0, ii = coordinates.length - 1; i < ii; ++i) {
|
|
segment = coordinates.slice(i, i + 2);
|
|
segmentData = [segment, feature, geometry, i];
|
|
rTree.insert(ol.extent.boundingExtent(segment), segmentData, uid);
|
|
}
|
|
} else if (geometry instanceof ol.geom.Polygon) {
|
|
var rings = geometry.getRings();
|
|
for (var j = 0, jj = rings.length; j < jj; ++j) {
|
|
this.addSegments_(selectionLayer, feature, rings[j]);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {ol.layer.Vector} selectionLayer Selection layer.
|
|
* @param {ol.Coordinate} coordinates Coordinates.
|
|
* @return {ol.Feature} Vertex feature.
|
|
*/
|
|
ol.interaction.Modify.prototype.createOrUpdateVertexFeature =
|
|
function(selectionLayer, coordinates) {
|
|
var editData = selectionLayer.getEditData();
|
|
var vertexFeature = editData.vertexFeature;
|
|
if (goog.isNull(vertexFeature)) {
|
|
vertexFeature = new ol.Feature({g: new ol.geom.Point(coordinates)});
|
|
selectionLayer.addFeatures([vertexFeature]);
|
|
editData.vertexFeature = vertexFeature;
|
|
} else {
|
|
var geometry = vertexFeature.getGeometry();
|
|
geometry.setCoordinates(coordinates);
|
|
}
|
|
return vertexFeature;
|
|
};
|
|
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
ol.interaction.Modify.prototype.handleDragStart = function(evt) {
|
|
this.dragSegments_ = [];
|
|
for (var i = 0, ii = this.layers_.length; i < ii; ++i) {
|
|
var layer = this.layers_[i];
|
|
var selectionData = layer.getSelectionData();
|
|
var selectionLayer = selectionData.layer;
|
|
if (!goog.isNull(selectionLayer)) {
|
|
var editData = selectionLayer.getEditData();
|
|
var vertexFeature = editData.vertexFeature;
|
|
var insertVertices = [];
|
|
if (!goog.isNull(vertexFeature) && vertexFeature.renderIntent !=
|
|
ol.layer.VectorLayerRenderIntent.HIDDEN) {
|
|
var vertex = vertexFeature.getGeometry().getCoordinates();
|
|
var vertexExtent = ol.extent.boundingExtent([vertex]);
|
|
var segments = editData.rTree.search(vertexExtent);
|
|
for (var j = 0, jj = segments.length; j < jj; ++j) {
|
|
var segmentData = segments[j];
|
|
var segment = segmentData[0];
|
|
var feature = segmentData[1];
|
|
var featureId = goog.getUid(feature);
|
|
var original = selectionData.featuresBySelectedFeatureUid[featureId];
|
|
if (vertexFeature.renderIntent ==
|
|
ol.layer.VectorLayerRenderIntent.TEMPORARY) {
|
|
if (ol.coordinate.equals(segment[0], vertex)) {
|
|
this.dragSegments_.push([selectionLayer, segmentData, 0]);
|
|
feature.setOriginal(original);
|
|
} else if (ol.coordinate.equals(segment[1], vertex)) {
|
|
this.dragSegments_.push([selectionLayer, segmentData, 1]);
|
|
feature.setOriginal(original);
|
|
}
|
|
} else if (
|
|
ol.coordinate.squaredDistanceToSegment(vertex, segment) === 0) {
|
|
insertVertices.push([selectionLayer, segmentData, vertex]);
|
|
feature.setOriginal(original);
|
|
}
|
|
}
|
|
for (j = insertVertices.length - 1; j >= 0; --j) {
|
|
this.insertVertex_.apply(this, insertVertices[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return this.modifiable_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
ol.interaction.Modify.prototype.handleDrag = function(evt) {
|
|
var vertex = evt.getCoordinate();
|
|
for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) {
|
|
var dragSegment = this.dragSegments_[i];
|
|
var selectionLayer = dragSegment[0];
|
|
var segmentData = dragSegment[1];
|
|
var feature = segmentData[1];
|
|
var geometry = segmentData[2];
|
|
var coordinates = geometry.getCoordinates();
|
|
var index = dragSegment[2];
|
|
coordinates[segmentData[3] + index] = vertex;
|
|
geometry.setCoordinates(coordinates);
|
|
|
|
var editData = selectionLayer.getEditData();
|
|
var segment = segmentData[0];
|
|
editData.rTree.remove(ol.extent.boundingExtent(segment), segmentData);
|
|
segment[index] = vertex;
|
|
this.createOrUpdateVertexFeature(selectionLayer, vertex);
|
|
editData.rTree.insert(ol.extent.boundingExtent(segment), segmentData,
|
|
goog.getUid(feature));
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
ol.interaction.Modify.prototype.handleMapBrowserEvent =
|
|
function(mapBrowserEvent) {
|
|
if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] &&
|
|
!this.getDragging() &&
|
|
mapBrowserEvent.type == ol.MapBrowserEvent.EventType.MOUSEMOVE) {
|
|
this.handleMouseMove_(mapBrowserEvent);
|
|
}
|
|
goog.base(this, 'handleMapBrowserEvent', mapBrowserEvent);
|
|
return !this.modifiable_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {ol.MapBrowserEvent} evt Event.
|
|
* @private
|
|
*/
|
|
ol.interaction.Modify.prototype.handleMouseMove_ = function(evt) {
|
|
var map = evt.map;
|
|
var layers = goog.array.filter(map.getLayerGroup().getLayers().getArray(),
|
|
this.ignoreTemporaryLayersFilter_);
|
|
if (!goog.isNull(this.layerFilter_)) {
|
|
layers = goog.array.filter(layers, this.layerFilter_);
|
|
}
|
|
this.layers_ = layers;
|
|
var pixel = evt.getPixel();
|
|
var pixelCoordinate = map.getCoordinateFromPixel(pixel);
|
|
var sortByDistance = function(a, b) {
|
|
return ol.coordinate.closestOnSegment(pixelCoordinate, a[0])[2] -
|
|
ol.coordinate.closestOnSegment(pixelCoordinate, b[0])[2];
|
|
};
|
|
|
|
var lowerLeft = map.getCoordinateFromPixel(
|
|
[pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
|
|
var upperRight = map.getCoordinateFromPixel(
|
|
[pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
|
|
var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
|
|
var vertexFeature;
|
|
this.modifiable_ = false;
|
|
for (var i = layers.length - 1; i >= 0; --i) {
|
|
var layer = layers[i];
|
|
var selectionLayer = layer.getSelectionData().layer;
|
|
if (!goog.isNull(selectionLayer)) {
|
|
var listener = goog.events.getListener(selectionLayer,
|
|
ol.layer.VectorEventType.ADD,
|
|
this.handleFeaturesAdded_, false, this);
|
|
if (goog.isNull(listener)) {
|
|
this.addLayer(layer);
|
|
}
|
|
var editData = selectionLayer.getEditData();
|
|
vertexFeature = editData.vertexFeature;
|
|
var segments = editData.rTree.search(box);
|
|
var renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN;
|
|
if (segments.length > 0) {
|
|
segments.sort(sortByDistance);
|
|
var segment = segments[0][0]; // the closest segment
|
|
var vertex = /** @type {ol.Coordinate} */
|
|
(ol.coordinate.closestOnSegment(pixelCoordinate, segment));
|
|
var vertexPixel = map.getPixelFromCoordinate(vertex);
|
|
if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
|
|
this.pixelTolerance_) {
|
|
var pixel1 = map.getPixelFromCoordinate(segment[0]);
|
|
var pixel2 = map.getPixelFromCoordinate(segment[1]);
|
|
var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
|
|
var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
|
|
var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
|
|
renderIntent = ol.layer.VectorLayerRenderIntent.FUTURE;
|
|
if (dist <= 10) {
|
|
vertex = squaredDist1 > squaredDist2 ? segment[1] : segment[0];
|
|
renderIntent = ol.layer.VectorLayerRenderIntent.TEMPORARY;
|
|
}
|
|
vertexFeature =
|
|
this.createOrUpdateVertexFeature(selectionLayer, vertex);
|
|
this.modifiable_ = true;
|
|
}
|
|
}
|
|
if (!goog.isNull(vertexFeature) &&
|
|
vertexFeature.renderIntent != renderIntent) {
|
|
selectionLayer.setRenderIntent(renderIntent, [vertexFeature]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {ol.layer.Layer} layer Layer.
|
|
* @return {boolean} Whether the layer is no temporary vector layer.
|
|
* @private
|
|
*/
|
|
ol.interaction.Modify.prototype.ignoreTemporaryLayersFilter_ = function(layer) {
|
|
return !(layer instanceof ol.layer.Vector && layer.getTemporary());
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {ol.layer.Vector} selectionLayer Selection layer.
|
|
* @param {Array} segmentData Segment data.
|
|
* @param {ol.Coordinate} vertex Vertex.
|
|
* @private
|
|
*/
|
|
ol.interaction.Modify.prototype.insertVertex_ =
|
|
function(selectionLayer, segmentData, vertex) {
|
|
var segment = segmentData[0];
|
|
var feature = segmentData[1];
|
|
var geometry = segmentData[2];
|
|
var index = segmentData[3];
|
|
var coordinates = geometry.getCoordinates();
|
|
coordinates.splice(index + 1, 0, vertex);
|
|
geometry.setCoordinates(coordinates);
|
|
var rTree = selectionLayer.getEditData().rTree;
|
|
rTree.remove(ol.extent.boundingExtent(segment), segmentData);
|
|
var uid = goog.getUid(feature);
|
|
var allSegments = rTree.search(geometry.getBounds(), uid);
|
|
for (var i = 0, ii = allSegments.length; i < ii; ++i) {
|
|
var allSegmentsData = allSegments[i];
|
|
if (allSegmentsData[2] === geometry && allSegmentsData[3] > index) {
|
|
++allSegmentsData[3];
|
|
}
|
|
}
|
|
var newSegment = [segment[0], vertex];
|
|
var newSegmentData = [newSegment, feature, geometry, index];
|
|
rTree.insert(ol.extent.boundingExtent(newSegment), newSegmentData, uid);
|
|
this.dragSegments_.push([selectionLayer, newSegmentData, 1]);
|
|
newSegment = [vertex, segment[1]];
|
|
newSegmentData = [newSegment, feature, geometry, index + 1];
|
|
rTree.insert(ol.extent.boundingExtent(newSegment), newSegmentData, uid);
|
|
this.dragSegments_.push([selectionLayer, newSegmentData, 0]);
|
|
};
|
|
|
|
|
|
/**
|
|
* Listen for feature removals.
|
|
* @param {ol.layer.VectorEvent} evt Event object.
|
|
* @private
|
|
*/
|
|
ol.interaction.Modify.prototype.handleFeaturesRemoved_ = function(evt) {
|
|
var layer = evt.target;
|
|
var rTree = layer.getEditData().rTree;
|
|
var features = evt.features;
|
|
for (var i = 0, ii = features.length; i < ii; ++i) {
|
|
var feature = features[i];
|
|
var segments = rTree.search(feature.getGeometry().getBounds(),
|
|
goog.getUid(feature));
|
|
for (var j = segments.length - 1; j >= 0; --j) {
|
|
var segment = segments[j];
|
|
rTree.remove(ol.extent.boundingExtent(segment[0]), segment);
|
|
}
|
|
}
|
|
};
|