Vector Source / index all features by uid

Previously features were indexed by uid only when they
did not have a defined id.
A new method was added: `getFeatureByUid`. This is not part of
the public API.
This will facilitate the lookup of features for hit detection.
This commit is contained in:
Olivier Guyot
2019-06-01 16:22:12 +02:00
parent 6b60b25e8b
commit 034e0be76f
2 changed files with 79 additions and 20 deletions

View File

@@ -241,11 +241,11 @@ class VectorSource extends Source {
this.idIndex_ = {};
/**
* A lookup of features without id (keyed by getUid(feature)).
* A lookup of features by uid (using getUid(feature)).
* @private
* @type {!Object<string, import("../Feature.js").default<Geometry>>}
*/
this.undefIdIndex_ = {};
this.uidIndex_ = {};
/**
* @private
@@ -359,10 +359,11 @@ class VectorSource extends Source {
} else {
valid = false;
}
} else {
assert(!(featureKey in this.undefIdIndex_),
}
if (valid) {
assert(!(featureKey in this.uidIndex_),
30); // The passed `feature` was already added to the source
this.undefIdIndex_[featureKey] = feature;
this.uidIndex_[featureKey] = feature;
}
return valid;
}
@@ -489,7 +490,7 @@ class VectorSource extends Source {
if (!this.featuresCollection_) {
this.featureChangeKeys_ = {};
this.idIndex_ = {};
this.undefIdIndex_ = {};
this.uidIndex_ = {};
}
} else {
if (this.featuresRtree_) {
@@ -770,6 +771,18 @@ class VectorSource extends Source {
}
/**
* Get a feature by its internal unique identifier (using `getUid`).
*
* @param {string} uid Feature identifier.
* @return {import("../Feature.js").default<Geometry>} The feature (or `null` if not found).
*/
getFeatureByUid(uid) {
const feature = this.uidIndex_[uid];
return feature !== undefined ? feature : null;
}
/**
* Get the format associated with this source.
*
@@ -831,20 +844,13 @@ class VectorSource extends Source {
const id = feature.getId();
if (id !== undefined) {
const sid = id.toString();
if (featureKey in this.undefIdIndex_) {
delete this.undefIdIndex_[featureKey];
if (this.idIndex_[sid] !== feature) {
this.removeFromIdIndex_(feature);
this.idIndex_[sid] = feature;
} else {
if (this.idIndex_[sid] !== feature) {
this.removeFromIdIndex_(feature);
this.idIndex_[sid] = feature;
}
}
} else {
if (!(featureKey in this.undefIdIndex_)) {
this.removeFromIdIndex_(feature);
this.undefIdIndex_[featureKey] = feature;
}
this.removeFromIdIndex_(feature);
this.uidIndex_[featureKey] = feature;
}
this.changed();
this.dispatchEvent(new VectorSourceEvent(
@@ -862,7 +868,7 @@ class VectorSource extends Source {
if (id !== undefined) {
return id in this.idIndex_;
} else {
return getUid(feature) in this.undefIdIndex_;
return getUid(feature) in this.uidIndex_;
}
}
@@ -964,9 +970,8 @@ class VectorSource extends Source {
const id = feature.getId();
if (id !== undefined) {
delete this.idIndex_[id.toString()];
} else {
delete this.undefIdIndex_[featureKey];
}
delete this.uidIndex_[featureKey];
this.dispatchEvent(new VectorSourceEvent(
VectorEventType.REMOVEFEATURE, feature));
}

View File

@@ -10,6 +10,7 @@ import {bbox as bboxStrategy} from '../../../../src/ol/loadingstrategy.js';
import {get as getProjection, transformExtent, fromLonLat} from '../../../../src/ol/proj.js';
import VectorSource from '../../../../src/ol/source/Vector.js';
import GeoJSON from '../../../../src/ol/format/GeoJSON.js';
import {getUid} from '../../../../src/ol/util.js';
describe('ol.source.Vector', function() {
@@ -519,6 +520,59 @@ describe('ol.source.Vector', function() {
});
describe('#getFeatureByUid()', function() {
let source;
beforeEach(function() {
source = new VectorSource();
});
it('returns a feature with an id', function() {
const feature = new Feature();
feature.setId('abcd');
source.addFeature(feature);
expect(source.getFeatureByUid(getUid(feature))).to.be(feature);
});
it('returns a feature without id', function() {
const feature = new Feature();
source.addFeature(feature);
expect(source.getFeatureByUid(getUid(feature))).to.be(feature);
});
it('returns null when no feature is found', function() {
const feature = new Feature();
feature.setId('abcd');
source.addFeature(feature);
const wrongId = 'abcd';
expect(source.getFeatureByUid(wrongId)).to.be(null);
});
it('returns null after removing feature', function() {
const feature = new Feature();
feature.setId('abcd');
source.addFeature(feature);
const uid = getUid(feature);
expect(source.getFeatureByUid(uid)).to.be(feature);
source.removeFeature(feature);
expect(source.getFeatureByUid(uid)).to.be(null);
});
it('returns null after clear', function() {
const feature = new Feature();
feature.setId('abcd');
source.addFeature(feature);
const uid = getUid(feature);
expect(source.getFeatureByUid(uid)).to.be(feature);
source.clear();
expect(source.getFeatureByUid(uid)).to.be(null);
});
it('returns null when no features are present', function() {
expect(source.getFeatureByUid('abcd')).to.be(null);
});
});
describe('#loadFeatures', function() {
describe('with the "bbox" strategy', function() {