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.

+
+

See the modify-features.js source to see how this is done.

+
+
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