Dynamic layers and lazy selection layer creation

With this change, the user provides a filter function instead of
an array of layers. Selection layers are created lazily, and
addition/removal of layers is not handled by the control to give
the user more options, as suggested by @elemoine.
This commit is contained in:
ahocevar
2013-08-22 19:29:08 +02:00
parent a417b75c1f
commit c6e61e2d23
4 changed files with 54 additions and 89 deletions

View File

@@ -46,7 +46,9 @@ var vector = new ol.layer.Vector({
}) })
}); });
var selectControl = new ol.control.Select({layers: [vector]}); var selectControl = new ol.control.Select({
layerFilter: function(layer) { return layer === vector; }
});
var map = new ol.Map({ var map = new ol.Map({
controls: ol.control.defaults().extend([selectControl]), controls: ol.control.defaults().extend([selectControl]),

View File

@@ -205,7 +205,8 @@
* @typedef {Object} ol.control.SelectOptions * @typedef {Object} ol.control.SelectOptions
* @property {string|undefined} className CSS class name. Default is 'ol-select'. * @property {string|undefined} className CSS class name. Default is 'ol-select'.
* @property {Element|undefined} element Element. * @property {Element|undefined} element Element.
* @property {Array.<ol.layer.Layer>} layers Layers to select features on. * @property {undefined|function(ol.layer.Layer):boolean} layerFilter Filter
* function to restrict selection to a subset of layers.
* @property {ol.Map|undefined} map Map. * @property {ol.Map|undefined} map Map.
* @property {Element|undefined} target Target. * @property {Element|undefined} target Target.
*/ */

View File

@@ -6,7 +6,6 @@ goog.require('goog.dom.TagName');
goog.require('goog.dom.classes'); goog.require('goog.dom.classes');
goog.require('goog.events'); goog.require('goog.events');
goog.require('goog.events.EventType'); goog.require('goog.events.EventType');
goog.require('ol.CollectionEventType');
goog.require('ol.MapBrowserEvent.EventType'); goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.control.Control'); goog.require('ol.control.Control');
goog.require('ol.css'); goog.require('ol.css');
@@ -41,24 +40,25 @@ ol.control.Select = function(opt_options) {
this.active_ = false; this.active_ = false;
/** /**
* @type {Array.<Object.<string, ol.Feature>>} * Mapping between original features and cloned features on selection layers.
* @type {Object.<*,Object.<string,ol.Feature>>}
* @private * @private
*/ */
this.featureMap_ = []; this.featureMap_ = {};
/** /**
* @type {Object.<*, ol.layer.Vector>} * Mapping between original layers and selection layers.
* @type {Object.<*,ol.layer.Vector>}
* @protected * @protected
*/ */
this.selectionLayers; this.selectionLayers = {};
/** /**
* @type {Array.<ol.layer.Layer>} * @type {null|function(ol.layer.Layer):boolean}
* @private * @private
*/ */
this.layers_ = goog.isDef(options.layers) ? options.layers : []; this.layerFilter_ = goog.isDef(options.layerFilter) ?
options.layerFilter : null;
this.createSelectionLayers_();
// TODO: css/button refactoring // TODO: css/button refactoring
var className = goog.isDef(options.className) ? options.className : var className = goog.isDef(options.className) ? options.className :
@@ -86,26 +86,6 @@ ol.control.Select = function(opt_options) {
goog.inherits(ol.control.Select, ol.control.Control); goog.inherits(ol.control.Select, ol.control.Control);
/**
* Create a selection layer for each source layer.
* @private
*/
ol.control.Select.prototype.createSelectionLayers_ = function() {
this.selectionLayers = {};
for (var i = 0, ii = this.layers_.length; i < ii; ++i) {
this.featureMap_.push({});
var layer = this.layers_[i];
var selectionLayer = new ol.layer.Vector({
source: new ol.source.Vector({parser: null}),
style: layer.getStyle()
});
selectionLayer.setTemporary(true);
selectionLayer.bindTo('visible', layer);
this.selectionLayers[goog.getUid(layer)] = selectionLayer;
}
};
/** /**
* @param {goog.events.BrowserEvent} browserEvent Browser event. * @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private * @private
@@ -164,14 +144,17 @@ ol.control.Select.prototype.deactivate = function() {
* @param {ol.MapBrowserEvent} evt Event. * @param {ol.MapBrowserEvent} evt Event.
*/ */
ol.control.Select.prototype.handleClick = function(evt) { ol.control.Select.prototype.handleClick = function(evt) {
var layers = goog.array.filter(this.layers_, this.layerFilterFunction, this); var map = this.getMap();
var layers = map.getLayerGroup().getLayersArray();
if (!goog.isNull(this.layerFilter_)) {
layers = goog.array.filter(layers, this.layerFilter_);
}
var clear = !ol.interaction.condition.shiftKeyOnly(evt.browserEvent); var clear = !ol.interaction.condition.shiftKeyOnly(evt.browserEvent);
function select(featuresByLayer) { function select(featuresByLayer) {
this.select(featuresByLayer, layers, clear); this.select(featuresByLayer, layers, clear);
} }
var map = this.getMap();
map.getFeatures({ map.getFeatures({
layers: layers, layers: layers,
pixel: evt.getPixel(), pixel: evt.getPixel(),
@@ -180,28 +163,6 @@ ol.control.Select.prototype.handleClick = function(evt) {
}; };
/**
* @param {ol.CollectionEvent} evt Event.
*/
ol.control.Select.prototype.handleLayerCollectionChange = function(evt) {
var layer = /** @type {ol.layer.Layer} */ (evt.elem);
var selectionLayer = this.selectionLayers[goog.getUid(layer)];
if (goog.isDef(selectionLayer)) {
selectionLayer.setVisible(evt.type === ol.CollectionEventType.ADD);
}
};
/**
* @param {ol.layer.Layer} layer Layer.
* @param {number} index Index.
* @return {boolean} Whether to include the layer.
*/
ol.control.Select.prototype.layerFilterFunction = function(layer, index) {
return this.selectionLayers[goog.getUid(layer)].getVisible();
};
/** /**
* @param {Array.<Array.<ol.Feature>>} featuresByLayer Features by layer. * @param {Array.<Array.<ol.Feature>>} featuresByLayer Features by layer.
* @param {Array.<ol.layer.Layer>} layers The queried layers. * @param {Array.<ol.layer.Layer>} layers The queried layers.
@@ -210,24 +171,35 @@ ol.control.Select.prototype.layerFilterFunction = function(layer, index) {
ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) { ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) {
for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) {
var layer = layers[i]; var layer = layers[i];
var selectionLayer = var layerId = goog.getUid(layer);
this.selectionLayers[goog.getUid(layer)]; var selectionLayer = this.selectionLayers[layerId];
if (!goog.isDef(selectionLayer)) {
selectionLayer = new ol.layer.Vector({
source: new ol.source.Vector({parser: null}),
style: layer.getStyle()
});
selectionLayer.setTemporary(true);
this.getMap().addLayer(selectionLayer);
this.selectionLayers[layerId] = selectionLayer;
this.featureMap_[layerId] = {};
}
var features = featuresByLayer[i]; var features = featuresByLayer[i];
var numFeatures = features.length; var numFeatures = features.length;
var selectedFeatures = []; var selectedFeatures = [];
var featuresToAdd = []; var featuresToAdd = [];
var unselectedFeatures = []; var unselectedFeatures = [];
var featuresToRemove = []; var featuresToRemove = [];
var featureMap = this.featureMap_[i]; var featureMap = this.featureMap_[layerId];
for (var j = 0; j < numFeatures; ++j) { for (var j = 0; j < numFeatures; ++j) {
var feature = features[j]; var feature = features[j];
var uid = goog.getUid(feature); var featureId = goog.getUid(feature);
var clone = featureMap[uid]; var clone = featureMap[featureId];
if (clone) { if (clone) {
// TODO: make toggle configurable // TODO: make toggle configurable
unselectedFeatures.push(feature); unselectedFeatures.push(feature);
featuresToRemove.push(clone); featuresToRemove.push(clone);
delete featureMap[uid]; delete featureMap[featureId];
} }
if (clear) { if (clear) {
for (var f in featureMap) { for (var f in featureMap) {
@@ -235,11 +207,11 @@ ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) {
featuresToRemove.push(featureMap[f]); featuresToRemove.push(featureMap[f]);
} }
featureMap = {}; featureMap = {};
this.featureMap_[i] = featureMap; this.featureMap_[layerId] = featureMap;
} }
if (!clone) { if (!clone) {
clone = feature.clone(); clone = feature.clone();
featureMap[uid] = clone; featureMap[featureId] = clone;
clone.renderIntent = ol.layer.VectorLayerRenderIntent.SELECTED; clone.renderIntent = ol.layer.VectorLayerRenderIntent.SELECTED;
selectedFeatures.push(feature); selectedFeatures.push(feature);
featuresToAdd.push(clone); featuresToAdd.push(clone);
@@ -261,15 +233,3 @@ ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) {
})); }));
} }
}; };
/**
* @inheritDoc
*/
ol.control.Select.prototype.setMap = function(map) {
goog.base(this, 'setMap', map);
var layers = map.getLayers();
goog.events.listen(layers,
[ol.CollectionEventType.ADD, ol.CollectionEventType.REMOVE],
this.handleLayerCollectionChange, false, this);
};

View File

@@ -1,7 +1,7 @@
goog.provide('ol.test.control.Select'); goog.provide('ol.test.control.Select');
describe('ol.control.Select', function() { describe('ol.control.Select', function() {
var map, target, select, features; var map, target, select, vector, features;
beforeEach(function() { beforeEach(function() {
target = document.createElement('div'); target = document.createElement('div');
@@ -27,10 +27,10 @@ describe('ol.control.Select', function() {
} }
}] }]
})); }));
var layer = new ol.layer.Vector({source: new ol.source.Vector({})}); vector = new ol.layer.Vector({source: new ol.source.Vector({})});
layer.addFeatures(features); vector.addFeatures(features);
select = new ol.control.Select({ select = new ol.control.Select({
layers: [layer], layerFilter: function(layer) { return layer === vector; },
map: map map: map
}); });
}); });
@@ -69,9 +69,11 @@ describe('ol.control.Select', function() {
}); });
describe('#activate and #deactivate', function() { describe('#activate and #deactivate', function() {
it('adds a temp layer to the map only when active', function() { it('adds a temp layer to the map only when active and in use', function() {
expect(map.getLayers().getLength()).to.be(0); expect(map.getLayers().getLength()).to.be(0);
select.activate(); select.activate();
expect(map.getLayers().getLength()).to.be(0);
select.select([features[0]], [vector]);
expect(map.getLayers().getLength()).to.be(1); expect(map.getLayers().getLength()).to.be(1);
expect(map.getLayers().getAt(0).getTemporary()).to.be(true); expect(map.getLayers().getAt(0).getTemporary()).to.be(true);
select.deactivate(); select.deactivate();
@@ -97,24 +99,24 @@ describe('ol.control.Select', function() {
describe('#select', function() { describe('#select', function() {
it('toggles selection of features', function() { it('toggles selection of features', function() {
var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; select.select([features], [vector]);
select.select([features], select.layers_); var layer = select.selectionLayers[goog.getUid(vector)];
expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2);
select.select([features], select.layers_); select.select([features], [vector]);
expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(0); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(0);
}); });
it('can append features to an existing selection', function() { it('can append features to an existing selection', function() {
var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; select.select([[features[0]]], [vector]);
select.select([[features[0]]], select.layers_); select.select([[features[1]]], [vector]);
select.select([[features[1]]], select.layers_); var layer = select.selectionLayers[goog.getUid(vector)];
expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2);
}); });
it('can clear a selection before selecting new features', function() { it('can clear a selection before selecting new features', function() {
var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; select.select([[features[0]]], [vector], true);
select.select([[features[0]]], select.layers_, true); select.select([[features[1]]], [vector], true);
select.select([[features[1]]], select.layers_, true); var layer = select.selectionLayers[goog.getUid(vector)];
expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(1); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(1);
}); });