diff --git a/examples/modify-features.html b/examples/modify-features.html
new file mode 100644
index 0000000000..26a5a698e0
--- /dev/null
+++ b/examples/modify-features.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+ Modify features example
+
+
+
+
+
+
+
+
+
+
+
+
+
Modify features example
+
Example of using the Modify interaction. Select a feature and drag the circle that appears when the cursor gets close to the selected geometry.
+
+
modify, edit, vector
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/modify-features.js b/examples/modify-features.js
new file mode 100644
index 0000000000..c3ef96c9c9
--- /dev/null
+++ b/examples/modify-features.js
@@ -0,0 +1,109 @@
+goog.require('ol.Map');
+goog.require('ol.RendererHint');
+goog.require('ol.View2D');
+goog.require('ol.interaction');
+goog.require('ol.interaction.Modify');
+goog.require('ol.interaction.Select');
+goog.require('ol.layer.Tile');
+goog.require('ol.layer.Vector');
+goog.require('ol.parser.ogc.GML_v3');
+goog.require('ol.source.MapQuestOpenAerial');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Rule');
+goog.require('ol.style.Shape');
+goog.require('ol.style.Stroke');
+goog.require('ol.style.Style');
+
+var raster = new ol.layer.Tile({
+ source: new ol.source.MapQuestOpenAerial()
+});
+
+var vector = new ol.layer.Vector({
+ id: 'vector',
+ source: new ol.source.Vector({
+ parser: new ol.parser.ogc.GML_v3(),
+ url: 'data/gml/topp-states-wfs.xml'
+ }),
+ style: new ol.style.Style({
+ rules: [
+ new ol.style.Rule({
+ filter: 'renderIntent("selected")',
+ symbolizers: [
+ new ol.style.Fill({
+ color: '#ffffff',
+ opacity: 0.2
+ }),
+ new ol.style.Stroke({
+ color: 'white',
+ width: 5
+ }),
+ new ol.style.Stroke({
+ color: '#0099ff',
+ width: 3
+ })
+ ]
+ }),
+ new ol.style.Rule({
+ filter: 'renderIntent("temporary")',
+ symbolizers: [
+ new ol.style.Shape({
+ fill: new ol.style.Fill({
+ color: '#0099ff',
+ opacity: 1
+ }),
+ stroke: new ol.style.Stroke({
+ color: 'white',
+ opacity: 0.75
+ }),
+ size: 14,
+ zIndex: 1
+ })
+ ]
+ }),
+ new ol.style.Rule({
+ filter: 'renderIntent("future")',
+ symbolizers: [
+ new ol.style.Shape({
+ fill: new ol.style.Fill({
+ color: '#00ff33',
+ opacity: 1
+ }),
+ stroke: new ol.style.Stroke({
+ color: 'white',
+ opacity: 0.75
+ }),
+ size: 14,
+ zIndex: 1
+ })
+ ]
+ })
+ ],
+ symbolizers: [
+ new ol.style.Fill({
+ color: '#ffffff',
+ opacity: 0.1
+ }),
+ new ol.style.Stroke({
+ color: '#ffcc33',
+ width: 2
+ })
+ ]
+ })
+});
+
+var selectInteraction = new ol.interaction.Select({
+ layerFilter: function(layer) { return layer.get('id') == 'vector'; }
+});
+
+var map = new ol.Map({
+ interactions: ol.interaction.defaults().extend(
+ [selectInteraction, new ol.interaction.Modify()]),
+ layers: [raster, vector],
+ renderer: ol.RendererHint.CANVAS,
+ target: 'map',
+ view: new ol.View2D({
+ center: [-11000000, 4600000],
+ zoom: 4
+ })
+});
diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index e39f3b5156..e107198442 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -377,6 +377,14 @@
* @todo stability experimental
*/
+/**
+ * @typedef {Object} ol.interaction.ModifyOptions
+ * @property {undefined|Array.|function(ol.layer.Layer):boolean} layers
+ * Layers or filter function to restrict modification to a subset of layers.
+ * @property {number|undefined} pixelTolerance Pixel tolerance for considering
+ * the pointer close enough to a vertex for editing. Default is 20 pixels.
+ */
+
/**
* @typedef {Object} ol.interaction.SelectOptions
* @property {ol.events.ConditionType|undefined} addCondition A conditional
diff --git a/src/ol/coordinate.js b/src/ol/coordinate.js
index a0eebcde74..a965bcd75f 100644
--- a/src/ol/coordinate.js
+++ b/src/ol/coordinate.js
@@ -135,6 +135,23 @@ ol.coordinate.format = function(coordinate, template, opt_fractionDigits) {
};
+/**
+ * @param {ol.Coordinate} coordinate1 First coordinate.
+ * @param {ol.Coordinate} coordinate2 Second coordinate.
+ * @return {boolean} Whether the passed coordinates are equal.
+ */
+ol.coordinate.equals = function(coordinate1, coordinate2) {
+ var equals = true;
+ for (var i = coordinate1.length - 1; i >= 0; --i) {
+ if (coordinate1[i] != coordinate2[i]) {
+ equals = false;
+ break;
+ }
+ }
+ return equals;
+};
+
+
/**
* @param {ol.Coordinate} coordinate Coordinate.
* @param {number} angle Angle.
diff --git a/src/ol/feature.js b/src/ol/feature.js
index caebeb56c5..38da602d17 100644
--- a/src/ol/feature.js
+++ b/src/ol/feature.js
@@ -42,6 +42,13 @@ ol.Feature = function(opt_values) {
*/
this.geometryName_;
+ /**
+ * Original of this feature when it was modified.
+ * @type {ol.Feature}
+ * @private
+ */
+ this.original_ = null;
+
/**
* The render intent for this feature.
* @type {ol.layer.VectorLayerRenderIntent|string}
@@ -101,6 +108,15 @@ ol.Feature.prototype.getGeometry = function() {
};
+/**
+ * Get the original of this feature when it was modified.
+ * @return {ol.Feature} Original.
+ */
+ol.Feature.prototype.getOriginal = function() {
+ return this.original_;
+};
+
+
/**
* Get any symbolizers set directly on the feature.
* @return {Array.} Symbolizers (or null if none).
@@ -176,6 +192,15 @@ ol.Feature.prototype.setGeometry = function(geometry) {
};
+/**
+ * Set the original of this feature when it was modified.
+ * @param {ol.Feature} original Original.
+ */
+ol.Feature.prototype.setOriginal = function(original) {
+ this.original_ = original;
+};
+
+
/**
* Gets the renderIntent for this feature.
* @return {string} Render intent.
diff --git a/src/ol/interaction/draginteraction.js b/src/ol/interaction/draginteraction.js
index 4132e8eaae..cea8b1796e 100644
--- a/src/ol/interaction/draginteraction.js
+++ b/src/ol/interaction/draginteraction.js
@@ -26,6 +26,13 @@ ol.interaction.Drag = function() {
*/
this.dragging_ = false;
+ /**
+ * Delta for INTERACTING view hint. Subclasses that do not want the
+ * INTERACTING hint to be set should override this to 0.
+ * @type {number}
+ */
+ this.interactingHint = 1;
+
/**
* @type {number}
*/
@@ -60,6 +67,14 @@ ol.interaction.Drag = function() {
goog.inherits(ol.interaction.Drag, ol.interaction.Interaction);
+/**
+ * @return {boolean} Whether we're dragging.
+ */
+ol.interaction.Drag.prototype.getDragging = function() {
+ return this.dragging_;
+};
+
+
/**
* @param {ol.MapBrowserEvent} mapBrowserEvent Event.
* @protected
@@ -115,7 +130,7 @@ ol.interaction.Drag.prototype.handleMapBrowserEvent =
goog.asserts.assertInstanceof(browserEvent, goog.events.BrowserEvent);
this.deltaX = browserEvent.clientX - this.startX;
this.deltaY = browserEvent.clientY - this.startY;
- view.setHint(ol.ViewHint.INTERACTING, -1);
+ view.setHint(ol.ViewHint.INTERACTING, -this.interactingHint);
this.dragging_ = false;
this.handleDragEnd(mapBrowserEvent);
}
@@ -131,7 +146,7 @@ ol.interaction.Drag.prototype.handleMapBrowserEvent =
(mapBrowserEvent.getCoordinate());
var handled = this.handleDragStart(mapBrowserEvent);
if (handled) {
- view.setHint(ol.ViewHint.INTERACTING, 1);
+ view.setHint(ol.ViewHint.INTERACTING, this.interactingHint);
this.dragging_ = true;
mapBrowserEvent.preventDefault();
stopEvent = true;
diff --git a/src/ol/interaction/modifyinteraction.exports b/src/ol/interaction/modifyinteraction.exports
new file mode 100644
index 0000000000..2b7b78bce7
--- /dev/null
+++ b/src/ol/interaction/modifyinteraction.exports
@@ -0,0 +1 @@
+@exportClass ol.interaction.Modify ol.interaction.ModifyOptions
diff --git a/src/ol/interaction/modifyinteraction.js b/src/ol/interaction/modifyinteraction.js
new file mode 100644
index 0000000000..4d0ed9da84
--- /dev/null
+++ b/src/ol/interaction/modifyinteraction.js
@@ -0,0 +1,524 @@
+goog.provide('ol.interaction.Modify');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('ol.CollectionEventType');
+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.Layer');
+goog.require('ol.layer.Vector');
+goog.require('ol.layer.VectorEventType');
+goog.require('ol.layer.VectorLayerRenderIntent');
+goog.require('ol.source.Vector');
+goog.require('ol.structs.RTree');
+
+
+/**
+ * @typedef {{feature: ol.Feature,
+ * geometry: ol.geom.Geometry,
+ * index: (number|undefined),
+ * style: ol.style.Style,
+ * segment: (Array.|undefined)}}
+ */
+ol.interaction.SegmentDataType;
+
+
+
+/**
+ * @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 : {};
+
+ var layerFilter = options.layers;
+ if (!goog.isDef(layerFilter)) {
+ layerFilter = goog.functions.TRUE;
+ } else if (goog.isArray(layerFilter)) {
+ layerFilter = function(layer) {return options.layers.indexOf(layer) > -1;};
+ }
+ goog.asserts.assertFunction(layerFilter);
+
+ /**
+ * @type {function(ol.layer.Layer):boolean}
+ * @private
+ */
+ this.layerFilter_ = layerFilter;
+
+ /**
+ * Temporary sketch layer.
+ * @type {ol.layer.Vector}
+ * @private
+ */
+ this.sketchLayer_ = null;
+
+ /**
+ * Editing vertex.
+ * @type {ol.Feature}
+ * @private
+ */
+ this.vertexFeature_ = null;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.modifiable_ = false;
+
+ /**
+ * Segment RTree for each layer
+ * @type {Object.<*, ol.structs.RTree>}
+ * @private
+ */
+ this.rTree_ = null;
+
+ /**
+ * @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);
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Modify.prototype.setMap = function(map) {
+ var oldMap = this.getMap();
+ var layers;
+ if (!goog.isNull(oldMap)) {
+ oldMap.removeLayer(this.sketchLayer_);
+ layers = oldMap.getLayerGroup().getLayers();
+ goog.asserts.assert(goog.isDef(layers));
+ layers.forEach(goog.bind(this.removeLayer_, this));
+ layers.unlisten(ol.CollectionEventType.ADD, this.handleLayerAdded_, false,
+ this);
+ layers.unlisten(ol.CollectionEventType.REMOVE, this.handleLayerRemoved_,
+ false, this);
+ }
+
+ if (!goog.isNull(map)) {
+ if (goog.isNull(this.rTree_)) {
+ this.rTree_ = new ol.structs.RTree();
+ }
+ if (goog.isNull(this.sketchLayer_)) {
+ var sketchLayer = new ol.layer.Vector({
+ source: new ol.source.Vector({parser: null})
+ });
+ this.sketchLayer_ = sketchLayer;
+ sketchLayer.setTemporary(true);
+ map.addLayer(sketchLayer);
+ }
+ layers = map.getLayerGroup().getLayers();
+ goog.asserts.assert(goog.isDef(layers));
+ layers.forEach(goog.bind(this.addLayer_, this));
+ layers.listen(ol.CollectionEventType.ADD, this.handleLayerAdded_, false,
+ this);
+ layers.listen(ol.CollectionEventType.REMOVE, this.handleLayerRemoved_,
+ false, this);
+ } else {
+ // removing from a map, clean up
+ this.rTree_ = null;
+ this.sketchLayer_ = null;
+ }
+
+ goog.base(this, 'setMap', map);
+};
+
+
+/**
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
+ */
+ol.interaction.Modify.prototype.handleLayerAdded_ = function(evt) {
+ goog.asserts.assertInstanceof(evt.getElement, ol.layer.Layer);
+ this.addLayer_(evt.getElement);
+};
+
+
+/**
+ * Add a layer for modification.
+ * @param {ol.layer.Layer} layer Layer.
+ * @private
+ */
+ol.interaction.Modify.prototype.addLayer_ = function(layer) {
+ if (this.layerFilter_(layer) && layer instanceof ol.layer.Vector &&
+ !layer.getTemporary()) {
+ this.addIndex_(layer.getFeatures(ol.layer.Vector.selectedFeaturesFilter),
+ layer);
+ goog.events.listen(layer, ol.layer.VectorEventType.INTENTCHANGE,
+ this.handleIntentChange_, false, this);
+ }
+};
+
+
+/**
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
+ */
+ol.interaction.Modify.prototype.handleLayerRemoved_ = function(evt) {
+ goog.asserts.assertInstanceof(evt.getElement, ol.layer.Layer);
+ this.removeLayer_(evt.getElement());
+};
+
+
+/**
+ * Remove a layer for modification.
+ * @param {ol.layer.Layer} layer Layer.
+ * @private
+ */
+ol.interaction.Modify.prototype.removeLayer_ = function(layer) {
+ if (this.layerFilter_(layer) && layer instanceof ol.layer.Vector &&
+ !layer.getTemporary()) {
+ this.removeIndex_(
+ layer.getFeatures(ol.layer.Vector.selectedFeaturesFilter));
+ goog.events.unlisten(layer, ol.layer.VectorEventType.INTENTCHANGE,
+ this.handleIntentChange_, false, this);
+ }
+};
+
+
+/**
+ * @param {Array.} features Array of features.
+ * @param {ol.layer.Vector} layer Layer the features belong to.
+ * @private
+ */
+ol.interaction.Modify.prototype.addIndex_ = function(features, layer) {
+ for (var i = 0, ii = features.length; i < ii; ++i) {
+ var feature = features[i];
+ var geometry = feature.getGeometry();
+ if (geometry instanceof ol.geom.AbstractCollection) {
+ var components = geometry.getComponents();
+ for (var j = 0, jj = components.length; j < jj; ++j) {
+ this.addSegments_(feature, components[j], layer);
+ }
+ } else {
+ this.addSegments_(feature, geometry, layer);
+ }
+ }
+};
+
+
+/**
+ * @param {Array.} features Array of features.
+ * @private
+ */
+ol.interaction.Modify.prototype.removeIndex_ = function(features) {
+ var rTree = this.rTree_;
+ for (var i = 0, ii = features.length; i < ii; ++i) {
+ var feature = features[i];
+ var segmentDataMatches = rTree.search(feature.getGeometry().getBounds(),
+ goog.getUid(feature));
+ for (var j = segmentDataMatches.length - 1; j >= 0; --j) {
+ var segmentDataMatch = segmentDataMatches[j];
+ rTree.remove(ol.extent.boundingExtent(segmentDataMatch.segment),
+ segmentDataMatch);
+ }
+ }
+};
+
+
+/**
+ * Listen for feature additions.
+ * @param {ol.layer.VectorEvent} evt Event object.
+ * @private
+ */
+ol.interaction.Modify.prototype.handleIntentChange_ = function(evt) {
+ var layer = evt.target;
+ goog.asserts.assertInstanceof(layer, ol.layer.Vector);
+ var features = evt.features;
+ for (var i = 0, ii = features.length; i < ii; ++i) {
+ var feature = features[i];
+ var renderIntent = feature.getRenderIntent();
+ if (renderIntent == ol.layer.VectorLayerRenderIntent.SELECTED) {
+ this.addIndex_([feature], layer);
+ } else {
+ this.removeIndex_([feature]);
+ }
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature to add segments for.
+ * @param {ol.geom.Geometry} geometry Geometry to add segments for.
+ * @param {ol.layer.Vector} layer Vector layer to add segments for.
+ * @private
+ */
+ol.interaction.Modify.prototype.addSegments_ =
+ function(feature, geometry, layer) {
+ var uid = goog.getUid(feature);
+ var rTree = this.rTree_;
+ var segment, segmentData, coordinates;
+ if (geometry instanceof ol.geom.Point) {
+ segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+ feature: feature,
+ geometry: geometry,
+ style: layer.getStyle()
+ });
+ 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 = /** @type {ol.interaction.SegmentDataType} */ ({
+ feature: feature,
+ geometry: geometry,
+ index: i,
+ style: layer.getStyle(),
+ segment: segment
+ });
+ 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_(feature, rings[j], layer);
+ }
+ }
+};
+
+
+/**
+ * @param {ol.style.Style} style Style of the layer that the feature being
+ * modified belongs to.
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @return {ol.Feature} Vertex feature.
+ * @private
+ */
+ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ =
+ function(style, coordinates) {
+ var vertexFeature = this.vertexFeature_;
+ if (goog.isNull(vertexFeature)) {
+ vertexFeature = new ol.Feature({g: new ol.geom.Point(coordinates)});
+ this.vertexFeature_ = vertexFeature;
+ this.sketchLayer_.addFeatures([vertexFeature]);
+ } else {
+ var geometry = vertexFeature.getGeometry();
+ geometry.setCoordinates(coordinates);
+ }
+ if (this.sketchLayer_.getStyle() !== style) {
+ this.sketchLayer_.setStyle(style);
+ }
+ return vertexFeature;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Modify.prototype.handleDragStart = function(evt) {
+ this.dragSegments_ = [];
+ var vertexFeature = this.vertexFeature_;
+ var renderIntent = vertexFeature.getRenderIntent();
+ if (goog.isDef(vertexFeature) &&
+ renderIntent != ol.layer.VectorLayerRenderIntent.HIDDEN) {
+ var insertVertices = [];
+ var vertex = vertexFeature.getGeometry().getCoordinates();
+ var vertexExtent = ol.extent.boundingExtent([vertex]);
+ var segmentDataMatches = this.rTree_.search(vertexExtent);
+ var distinctFeatures = {};
+ for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) {
+ var segmentDataMatch = segmentDataMatches[i];
+ var segment = segmentDataMatch.segment;
+ if (!(goog.getUid(segmentDataMatch.feature) in distinctFeatures)) {
+ var feature = segmentDataMatch.feature;
+ distinctFeatures[goog.getUid(feature)] = true;
+ var original = new ol.Feature(feature.getAttributes());
+ original.setGeometry(feature.getGeometry().clone());
+ original.setId(feature.getId());
+ original.setOriginal(feature.getOriginal());
+ original.setSymbolizers(feature.getSymbolizers());
+ feature.setOriginal(original);
+ }
+ if (renderIntent == ol.layer.VectorLayerRenderIntent.TEMPORARY) {
+ if (ol.coordinate.equals(segment[0], vertex)) {
+ this.dragSegments_.push([segmentDataMatch, 0]);
+ } else if (ol.coordinate.equals(segment[1], vertex)) {
+ this.dragSegments_.push([segmentDataMatch, 1]);
+ }
+ } else if (
+ ol.coordinate.squaredDistanceToSegment(vertex, segment) === 0) {
+ insertVertices.push([segmentDataMatch, vertex]);
+ }
+ }
+ for (i = insertVertices.length - 1; i >= 0; --i) {
+ this.insertVertex_.apply(this, insertVertices[i]);
+ }
+ }
+ 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 segmentData = dragSegment[0];
+ var feature = segmentData.feature;
+ var geometry = segmentData.geometry;
+ var coordinates = geometry.getCoordinates();
+
+ var oldBounds, newBounds;
+ if (geometry instanceof ol.geom.Point) {
+ oldBounds = geometry.getBounds();
+ geometry.setCoordinates(vertex);
+ newBounds = geometry.getBounds();
+ } else {
+ var index = dragSegment[1];
+ coordinates[segmentData.index + index] = vertex;
+ geometry.setCoordinates(coordinates);
+ var segment = segmentData.segment;
+ oldBounds = ol.extent.boundingExtent(segment);
+ segment[index] = vertex;
+ newBounds = ol.extent.boundingExtent(segment);
+ }
+ this.createOrUpdateVertexFeature_(segmentData.style, vertex);
+ this.rTree_.remove(oldBounds, segmentData);
+ this.rTree_.insert(newBounds, 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 pixel = evt.getPixel();
+ var pixelCoordinate = map.getCoordinateFromPixel(pixel);
+ var sortByDistance = function(a, b) {
+ return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) -
+ ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment);
+ };
+
+ 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]);
+
+ this.modifiable_ = false;
+ var vertexFeature = this.vertexFeature_;
+ var rTree = this.rTree_;
+ var segmentDataMatches = rTree.search(box);
+ var renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN;
+ if (segmentDataMatches.length > 0) {
+ segmentDataMatches.sort(sortByDistance);
+ var segmentDataMatch = segmentDataMatches[0];
+ var segment = segmentDataMatch.segment; // the closest segment
+ var vertex = (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_(segmentDataMatch.style,
+ vertex);
+ this.modifiable_ = true;
+ }
+ }
+
+ if (!goog.isNull(vertexFeature) &&
+ renderIntent != vertexFeature.getRenderIntent()) {
+ vertexFeature.setRenderIntent(renderIntent);
+ }
+};
+
+
+/**
+ * @param {ol.interaction.SegmentDataType} segmentData Segment data.
+ * @param {ol.Coordinate} vertex Vertex.
+ * @private
+ */
+ol.interaction.Modify.prototype.insertVertex_ =
+ function(segmentData, vertex) {
+ var segment = segmentData.segment;
+ var feature = segmentData.feature;
+ var geometry = segmentData.geometry;
+ var index = segmentData.index;
+ var coordinates = geometry.getCoordinates();
+ coordinates.splice(index + 1, 0, vertex);
+ geometry.setCoordinates(coordinates);
+ var rTree = this.rTree_;
+ goog.asserts.assert(goog.isDef(segment));
+ rTree.remove(ol.extent.boundingExtent(segment), segmentData);
+ var uid = goog.getUid(feature);
+ var segmentDataMatches = this.rTree_.search(geometry.getBounds(), uid);
+ for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) {
+ var segmentDataMatch = segmentDataMatches[i];
+ if (segmentDataMatch.geometry === geometry &&
+ segmentDataMatch.index > index) {
+ ++segmentDataMatch.index;
+ }
+ }
+ var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+ style: segmentData.style,
+ segment: [segment[0], vertex],
+ feature: feature,
+ geometry: geometry,
+ index: index
+ });
+ rTree.insert(ol.extent.boundingExtent(newSegmentData.segment), newSegmentData,
+ uid);
+ this.dragSegments_.push([newSegmentData, 1]);
+ newSegmentData = goog.object.clone(newSegmentData);
+ newSegmentData.segment = [vertex, segment[1]];
+ newSegmentData.index += 1;
+ rTree.insert(ol.extent.boundingExtent(newSegmentData.segment), newSegmentData,
+ uid);
+ this.dragSegments_.push([newSegmentData, 0]);
+};
diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js
index 48c90a2817..28d1df9d60 100644
--- a/src/ol/layer/vectorlayer.js
+++ b/src/ol/layer/vectorlayer.js
@@ -158,7 +158,7 @@ ol.layer.Vector = function(options) {
* @type {boolean}
* @private
*/
- this.temp_ = false;
+ this.temporary_ = false;
};
goog.inherits(ol.layer.Vector, ol.layer.Layer);
@@ -241,7 +241,7 @@ ol.layer.Vector.prototype.clear = function() {
* @return {boolean} Whether this layer is temporary.
*/
ol.layer.Vector.prototype.getTemporary = function() {
- return this.temp_;
+ return this.temporary_;
};
@@ -463,10 +463,10 @@ ol.layer.Vector.prototype.removeFeatures = function(features) {
/**
- * @param {boolean} temp Whether this layer is temporary.
+ * @param {boolean} temporary Whether this layer is temporary.
*/
-ol.layer.Vector.prototype.setTemporary = function(temp) {
- this.temp_ = temp;
+ol.layer.Vector.prototype.setTemporary = function(temporary) {
+ this.temporary_ = temporary;
};
diff --git a/src/ol/layer/vectorlayerrenderintent.js b/src/ol/layer/vectorlayerrenderintent.js
index f877dc1732..6b84c28700 100644
--- a/src/ol/layer/vectorlayerrenderintent.js
+++ b/src/ol/layer/vectorlayerrenderintent.js
@@ -6,6 +6,7 @@ goog.provide('ol.layer.VectorLayerRenderIntent');
*/
ol.layer.VectorLayerRenderIntent = {
DEFAULT: 'default',
+ FUTURE: 'future',
HIDDEN: 'hidden',
SELECTED: 'selected',
TEMPORARY: 'temporary'
diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js
index 5416d4844d..6057998bc6 100644
--- a/src/ol/structs/rtree.js
+++ b/src/ol/structs/rtree.js
@@ -36,7 +36,7 @@ goog.require('ol.extent');
* leaf: (Object|undefined),
* nodes: (Array.|undefined),
* target: (Object|undefined),
- * type: (string|undefined)}}
+ * type: (string|number|undefined)}}
*/
ol.structs.RTreeNode;
@@ -186,7 +186,8 @@ ol.structs.RTree.prototype.chooseLeafSubtree_ = function(rect, root) {
*
* @param {ol.Extent} extent Extent.
* @param {Object} obj Object to insert.
- * @param {string=} opt_type Optional type to store along with the object.
+ * @param {string|number=} opt_type Optional type to store along with the
+ * object.
*/
ol.structs.RTree.prototype.insert = function(extent, obj, opt_type) {
var node = /** @type {ol.structs.RTreeNode} */
@@ -554,7 +555,8 @@ ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) {
* Non-recursive search function
*
* @param {ol.Extent} extent Extent.
- * @param {string=} opt_type Optional type of the objects we want to find.
+ * @param {string|number=} opt_type Optional type of the objects we want to
+ * find.
* @return {Array} Result.
* @this {ol.structs.RTree}
*/
@@ -569,7 +571,8 @@ ol.structs.RTree.prototype.search = function(extent, opt_type) {
* Non-recursive search function
*
* @param {ol.Extent} extent Extent.
- * @param {string=} opt_type Optional type of the objects we want to find.
+ * @param {string|number=} opt_type Optional type of the objects we want to
+ * find.
* @return {Object} Result. Keys are UIDs of the values.
* @this {ol.structs.RTree}
*/
@@ -587,7 +590,7 @@ ol.structs.RTree.prototype.searchReturningObject = function(extent, opt_type) {
* @param {boolean} returnNode Do we return nodes?
* @param {Array|Object} result Result.
* @param {ol.structs.RTreeNode} root Root.
- * @param {string=} opt_type Optional type to search for.
+ * @param {string|number=} opt_type Optional type to search for.
* @param {boolean=} opt_resultAsObject If set, result will be an object keyed
* by UID.
* @private