From db408424876a1e4b052770e587bf8162fe9e78ab Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 6 Nov 2013 22:18:22 +0100 Subject: [PATCH] Add ol.source.Vector --- src/objectliterals.jsdoc | 10 ++ src/ol/source/vectorsource.js | 102 ++++++++++++++++ test/spec/ol/source/vectorsource.test.js | 141 +++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 src/ol/source/vectorsource.js create mode 100644 test/spec/ol/source/vectorsource.test.js diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 87a96162e5..d43e07eb6a 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -531,6 +531,16 @@ * @todo stability experimental */ +/** + * @typedef {Object} ol.source.VectorOptions + * @property {Array.|undefined} attributions Attributions. + * @property {ol.Extent|undefined} extent Extent. + * @property {Array.|undefined} features Features. + * @property {string|undefined} logo Logo. + * @property {ol.proj.ProjectionLike} projection Projection. + * @property {ol.source.State|undefined} state State. + */ + /** * @typedef {Object} ol.source.WMTSOptions * @property {Array.|undefined} attributions Attributions. diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js new file mode 100644 index 0000000000..4f9162d508 --- /dev/null +++ b/src/ol/source/vectorsource.js @@ -0,0 +1,102 @@ +// FIXME put features in an ol.Collection +// FIXME make change-detection more refined (notably, geometry hint) +// FIXME keep R-Tree up-to-date, probably needs a new R-Tree implementation + +goog.provide('ol.source.Vector'); + +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('ol.source.Source'); +goog.require('ol.structs.RTree'); + + + +/** + * @constructor + * @extends {ol.source.Source} + * @param {ol.source.VectorOptions=} opt_options Options. + */ +ol.source.Vector = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + state: options.state + }); + + /** + * @private + * @type {ol.structs.RTree} + */ + this.rTree_ = new ol.structs.RTree(); + + /** + * @private + * @type {Object.} + */ + this.featureChangeKeys_ = {}; + + if (goog.isDef(options.features)) { + var features = options.features; + var i, ii; + for (i = 0, ii = features.length; i < ii; ++i) { + this.addFeature(features[i]); + } + } + +}; +goog.inherits(ol.source.Vector, ol.source.Source); + + +/** + * @param {ol.Feature} feature Feature. + */ +ol.source.Vector.prototype.addFeature = function(feature) { + var featureKey = goog.getUid(feature) + ''; + goog.asserts.assert(!(featureKey in this.featureChangeKeys_)); + this.featureChangeKeys_[featureKey] = goog.events.listen(feature, + goog.events.EventType.CHANGE, this.handleFeatureChange_, false, this); + var extent = feature.getGeometry().getExtent(); + this.rTree_.insert(extent, feature); + this.dispatchChangeEvent(); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @return {Array.} Features. + */ +ol.source.Vector.prototype.getFeatures = function(extent) { + return this.rTree_.search(extent); +}; + + +/** + * @param {goog.events.Event} event Event. + * @private + */ +ol.source.Vector.prototype.handleFeatureChange_ = function(event) { + //var feature = /** @type {ol.Feature} */ (event.target); + // FIXME keep R-Tree up to date + this.dispatchChangeEvent(); +}; + + +/** + * @param {ol.Feature} feature Feature. + */ +ol.source.Vector.prototype.removeFeature = function(feature) { + var extent = feature.getGeometry().getExtent(); + this.rTree_.remove(extent, feature); + var featureKey = goog.getUid(feature) + ''; + goog.asserts.assert(featureKey in this.featureChangeKeys_); + goog.events.unlistenByKey(this.featureChangeKeys_[featureKey]); + delete this.featureChangeKeys_[featureKey]; + this.dispatchChangeEvent(); +}; diff --git a/test/spec/ol/source/vectorsource.test.js b/test/spec/ol/source/vectorsource.test.js new file mode 100644 index 0000000000..3c164fb381 --- /dev/null +++ b/test/spec/ol/source/vectorsource.test.js @@ -0,0 +1,141 @@ +goog.provide('ol.test.source.Vector'); + + +describe('ol.source.Vector', function() { + + var pointFeature; + var infiniteExtent; + beforeEach(function() { + pointFeature = new ol.Feature(new ol.geom.Point([0, 0])); + infiniteExtent = [-Infinity, -Infinity, Infinity, Infinity]; + }); + + describe('when empty', function() { + + var vectorSource; + beforeEach(function() { + vectorSource = new ol.source.Vector(); + }); + + describe('#getFeatures', function() { + + it('returns an empty array', function() { + var features = vectorSource.getFeatures(infiniteExtent); + expect(features).to.be.an(Array); + expect(features).to.be.empty(); + }); + + }); + + describe('#addFeature', function() { + + it('can add a single point feature', function() { + vectorSource.addFeature(pointFeature); + var features = vectorSource.getFeatures(infiniteExtent); + expect(features).to.be.an(Array); + expect(features).to.have.length(1); + expect(features[0]).to.be(pointFeature); + }); + + it('fires a change event', function() { + var listener = sinon.spy(); + goog.events.listen(vectorSource, 'change', listener); + vectorSource.addFeature(pointFeature); + expect(listener).to.be.called(); + }); + + }); + + }); + + describe('when populated with 10 random points', function() { + + var features; + var vectorSource; + beforeEach(function() { + features = []; + var i; + for (i = 0; i < 10; ++i) { + features[i] = + new ol.Feature(new ol.geom.Point([Math.random(), Math.random()])); + } + vectorSource = new ol.source.Vector({ + features: features + }); + }); + + describe('#getFeatures', function() { + + it('returns the expected number of features', function() { + expect(vectorSource.getFeatures(infiniteExtent)).have.length(10); + }); + + }); + + describe('#removeFeature', function() { + + it('works as expected', function() { + var i; + for (i = features.length - 1; i >= 0; --i) { + vectorSource.removeFeature(features[i]); + expect(vectorSource.getFeatures(infiniteExtent)).have.length(i); + } + }); + + it('fires a change event', function() { + var listener = sinon.spy(); + goog.events.listen(vectorSource, 'change', listener); + vectorSource.removeFeature(features[0]); + expect(listener).to.be.called(); + }); + + }); + + describe('modifying a feature\'s geometry', function() { + + it('fires a change event', function() { + var listener = sinon.spy(); + goog.events.listen(vectorSource, 'change', listener); + features[0].getGeometry().setCoordinate([100, 100]); + expect(listener).to.be.called(); + }); + + if (false) { + it('keeps the R-Tree index up to date', function() { + expect(vectorSource.getFeatures([0, 0, 1, 1])).to.have.length(10); + features[0].getGeometry().setCoordinate([100, 100]); + expect(vectorSource.getFeatures([0, 0, 1, 1])).to.have.length(9); + features[0].getGeometry().setCoordinate([0.5, 0.5]); + expect(vectorSource.getFeatures([0, 0, 1, 1])).to.have.length(10); + }); + } + + }); + + describe('setting a features geometry', function() { + + it('fires a change event', function() { + var listener = sinon.spy(); + goog.events.listen(vectorSource, 'change', listener); + features[0].setGeometry(new ol.geom.Point([100, 100])); + expect(listener).to.be.called(); + }); + + if (false) { + it('keeps the R-Tree index up to date', function() { + expect(vectorSource.getFeatures([0, 0, 1, 1])).to.have.length(10); + features[0].setGeometry(new ol.geom.Point([100, 100])); + expect(vectorSource.getFeatures([0, 0, 1, 1])).to.have.length(9); + }); + } + }); + + }); + +}); + + +goog.require('goog.events'); +goog.require('ol.Feature'); +goog.require('ol.geom.Point'); +goog.require('ol.source.Vector');