Provide a method for retrieving features by id

This commit is contained in:
Tim Schaub
2014-05-20 09:37:36 -06:00
parent 34cabd1579
commit 652f11cefa
2 changed files with 210 additions and 0 deletions

View File

@@ -69,6 +69,20 @@ ol.source.Vector = function(opt_options) {
*/ */
this.nullGeometryFeatures_ = {}; this.nullGeometryFeatures_ = {};
/**
* A lookup of features by id (the return from feature.getId()).
* @private
* @type {Object.<string, ol.Feature>}
*/
this.idIndex_ = {};
/**
* A lookup of features without id (keyed by goog.getUid(feature)).
* @private
* @type {Object.<string, ol.Feature>}
*/
this.undefIdIndex_ = {};
/** /**
* @private * @private
* @type {Object.<string, Array.<goog.events.Key>>} * @type {Object.<string, Array.<goog.events.Key>>}
@@ -116,6 +130,14 @@ ol.source.Vector.prototype.addFeatureInternal = function(feature) {
} else { } else {
this.nullGeometryFeatures_[goog.getUid(feature).toString()] = feature; this.nullGeometryFeatures_[goog.getUid(feature).toString()] = feature;
} }
var id = feature.getId();
if (goog.isDef(id)) {
var sid = id.toString();
goog.asserts.assert(!(goog.isDef(this.idIndex_[sid])));
this.idIndex_[sid] = feature;
} else {
this.undefIdIndex_[goog.getUid(feature).toString()] = feature;
}
this.dispatchEvent( this.dispatchEvent(
new ol.source.VectorEvent(ol.source.VectorEventType.ADDFEATURE, feature)); new ol.source.VectorEvent(ol.source.VectorEventType.ADDFEATURE, feature));
}; };
@@ -314,6 +336,20 @@ ol.source.Vector.prototype.getExtent = function() {
}; };
/**
* Get a feature by its identifier (the value returned by feature.getId()).
* Note that the index treats string and numeric identifiers as the same. So
* `source.getFeatureById(2)` will return a feature with id `'2'` or `2`.
*
* @param {string|number} id Feature identifier.
* @return {ol.Feature} The feature (or `null` if not found).
*/
ol.source.Vector.prototype.getFeatureById = function(id) {
var feature = this.idIndex_[id.toString()];
return goog.isDef(feature) ? feature : null;
};
/** /**
* @param {goog.events.Event} event Event. * @param {goog.events.Event} event Event.
* @private * @private
@@ -336,6 +372,35 @@ ol.source.Vector.prototype.handleFeatureChange_ = function(event) {
this.rBush_.update(extent, feature); this.rBush_.update(extent, feature);
} }
} }
var id = feature.getId();
var removed;
if (goog.isDef(id)) {
var sid = id.toString();
if (featureKey in this.undefIdIndex_) {
delete this.undefIdIndex_[featureKey];
goog.asserts.assert(!goog.isDef(this.idIndex_[sid]),
'Duplicate feature id: ' + id);
this.idIndex_[sid] = feature;
} else {
if (this.idIndex_[sid] !== feature) {
removed = this.removeFromIdIndex_(feature);
goog.asserts.assert(removed,
'Expected feature to be removed from index');
goog.asserts.assert(!(sid in this.idIndex_),
'Duplicate feature id: ' + id);
this.idIndex_[sid] = feature;
}
}
} else {
if (!(featureKey in this.undefIdIndex_)) {
removed = this.removeFromIdIndex_(feature);
goog.asserts.assert(removed,
'Expected feature to be removed from index');
this.undefIdIndex_[featureKey] = feature;
} else {
goog.asserts.assert(this.undefIdIndex_[featureKey] === feature);
}
}
this.dispatchChangeEvent(); this.dispatchChangeEvent();
}; };
@@ -384,11 +449,37 @@ ol.source.Vector.prototype.removeFeatureInternal = function(feature) {
goog.array.forEach(this.featureChangeKeys_[featureKey], goog.array.forEach(this.featureChangeKeys_[featureKey],
goog.events.unlistenByKey); goog.events.unlistenByKey);
delete this.featureChangeKeys_[featureKey]; delete this.featureChangeKeys_[featureKey];
var id = feature.getId();
if (goog.isDef(id)) {
delete this.idIndex_[id.toString()];
} else {
delete this.undefIdIndex_[featureKey];
}
this.dispatchEvent(new ol.source.VectorEvent( this.dispatchEvent(new ol.source.VectorEvent(
ol.source.VectorEventType.REMOVEFEATURE, feature)); ol.source.VectorEventType.REMOVEFEATURE, feature));
}; };
/**
* Remove a feature from the id index. Called internally when the feature id
* may have changed.
* @param {ol.Feature} feature The feature.
* @return {boolean} Removed the feature from the index.
* @private
*/
ol.source.Vector.prototype.removeFromIdIndex_ = function(feature) {
var removed = false;
for (var id in this.idIndex_) {
if (this.idIndex_[id] === feature) {
delete this.idIndex_[id];
removed = true;
break;
}
}
return removed;
};
/** /**
* @constructor * @constructor

View File

@@ -244,6 +244,125 @@ describe('ol.source.Vector', function() {
}); });
describe('#getFeatureById()', function() {
var source;
beforeEach(function() {
source = new ol.source.Vector();
});
it('returns a feature by id', function() {
var feature = new ol.Feature();
feature.setId('foo');
source.addFeature(feature);
expect(source.getFeatureById('foo')).to.be(feature);
});
it('returns a feature by id (set after add)', function() {
var feature = new ol.Feature();
source.addFeature(feature);
expect(source.getFeatureById('foo')).to.be(null);
feature.setId('foo');
expect(source.getFeatureById('foo')).to.be(feature);
});
it('returns null when no feature is found', function() {
var feature = new ol.Feature();
feature.setId('foo');
source.addFeature(feature);
expect(source.getFeatureById('bar')).to.be(null);
});
it('returns null after removing feature', function() {
var feature = new ol.Feature();
feature.setId('foo');
source.addFeature(feature);
expect(source.getFeatureById('foo')).to.be(feature);
source.removeFeature(feature);
expect(source.getFeatureById('foo')).to.be(null);
});
it('returns null after unsetting id', function() {
var feature = new ol.Feature();
feature.setId('foo');
source.addFeature(feature);
expect(source.getFeatureById('foo')).to.be(feature);
feature.setId(undefined);
expect(source.getFeatureById('foo')).to.be(null);
});
it('returns null after clear', function() {
var feature = new ol.Feature();
feature.setId('foo');
source.addFeature(feature);
expect(source.getFeatureById('foo')).to.be(feature);
source.clear();
expect(source.getFeatureById('foo')).to.be(null);
});
it('returns null when no features are indexed', function() {
expect(source.getFeatureById('foo')).to.be(null);
source.addFeature(new ol.Feature());
expect(source.getFeatureById('foo')).to.be(null);
});
it('returns correct feature after add/remove/add', function() {
expect(source.getFeatureById('foo')).to.be(null);
var first = new ol.Feature();
first.setId('foo');
source.addFeature(first);
expect(source.getFeatureById('foo')).to.be(first);
source.removeFeature(first);
expect(source.getFeatureById('foo')).to.be(null);
var second = new ol.Feature();
second.setId('foo');
source.addFeature(second);
expect(source.getFeatureById('foo')).to.be(second);
});
it('returns correct feature after add/change', function() {
expect(source.getFeatureById('foo')).to.be(null);
var feature = new ol.Feature();
feature.setId('foo');
source.addFeature(feature);
expect(source.getFeatureById('foo')).to.be(feature);
feature.setId('bar');
expect(source.getFeatureById('foo')).to.be(null);
expect(source.getFeatureById('bar')).to.be(feature);
});
});
describe('the feature id index', function() {
var source;
beforeEach(function() {
source = new ol.source.Vector();
});
it('enforces a uniqueness constraint (on add)', function() {
var feature = new ol.Feature();
feature.setId('foo');
source.addFeature(feature);
var dupe = new ol.Feature();
dupe.setId('foo');
expect(function() {
source.addFeature(dupe);
}).to.throwException();
});
it('enforces a uniqueness constraint (on change)', function() {
var foo = new ol.Feature();
foo.setId('foo');
source.addFeature(foo);
var bar = new ol.Feature();
bar.setId('bar');
source.addFeature(bar);
expect(function() {
bar.setId('foo');
}).to.throwException();
});
});
}); });