diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index f6a1aeefba..db85cf9fc1 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -38,6 +38,12 @@ ol.control.Select = function(opt_options) { */ this.active_ = false; + /** + * @type {Object.} + * @private + */ + this.featureMap_ = {}; + /** * @type {ol.layer.Vector} * @protected @@ -150,31 +156,44 @@ ol.control.Select.prototype.handleClick = function(evt) { */ ol.control.Select.prototype.select = function(featuresByLayer, clear) { for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { + var layer = this.layers_[i]; var features = featuresByLayer[i]; var numFeatures = features.length; var selectedFeatures = []; + var featuresToAdd = []; var unselectedFeatures = []; + var featuresToRemove = []; for (var j = 0; j < numFeatures; ++j) { var feature = features[j]; - var selectedFeature = this.layer.getFeatureWithUid(goog.getUid(feature)); - if (selectedFeature) { + var uid = goog.getUid(feature); + var clone = this.featureMap_[uid]; + if (clone) { // TODO: make toggle configurable - unselectedFeatures.push(selectedFeature); - } else { selectedFeatures.push(feature); + featuresToRemove.push(clone); + delete this.featureMap_[uid]; + } + if (clear) { + for (var f in this.featureMap_) { + unselectedFeatures.push(layer.getFeatureWithUid(f)); + featuresToRemove.push(this.featureMap_[f]); + } + this.featureMap_ = {}; + } + if (!clone) { + clone = feature.clone(); + this.featureMap_[uid] = clone; + selectedFeatures.push(feature); + featuresToAdd.push(clone); } } - var layer = this.layers_[i]; if (goog.isFunction(layer.setRenderIntent)) { // TODO: Implement setRenderIntent for ol.layer.Vector layer.setRenderIntent('hidden', selectedFeatures); layer.setRenderIntent('default', unselectedFeatures); } - if (clear) { - this.layer.clear(); - } - this.layer.removeFeatures(unselectedFeatures); - this.layer.addFeatures(selectedFeatures); + this.layer.removeFeatures(featuresToRemove); + this.layer.addFeatures(featuresToAdd); this.dispatchEvent(/** @type {ol.control.SelectEventObject} */ ({ layer: layer, selected: selectedFeatures, diff --git a/src/ol/feature.js b/src/ol/feature.js index 24de9c40e3..aa19448f98 100644 --- a/src/ol/feature.js +++ b/src/ol/feature.js @@ -44,6 +44,20 @@ ol.Feature = function(opt_values) { goog.inherits(ol.Feature, ol.Object); +/** + * Create a clone of this feature. Both the feature and its geometry will be + * cloned. + * @return {ol.Feature} A clone of this feature, with a cloned geometry. + */ +ol.Feature.prototype.clone = function() { + var clone = new ol.Feature(this.getAttributes()); + clone.setGeometry(this.getGeometry().clone()); + clone.featureId_ = this.featureId_; + clone.symbolizers_ = this.symbolizers_; + return clone; +}; + + /** * Gets a copy of the attributes of this feature. * @return {Object.} Attributes object. diff --git a/src/ol/geom/geometry.js b/src/ol/geom/geometry.js index 964e6fa4ce..383c255fa1 100644 --- a/src/ol/geom/geometry.js +++ b/src/ol/geom/geometry.js @@ -27,6 +27,19 @@ ol.geom.Geometry = function() { ol.geom.Geometry.prototype.dimension; +/** + * Create a clone of this geometry. The clone will not be represented in any + * shared structure. + * @return {ol.geom.Geometry} The cloned geometry. + */ +ol.geom.Geometry.prototype.clone = function() { + var clone = new this.constructor(this.getCoordinates()); + clone.bounds_ = this.bounds_; + clone.dimension = this.dimension; + return clone; +}; + + /** * Get the rectangular 2D envelope for this geoemtry. * @return {ol.Extent} The bounding rectangular envelope. diff --git a/test/spec/ol/control/selectcontrol.test.js b/test/spec/ol/control/selectcontrol.test.js index d345ea7b68..91a9a729fd 100644 --- a/test/spec/ol/control/selectcontrol.test.js +++ b/test/spec/ol/control/selectcontrol.test.js @@ -120,8 +120,8 @@ describe('ol.control.Select', function() { it('can clear a selection before selecting new features', function() { select.select([[features[0]]], true); select.select([[features[1]]], true); - expect(goog.object.getValues(select.layer.featureCache_.idLookup_)[0]) - .to.eql(features[1]); + expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) + .to.be(1); }); }); diff --git a/test/spec/ol/feature.test.js b/test/spec/ol/feature.test.js index 12ae10ba37..fb31212579 100644 --- a/test/spec/ol/feature.test.js +++ b/test/spec/ol/feature.test.js @@ -34,6 +34,24 @@ describe('ol.Feature', function() { }); + describe('#clone()', function() { + + it('creates a clone with a cloned geometry', function() { + var feature = new ol.Feature({ + loc: new ol.geom.Point([10, 20]), + foo: 'bar' + }); + feature.setFeatureId('foo'); + var clone = feature.clone(); + expect(clone).to.not.be(feature); + expect(clone.get('foo')).to.be('bar'); + expect(clone.getFeatureId()).to.be('foo'); + expect(clone.getGeometry()).to.not.be(feature.getGeometry()); + expect(clone.getGeometry().getCoordinates()).to.eql([10, 20]); + }); + + }); + describe('#get()', function() { it('returns values set at construction', function() { diff --git a/test/spec/ol/geom/geometrycollection.test.js b/test/spec/ol/geom/geometrycollection.test.js index d9c1616c33..622cd48943 100644 --- a/test/spec/ol/geom/geometrycollection.test.js +++ b/test/spec/ol/geom/geometrycollection.test.js @@ -55,6 +55,23 @@ describe('ol.geom.GeometryCollection', function() { }); + describe('#clone()', function() { + + it('has a working clone method', function() { + var point = new ol.geom.Point([10, 20]); + var line = new ol.geom.LineString([[10, 20], [30, 40]]); + var poly = new ol.geom.Polygon([outer, inner1, inner2]); + var multi = new ol.geom.GeometryCollection([point, line, poly]); + var clone = multi.clone(); + expect(clone).to.not.be(multi); + var components = clone.components; + expect(components[0]).to.eql([10, 20]); + expect(components[1]).to.eql([[10, 20], [30, 40]]); + expect(components[2]).to.eql([outer, inner1, inner2]); + }); + + }); + describe('#getBounds()', function() { it('returns the bounding extent', function() {