diff --git a/examples/modify-features.html b/examples/modify-features.html
new file mode 100644
index 0000000000..1f8b02941a
--- /dev/null
+++ b/examples/modify-features.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+ 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..7858f53f00
--- /dev/null
+++ b/examples/modify-features.js
@@ -0,0 +1,88 @@
+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.5
+ }),
+ new ol.style.Stroke({
+ color: '#6666ff',
+ width: 0.5
+ })
+ ]
+ }),
+ new ol.style.Rule({
+ filter: 'renderIntent("temporary")',
+ symbolizers: [
+ new ol.style.Shape({
+ fill: new ol.style.Fill({color: '#bada55'}),
+ size: 16
+ })
+ ]
+ }),
+ new ol.style.Rule({
+ filter: 'renderIntent("future")',
+ symbolizers: [
+ new ol.style.Shape({
+ fill: new ol.style.Fill({color: '#013'}),
+ size: 16
+ })
+ ]
+ })
+ ],
+ symbolizers: [
+ new ol.style.Fill({
+ color: '#ffffff',
+ opacity: 0.25
+ }),
+ new ol.style.Stroke({
+ color: '#6666ff'
+ })
+ ]
+ })
+});
+
+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..76713ddf18 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -377,6 +377,14 @@
* @todo stability experimental
*/
+/**
+ * @typedef {Object} ol.interaction.ModifyOptions
+ * @property {undefined|function(ol.layer.Layer):boolean} layerFilter 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/interaction/draginteraction.js b/src/ol/interaction/draginteraction.js
index 4132e8eaae..f3af8a62ef 100644
--- a/src/ol/interaction/draginteraction.js
+++ b/src/ol/interaction/draginteraction.js
@@ -26,6 +26,12 @@ ol.interaction.Drag = function() {
*/
this.dragging_ = false;
+ /**
+ * @type {number} Delta for INTERACTING view hint. Subclasses that do not want
+ * the INTERACTING hint to be set should override this to 0.
+ */
+ this.interactingHint = 1;
+
/**
* @type {number}
*/
@@ -60,6 +66,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 +129,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 +145,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.js b/src/ol/interaction/modifyinteraction.js
new file mode 100644
index 0000000000..0888551fb9
--- /dev/null
+++ b/src/ol/interaction/modifyinteraction.js
@@ -0,0 +1,391 @@
+goog.provide('ol.interaction.Modify');
+
+goog.require('goog.array');
+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.VectorLayerEventType');
+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.}
+ * @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.dragVertices_ = null;
+
+ this.interactingHint = 0;
+};
+goog.inherits(ol.interaction.Modify, ol.interaction.Drag);
+
+
+/**
+ * @param {ol.layer.VectorLayerEventObject} evt Event object.
+ */
+ol.interaction.Modify.prototype.addIndex = function(evt) {
+ var layer = evt.target;
+ var features = evt.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],
+ [[geometry.components, j]]);
+ }
+ } else {
+ this.addSegments_(layer, feature, geometry);
+ }
+ }
+};
+
+
+/**
+ * @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();
+ var vertexFeature = new ol.Feature();
+ vertexFeature.renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN;
+ vertexFeature.setGeometry(new ol.geom.Point([NaN, NaN]));
+ selectionLayer.addFeatures([vertexFeature]);
+ editData.vertexFeature = vertexFeature;
+ }
+ this.addIndex(/** @type {ol.layer.VectorLayerEventObject} */
+ ({target: selectionLayer, features: goog.object.getValues(
+ selectionData.selectedFeaturesByFeatureUid)}));
+ goog.events.listen(selectionLayer, ol.layer.VectorLayerEventType.ADD,
+ this.addIndex, false, this);
+ goog.events.listen(selectionLayer, ol.layer.VectorLayerEventType.REMOVE,
+ this.removeIndex, 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.
+ * @param {Array=} opt_parent Parent structure on the feature that the geometry
+ * belongs to. This array has two values:
+ * [0] the parent array;
+ * [1] the index of the geometry in the parent array.
+ * @private
+ */
+ol.interaction.Modify.prototype.addSegments_ =
+ function(selectionLayer, feature, geometry, opt_parent) {
+ 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];
+ if (goog.isDef(opt_parent)) {
+ segmentData.push(opt_parent);
+ }
+ 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];
+ if (opt_parent) {
+ segmentData.push(opt_parent);
+ }
+ rTree.insert(ol.extent.boundingExtent(segment), segmentData, uid);
+ }
+ } else if (geometry instanceof ol.geom.Polygon) {
+ for (var j = 0, jj = geometry.rings.length; j < jj; ++j) {
+ this.addSegments_(selectionLayer, feature, geometry.rings[j],
+ [geometry.rings, j]);
+ }
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Modify.prototype.handleDragStart = function(evt) {
+ this.dragVertices_ = [];
+ for (var i = 0, ii = this.layers_.length; i < ii; ++i) {
+ var selectionData = this.layers_[i].getSelectionData();
+ var selectionLayer = selectionData.layer;
+ if (!goog.isNull(selectionLayer)) {
+ var editData = selectionLayer.getEditData();
+ var vertexFeature = editData.vertexFeature;
+ 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];
+ if (vertexFeature.renderIntent ==
+ ol.layer.VectorLayerRenderIntent.TEMPORARY) {
+ if (ol.coordinate.equals(segment[0], vertex)) {
+ this.dragVertices_.push([selectionLayer, segmentData, 0]);
+ } else {
+ this.dragVertices_.push([selectionLayer, segmentData, 1]);
+ }
+ } else {
+ this.insertVertex_(selectionLayer, segmentData, vertex);
+ }
+ }
+ }
+ }
+ }
+ return this.modifiable_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Modify.prototype.handleDrag = function(evt) {
+ var vertex = evt.getCoordinate();
+ for (var i = 0, ii = this.dragVertices_.length; i < ii; ++i) {
+ var dragVertex = this.dragVertices_[i];
+ var selectionLayer = dragVertex[0];
+ var segmentData = dragVertex[1];
+ var feature = segmentData[1];
+ var geometry = segmentData[2];
+ var index = dragVertex[2];
+ geometry.set(segmentData[3] + index, 0, vertex[0]);
+ geometry.set(segmentData[3] + index, 1, vertex[1]);
+ feature.getGeometry().invalidateBounds();
+
+ var editData = selectionLayer.getEditData();
+ var vertexFeature = editData.vertexFeature;
+ var vertexGeometry = vertexFeature.getGeometry();
+ var segment = segmentData[0];
+ editData.rTree.remove(ol.extent.boundingExtent(segment), segmentData);
+ segment[index] = vertex;
+ vertexGeometry.set(0, vertex[0]);
+ vertexGeometry.set(1, vertex[1]);
+ editData.rTree.insert(ol.extent.boundingExtent(segment), segmentData,
+ goog.getUid(feature));
+
+ selectionLayer.updateFeatures([feature, vertexFeature]);
+ }
+};
+
+
+/**
+ * @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)) {
+ if (goog.isNull(goog.events.getListener(selectionLayer,
+ ol.layer.VectorLayerEventType.ADD, this.addIndex, false, this))) {
+ 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 geometry = vertexFeature.getGeometry();
+ var vertex = /** @type {ol.Coordinate} */
+ (ol.coordinate.closestOnSegment(pixelCoordinate, segment));
+ var coordPixel = map.getPixelFromCoordinate(vertex);
+ var pixel1 = map.getPixelFromCoordinate(segment[0]);
+ var pixel2 = map.getPixelFromCoordinate(segment[1]);
+ var squaredDist1 = ol.coordinate.squaredDistance(coordPixel, pixel1);
+ var squaredDist2 = ol.coordinate.squaredDistance(coordPixel, 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;
+ }
+ geometry.set(0, vertex[0]);
+ geometry.set(1, vertex[1]);
+ selectionLayer.updateFeatures([vertexFeature]);
+ this.modifiable_ = true;
+ }
+ if (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);
+ var oldGeometry = geometry;
+ geometry = new geometry.constructor(coordinates);
+ var parent;
+ if (segmentData.length > 4) {
+ parent = segmentData[4];
+ parent[0][parent[1]] = geometry;
+ feature.getGeometry().invalidateBounds();
+ } else {
+ feature.setGeometry(geometry);
+ }
+ 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] === oldGeometry) {
+ allSegmentsData[2] = geometry;
+ if (allSegmentsData[3] > index) {
+ ++allSegmentsData[3];
+ }
+ }
+ }
+ var newSegment = [segment[0], vertex];
+ var newSegmentData = [newSegment, feature, geometry, index];
+ if (goog.isDef(parent)) {
+ newSegmentData.push(parent);
+ }
+ rTree.insert(ol.extent.boundingExtent(newSegment), newSegmentData, uid);
+ this.dragVertices_.push([selectionLayer, newSegmentData, 1]);
+ newSegment = [vertex, segment[1]];
+ newSegmentData = [newSegment, feature, geometry, index + 1];
+ if (goog.isDef(parent)) {
+ newSegmentData.push(parent);
+ }
+ rTree.insert(ol.extent.boundingExtent(newSegment), newSegmentData, uid);
+ this.dragVertices_.push([selectionLayer, newSegmentData, 0]);
+};
+
+
+/**
+ * @param {ol.layer.VectorLayerEventObject} evt Event object.
+ */
+ol.interaction.Modify.prototype.removeIndex = 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);
+ }
+ }
+};
diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js
index 48c90a2817..4810a77915 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,30 @@ 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;
+};
+
+
+/**
+ * TODO: This should go away - features should either fire events when changed,
+ * or feature changes should be made through the layer.
+ *
+ * @param {Array.} features Features.
+ */
+ol.layer.Vector.prototype.updateFeatures = function(features) {
+ var extent = ol.extent.createEmpty();
+ for (var i = features.length - 1; i >= 0; --i) {
+ var feature = features[i];
+ var geometry = feature.getGeometry();
+ this.featureCache_.remove(feature);
+ this.featureCache_.add(feature);
+ ol.extent.extend(extent, geometry.getBounds());
+ }
+ this.dispatchEvent(new ol.layer.VectorEvent(
+ ol.layer.VectorLayerEventType.CHANGE, features, [extent]));
};
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