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:
@@ -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]),
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user