diff --git a/src/ol/expr/expression.js b/src/ol/expr/expression.js index 25eb4163e5..ce22d86f67 100644 --- a/src/ol/expr/expression.js +++ b/src/ol/expr/expression.js @@ -277,7 +277,7 @@ ol.expr.lib[ol.expr.functions.GEOMETRY_TYPE] = function(type) { * @this {ol.Feature} */ ol.expr.lib[ol.expr.functions.RENDER_INTENT] = function(renderIntent) { - return this.renderIntent == renderIntent; + return this.getRenderIntent() == renderIntent; }; diff --git a/src/ol/feature.js b/src/ol/feature.js index 4adffbe482..caebeb56c5 100644 --- a/src/ol/feature.js +++ b/src/ol/feature.js @@ -45,8 +45,9 @@ ol.Feature = function(opt_values) { /** * The render intent for this feature. * @type {ol.layer.VectorLayerRenderIntent|string} + * @private */ - this.renderIntent = ol.layer.VectorLayerRenderIntent.DEFAULT; + this.renderIntent_ = ol.layer.VectorLayerRenderIntent.DEFAULT; /** * @type {Array.} @@ -175,6 +176,29 @@ ol.Feature.prototype.setGeometry = function(geometry) { }; +/** + * Gets the renderIntent for this feature. + * @return {string} Render intent. + */ +ol.Feature.prototype.getRenderIntent = function() { + return this.renderIntent_; +}; + + +/** + * Changes the renderIntent for this feature. + * @param {string} renderIntent Render intent. + */ +ol.Feature.prototype.setRenderIntent = function(renderIntent) { + this.renderIntent_ = renderIntent; + var geometry = this.getGeometry(); + if (!goog.isNull(geometry)) { + this.dispatchEvent(new ol.FeatureEvent( + ol.FeatureEventType.INTENTCHANGE, this, geometry.getBounds())); + } +}; + + /** * Set the symbolizers to be used for this feature. * @param {Array.} symbolizers Symbolizers for this @@ -196,7 +220,8 @@ ol.Feature.DEFAULT_GEOMETRY = 'geometry'; * @enum {string} */ ol.FeatureEventType = { - CHANGE: 'featurechange' + CHANGE: 'featurechange', + INTENTCHANGE: 'featureintentchange' }; diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 705e872671..eafea029ed 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -1,14 +1,12 @@ goog.provide('ol.interaction.Select'); goog.require('goog.array'); -goog.require('goog.object'); goog.require('ol.Feature'); goog.require('ol.events.ConditionType'); goog.require('ol.events.condition'); goog.require('ol.interaction.Interaction'); goog.require('ol.layer.Vector'); goog.require('ol.layer.VectorLayerRenderIntent'); -goog.require('ol.source.Vector'); @@ -36,20 +34,6 @@ ol.interaction.Select = function(opt_options) { this.addCondition_ = goog.isDef(options.addCondition) ? options.addCondition : ol.events.condition.shiftKeyOnly; - /** - * Mapping between original features and cloned features on selection layers. - * @type {Object.<*,Object.<*,ol.Feature>>} - * @private - */ - this.featureMap_ = {}; - - /** - * Mapping between original layers and selection layers, by map. - * @type {Object.<*,{map:ol.Map,layers:Object.<*,ol.layer.Vector>}>} - * @protected - */ - this.selectionLayers = {}; - /** * @type {null|function(ol.layer.Layer):boolean} * @private @@ -99,82 +83,29 @@ ol.interaction.Select.prototype.handleMapBrowserEvent = */ ol.interaction.Select.prototype.select = function(map, featuresByLayer, layers, clear) { - var mapId = goog.getUid(map); - if (!(mapId in this.selectionLayers)) { - this.selectionLayers[mapId] = {map: map, layers: {}}; - } for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { var layer = layers[i]; - var layerId = goog.getUid(layer); - var selectionLayer = this.selectionLayers[mapId].layers[layerId]; - if (!goog.isDef(selectionLayer)) { - selectionLayer = new ol.layer.Vector({ - source: new ol.source.Vector({parser: null}), - style: layer instanceof ol.layer.Vector ? layer.getStyle() : null - }); - selectionLayer.setTemporary(true); - map.addLayer(selectionLayer); - this.selectionLayers[mapId].layers[layerId] = selectionLayer; - this.featureMap_[layerId] = {}; + if (!(layer instanceof ol.layer.Vector)) { + // TODO Support non-vector layers and remove this + continue; } - var selectedFeatures, unselectedFeatures; - if (goog.isFunction(layer.setRenderIntent)) { - selectedFeatures = []; - unselectedFeatures = []; - } - var features = featuresByLayer[i]; - var numFeatures = features.length; - var featuresToAdd = []; - var featuresToRemove = []; - var featureMap = this.featureMap_[layerId]; - var oldFeatureMap = featureMap; + var featuresToSelect = featuresByLayer[i]; + var selectedFeatures = layer.getFeatures( + ol.layer.Vector.selectedFeaturesFilter); if (clear) { - for (var f in featureMap) { - if (goog.isDef(unselectedFeatures)) { - unselectedFeatures.push(layer.getFeatureWithUid(f)); - } - featuresToRemove.push(featureMap[f]); - } - featureMap = {}; - this.featureMap_[layerId] = featureMap; - } - for (var j = 0; j < numFeatures; ++j) { - var feature = features[j]; - var featureId = goog.getUid(feature); - var clone = featureMap[featureId]; - if (clone) { - // TODO: make toggle configurable - if (goog.isDef(unselectedFeatures)) { - unselectedFeatures.push(feature); - } - delete featureMap[featureId]; - featuresToRemove.push(clone); - } else if (!(featureId in oldFeatureMap)) { - clone = new ol.Feature(feature.getAttributes()); - clone.setGeometry(feature.getGeometry().clone()); - clone.setId(feature.getId()); - clone.setSymbolizers(feature.getSymbolizers()); - clone.renderIntent = ol.layer.VectorLayerRenderIntent.SELECTED; - featureMap[featureId] = clone; - if (goog.isDef(selectedFeatures)) { - selectedFeatures.push(feature); - } - featuresToAdd.push(clone); + for (var j = selectedFeatures.length - 1; j >= 0; --j) { + selectedFeatures[j].setRenderIntent( + ol.layer.VectorLayerRenderIntent.DEFAULT); } } - if (goog.isFunction(layer.setRenderIntent)) { - layer.setRenderIntent(ol.layer.VectorLayerRenderIntent.HIDDEN, - selectedFeatures); - layer.setRenderIntent(ol.layer.VectorLayerRenderIntent.DEFAULT, - unselectedFeatures); - } - selectionLayer.removeFeatures(featuresToRemove); - selectionLayer.addFeatures(featuresToAdd); - if (goog.object.getCount(featureMap) == 0) { - map.removeLayer(selectionLayer); - delete this.selectionLayers[mapId].layers[layerId]; - delete this.featureMap_[layerId]; + for (var j = featuresToSelect.length - 1; j >= 0; --j) { + var feature = featuresToSelect[j]; + // TODO: Make toggle configurable + feature.setRenderIntent(feature.getRenderIntent() == + ol.layer.VectorLayerRenderIntent.SELECTED ? + ol.layer.VectorLayerRenderIntent.DEFAULT : + ol.layer.VectorLayerRenderIntent.SELECTED); } // TODO: Dispatch an event with selectedFeatures and unselectedFeatures } diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 235433a756..71da158900 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -10,6 +10,7 @@ goog.require('ol.Feature'); goog.require('ol.FeatureEventType'); goog.require('ol.extent'); goog.require('ol.layer.Layer'); +goog.require('ol.layer.VectorLayerRenderIntent'); goog.require('ol.proj'); goog.require('ol.source.Vector'); goog.require('ol.structs.RTree'); @@ -103,16 +104,6 @@ ol.layer.FeatureCache.prototype.getFeaturesByIds_ = function(ids) { }; -/** - * @param {string} uid Feature uid. - * @return {ol.Feature|undefined} The feature with the provided uid if it is in - * the cache, otherwise undefined. - */ -ol.layer.FeatureCache.prototype.getFeatureWithUid = function(uid) { - return this.idLookup_[uid]; -}; - - /** * Remove a feature from the cache. * @param {ol.Feature} feature Feature. @@ -188,6 +179,8 @@ ol.layer.Vector.prototype.addFeatures = function(features) { } goog.events.listen(feature, ol.FeatureEventType.CHANGE, this.handleFeatureChange_, false, this); + goog.events.listen(feature, ol.FeatureEventType.INTENTCHANGE, + this.handleIntentChange_, false, this); } this.dispatchEvent(new ol.layer.VectorEvent(ol.layer.VectorEventType.ADD, features, [extent])); @@ -217,6 +210,23 @@ ol.layer.Vector.prototype.handleFeatureChange_ = function(evt) { }; +/** + * Listener for render intent change events of features. + * @param {ol.FeatureEvent} evt The feature intent change event. + * @private + */ +ol.layer.Vector.prototype.handleIntentChange_ = function(evt) { + goog.asserts.assertInstanceof(evt.target, ol.Feature); + var feature = /** @type {ol.Feature} */ (evt.target); + var geometry = feature.getGeometry(); + if (!goog.isNull(geometry)) { + this.dispatchEvent(new ol.layer.VectorEvent( + ol.layer.VectorEventType.INTENTCHANGE, [feature], + [geometry.getBounds()])); + } +}; + + /** * Remove all features from the layer. */ @@ -251,6 +261,30 @@ ol.layer.Vector.prototype.getStyle = function() { }; +/** + * Returns an array of features that match a filter. This will not fetch data, + * it only considers features that are loaded already. + * @param {(function(ol.Feature):boolean)=} opt_filter Filter function. + * @return {Array.} Features that match the filter, or all features + * if no filter was provided. + */ +ol.layer.Vector.prototype.getFeatures = function(opt_filter) { + var result; + var features = this.featureCache_.getFeaturesObject(); + if (goog.isDef(opt_filter)) { + result = []; + for (var f in features) { + if (opt_filter(features[f]) === true) { + result.push(features[f]); + } + } + } else { + result = goog.object.getValues(features); + } + return result; +}; + + /** * Get all features whose bounding box intersects the provided extent. This * method is intended for being called by the renderer. When null is returned, @@ -332,16 +366,6 @@ ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral = }; -/** - * @param {string|number} uid Feature uid. - * @return {ol.Feature|undefined} The feature with the provided uid if it is on - * the layer, otherwise undefined. - */ -ol.layer.Vector.prototype.getFeatureWithUid = function(uid) { - return this.featureCache_.getFeatureWithUid(/** @type {string} */ (uid)); -}; - - /** * @param {Object|Element|Document|string} data Feature data. * @param {ol.parser.Parser} parser Feature parser. @@ -423,31 +447,6 @@ ol.layer.Vector.prototype.removeFeatures = function(features) { }; -/** - * Changes the renderIntent for an array of features. - * @param {string} renderIntent Render intent. - * @param {Array.=} opt_features Features to change the renderIntent - * for. If not provided, all features will be changed. - */ -ol.layer.Vector.prototype.setRenderIntent = - function(renderIntent, opt_features) { - var features = goog.isDef(opt_features) ? opt_features : - goog.object.getValues(this.featureCache_.getFeaturesObject()); - var extent = ol.extent.createEmpty(), - feature, geometry; - for (var i = features.length - 1; i >= 0; --i) { - feature = features[i]; - feature.renderIntent = renderIntent; - geometry = feature.getGeometry(); - if (!goog.isNull(geometry)) { - ol.extent.extend(extent, geometry.getBounds()); - } - } - this.dispatchEvent(new ol.layer.VectorEvent( - ol.layer.VectorEventType.INTENTCHANGE, features, [extent])); -}; - - /** * @param {boolean} temp Whether this layer is temporary. */ @@ -479,6 +478,15 @@ ol.layer.Vector.uidTransformFeatureInfo = function(features) { }; +/** + * @param {ol.Feature} feature Feature. + * @return {boolean} Whether the feature is selected. + */ +ol.layer.Vector.selectedFeaturesFilter = function(feature) { + return feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.SELECTED; +}; + + /** * @constructor @@ -511,6 +519,6 @@ goog.inherits(ol.layer.VectorEvent, goog.events.Event); ol.layer.VectorEventType = { ADD: 'featureadd', CHANGE: 'featurechange', - REMOVE: 'featureremove', - INTENTCHANGE: 'intentchange' + INTENTCHANGE: 'featureintentchange', + REMOVE: 'featureremove' }; diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 2cc87bc590..5e39824950 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -275,7 +275,8 @@ ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel = halfWidth, halfHeight, uid, coordinates, j; for (var id in candidates) { candidate = candidates[id]; - if (candidate.renderIntent == ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (candidate.getRenderIntent() == + ol.layer.VectorLayerRenderIntent.HIDDEN) { continue; } geom = candidate.getGeometry(); diff --git a/src/ol/renderer/canvas/canvasvectorrenderer.js b/src/ol/renderer/canvas/canvasvectorrenderer.js index 8f5b2c6dd4..30d7a3ebca 100644 --- a/src/ol/renderer/canvas/canvasvectorrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorrenderer.js @@ -160,7 +160,7 @@ ol.renderer.canvas.Vector.prototype.renderLineStringFeatures_ = context.beginPath(); for (i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; - if (feature.renderIntent === ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) { continue; } id = goog.getUid(feature); @@ -249,7 +249,7 @@ ol.renderer.canvas.Vector.prototype.renderPointFeatures_ = context.globalAlpha = alpha; for (i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; - if (feature.renderIntent === ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) { continue; } id = goog.getUid(feature); @@ -325,7 +325,7 @@ ol.renderer.canvas.Vector.prototype.renderText_ = for (var i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; - if (feature.renderIntent === ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) { continue; } vecs = ol.renderer.canvas.Vector.getLabelVectors( @@ -393,7 +393,7 @@ ol.renderer.canvas.Vector.prototype.renderPolygonFeatures_ = context.beginPath(); for (i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; - if (feature.renderIntent === ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) { continue; } geometry = feature.getGeometry(); diff --git a/test/spec/ol/expr/expression.test.js b/test/spec/ol/expr/expression.test.js index 4f0f0942db..35dfa37144 100644 --- a/test/spec/ol/expr/expression.test.js +++ b/test/spec/ol/expr/expression.test.js @@ -894,7 +894,7 @@ describe('ol.expr.lib', function() { describe('renderIntent()', function() { var feature = new ol.Feature(); - feature.renderIntent = 'foo'; + feature.setRenderIntent('foo'); var isFoo = parse('renderIntent("foo")'); var isBar = parse('renderIntent("bar")'); diff --git a/test/spec/ol/interaction/selectinteraction.test.js b/test/spec/ol/interaction/selectinteraction.test.js index 12de3a07a6..cedd9211f1 100644 --- a/test/spec/ol/interaction/selectinteraction.test.js +++ b/test/spec/ol/interaction/selectinteraction.test.js @@ -46,29 +46,27 @@ describe('ol.interaction.Select', function() { describe('#select', function() { + var selectedFeaturesFilter = function(feature) { + return feature.getRenderIntent() == 'selected'; + }; + it('toggles selection of features', function() { select.select(map, [features], [vector]); - var layer = select.selectionLayers[goog.getUid(map)] - .layers[goog.getUid(vector)]; - expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); + expect(vector.getFeatures(selectedFeaturesFilter).length).to.be(2); select.select(map, [features], [vector]); - expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(0); + expect(vector.getFeatures(selectedFeaturesFilter).length).to.be(0); }); it('can append features to an existing selection', function() { - select.select(map, [[features[0]]], [vector]); + select.select(map, [[features[0]]], [vector], true); select.select(map, [[features[1]]], [vector]); - var layer = select.selectionLayers[goog.getUid(map)] - .layers[goog.getUid(vector)]; - expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); + expect(vector.getFeatures(selectedFeaturesFilter).length).to.be(2); }); it('can clear a selection before selecting new features', function() { select.select(map, [[features[0]]], [vector], true); select.select(map, [[features[1]]], [vector], true); - var layer = select.selectionLayers[goog.getUid(map)] - .layers[goog.getUid(vector)]; - expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(1); + expect(vector.getFeatures(selectedFeaturesFilter).length).to.be(1); }); }); @@ -76,7 +74,6 @@ describe('ol.interaction.Select', function() { }); goog.require('goog.dispose'); -goog.require('goog.object'); goog.require('ol.Map'); goog.require('ol.interaction.Select'); goog.require('ol.layer.Vector');