From 93ba55d3575924eb94b89a1326666b071376a178 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 23 Apr 2013 18:31:20 +0200 Subject: [PATCH] Add ol.geom2.PointCollection --- src/ol/geom2/pointcollection.exports | 4 + src/ol/geom2/pointcollection.js | 142 ++++++++++ test/spec/ol/geom2/pointcollection.test.js | 293 +++++++++++++++++++++ 3 files changed, 439 insertions(+) create mode 100644 src/ol/geom2/pointcollection.exports create mode 100644 src/ol/geom2/pointcollection.js create mode 100644 test/spec/ol/geom2/pointcollection.test.js diff --git a/src/ol/geom2/pointcollection.exports b/src/ol/geom2/pointcollection.exports new file mode 100644 index 0000000000..7a7a485279 --- /dev/null +++ b/src/ol/geom2/pointcollection.exports @@ -0,0 +1,4 @@ +@exportSymbol ol.geom2.PointCollection +@exportSymbol ol.geom2.PointCollection.createEmpty +@exportSymbol ol.geom2.PointCollection.pack +@exportProperty ol.geom2.PointCollection.prototype.add diff --git a/src/ol/geom2/pointcollection.js b/src/ol/geom2/pointcollection.js new file mode 100644 index 0000000000..877071878d --- /dev/null +++ b/src/ol/geom2/pointcollection.js @@ -0,0 +1,142 @@ +goog.provide('ol.geom2.Point'); +goog.provide('ol.geom2.PointCollection'); + +goog.require('goog.asserts'); +goog.require('ol.Extent'); +goog.require('ol.geom2'); +goog.require('ol.structs.Buffer'); + + +/** + * @typedef {Array.} + */ +ol.geom2.Point; + + + +/** + * @constructor + * @param {ol.structs.Buffer} buf Buffer. + * @param {number=} opt_dim Dimension. + */ +ol.geom2.PointCollection = function(buf, opt_dim) { + + /** + * @type {ol.structs.Buffer} + */ + this.buf = buf; + + /** + * @type {number} + */ + this.dim = goog.isDef(opt_dim) ? opt_dim : 2; + +}; + + +/** + * @param {number} capacity Capacity. + * @param {number=} opt_dim Dimension. + * @return {ol.geom2.PointCollection} Point collection. + */ +ol.geom2.PointCollection.createEmpty = function(capacity, opt_dim) { + var dim = goog.isDef(opt_dim) ? opt_dim : 2; + var buf = new ol.structs.Buffer(new Array(capacity * dim), 0); + return new ol.geom2.PointCollection(buf, dim); +}; + + +/** + * @param {Array.} unpackedPoints Unpacked points. + * @param {number=} opt_capacity Capacity. + * @param {number=} opt_dim Dimension. + * @return {ol.geom2.PointCollection} Point collection. + */ +ol.geom2.PointCollection.pack = + function(unpackedPoints, opt_capacity, opt_dim) { + var n = unpackedPoints.length; + var dim = goog.isDef(opt_dim) ? opt_dim : + n > 0 ? unpackedPoints[0].length : 2; + var capacity = goog.isDef(opt_capacity) ? opt_capacity : n * dim; + goog.asserts.assert(capacity >= n * dim); + var arr = new Array(capacity); + ol.geom2.packPoints(arr, 0, unpackedPoints, dim); + var buf = new ol.structs.Buffer(arr, n * dim); + return new ol.geom2.PointCollection(buf, dim); +}; + + +/** + * @param {ol.geom2.Point} point Point. + * @return {number} Offset. + */ +ol.geom2.PointCollection.prototype.add = function(point) { + goog.asserts.assert(point.length == this.dim); + return this.buf.add(point); +}; + + +/** + * @param {number} offset Offset. + * @return {ol.geom2.Point} Point. + */ +ol.geom2.PointCollection.prototype.get = function(offset) { + var arr = this.buf.getArray(); + var dim = this.dim; + goog.asserts.assert(0 <= offset && offset + dim < arr.length); + goog.asserts.assert(offset % dim === 0); + return arr.slice(offset, offset + dim); +}; + + +/** + * @return {number} Count. + */ +ol.geom2.PointCollection.prototype.getCount = function() { + return this.buf.getCount() / this.dim; +}; + + +/** + * @return {ol.Extent} Extent. + */ +ol.geom2.PointCollection.prototype.getExtent = function() { + return ol.geom2.getExtent(this.buf, this.dim); +}; + + +/** + * @param {number} offset Offset. + */ +ol.geom2.PointCollection.prototype.remove = function(offset) { + this.buf.remove(this.dim, offset); +}; + + +/** + * @param {number} offset Offset. + * @param {ol.geom2.Point} point Point. + */ +ol.geom2.PointCollection.prototype.set = function(offset, point) { + this.buf.set(point, offset); +}; + + +/** + * @return {Array.} Points. + */ +ol.geom2.PointCollection.prototype.unpack = function() { + var dim = this.dim; + var n = this.getCount(); + var points = new Array(n); + var i = 0; + var bufArr = this.buf.getArray(); + this.buf.forEachRange(function(start, stop) { + var j; + for (j = start; j < stop; j += dim) { + points[i++] = bufArr.slice(j, j + dim); + } + }); + goog.asserts.assert(i == n); + return points; +}; diff --git a/test/spec/ol/geom2/pointcollection.test.js b/test/spec/ol/geom2/pointcollection.test.js new file mode 100644 index 0000000000..52d7d33bd5 --- /dev/null +++ b/test/spec/ol/geom2/pointcollection.test.js @@ -0,0 +1,293 @@ +goog.provide('ol.test.geom2.PointCollection'); + + +describe('ol.geom2.PointCollection', function() { + + describe('createEmpty', function() { + + it('creates an empty instance with the specified capacity', function() { + var pc = ol.geom2.PointCollection.createEmpty(16); + expect(pc.getCount()).to.be(0); + expect(pc.buf.getArray()).to.have.length(32); + }); + + it('can create empty collections for higher dimensions', function() { + var pc = ol.geom2.PointCollection.createEmpty(16, 3); + expect(pc.getCount()).to.be(0); + expect(pc.buf.getArray()).to.have.length(48); + }); + + }); + + describe('pack', function() { + + it('packs an empty array', function() { + var pc = ol.geom2.PointCollection.pack([]); + expect(pc.buf.getArray()).to.be.empty(); + expect(pc.dim).to.be(2); + }); + + it('packs an empty array with a capacity', function() { + var pc = ol.geom2.PointCollection.pack([], 4); + expect(pc.buf.getArray()).to.eql([NaN, NaN, NaN, NaN]); + expect(pc.dim).to.be(2); + }); + + it('packs an empty array with a capacity and a dimension', function() { + var pc = ol.geom2.PointCollection.pack([], 8, 2); + expect(pc.buf.getArray()).to.eql( + [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN]); + expect(pc.dim).to.be(2); + }); + + it('packs a single point', function() { + var pc = ol.geom2.PointCollection.pack([[0, 1]]); + expect(pc.buf.getArray()).to.eql([0, 1]); + expect(pc.dim).to.be(2); + }); + + it('can pack multiple points', function() { + var pc = ol.geom2.PointCollection.pack([[0, 1], [2, 3], [4, 5]]); + expect(pc.buf.getArray()).to.eql([0, 1, 2, 3, 4, 5]); + expect(pc.dim).to.be(2); + }); + + it('can pack multiple points with a capacity', function() { + var pc = ol.geom2.PointCollection.pack([[0, 1], [2, 3], [4, 5]], 8); + expect(pc.buf.getArray()).to.eql([0, 1, 2, 3, 4, 5, NaN, NaN]); + expect(pc.dim).to.be(2); + }); + + it('can pack a single 3-dimensional point', function() { + var pc = ol.geom2.PointCollection.pack([[0, 1, 2]]); + expect(pc.buf.getArray()).to.eql([0, 1, 2]); + expect(pc.dim).to.be(3); + }); + + it('can pack a multiple 3-dimensional points', function() { + var pc = ol.geom2.PointCollection.pack([[0, 1, 2], [4, 5, 6]]); + expect(pc.buf.getArray()).to.eql([0, 1, 2, 4, 5, 6]); + expect(pc.dim).to.be(3); + }); + + it('raises an error when not all points have the same dimension', + function() { + expect(function() { + var pc = ol.geom2.PointCollection.pack([[0, 1], [2]]); + pc = pc; // suppress gjslint warning about unused variable + }).to.throwException(); + }); + + it('raises an error when the capacity is too small', function() { + expect(function() { + var pc = ol.geom2.PointCollection.pack([[0, 1], [2, 3], [4, 5]], 2); + pc = pc; // suppress gjslint warning about unused variable + }).to.throwException(); + }); + + }); + + describe('with an empty buffer, with capacity for two points', function() { + + var pc; + beforeEach(function() { + var buf = new ol.structs.Buffer(new Array(4), 0); + pc = new ol.geom2.PointCollection(buf); + }); + + describe('add', function() { + + it('can add a first point', function() { + expect(pc.add([0, 1])).to.be(0); + expect(pc.buf.getArray()).to.eql([0, 1, NaN, NaN]); + }); + + it('can add a second point', function() { + expect(pc.add([0, 1])).to.be(0); + expect(pc.buf.getArray()).to.eql([0, 1, NaN, NaN]); + expect(pc.add([2, 3])).to.be(2); + expect(pc.buf.getArray()).to.eql([0, 1, 2, 3]); + }); + + it('raises an error when the third point is added', function() { + expect(pc.add([0, 1])).to.be(0); + expect(pc.buf.getArray()).to.eql([0, 1, NaN, NaN]); + expect(pc.add([2, 3])).to.be(2); + expect(pc.buf.getArray()).to.eql([0, 1, 2, 3]); + expect(function() { + pc.add([4, 5]); + }).to.throwException(); + }); + + it('raises an error if a point of the wrong dimension is added', + function() { + expect(function() { + pc.add([0, 1, 2]); + }).to.throwException(); + }); + + }); + + describe('getCount', function() { + + it('returns 0', function() { + expect(pc.getCount()).to.be(0); + }); + + }); + + describe('getExtent', function() { + + it('returns an empty extent', function() { + expect(ol.extent.isEmpty(pc.getExtent())).to.be(true); + }); + + }); + + describe('unpack', function() { + + it('returns an empty array', function() { + expect(pc.unpack()).to.be.empty(); + }); + + }); + + }); + + describe('with a partially populated instance', function() { + + var dirtySet, pc; + beforeEach(function() { + dirtySet = new ol.structs.IntegerSet(); + pc = ol.geom2.PointCollection.pack([[0, 1], [2, 3]], 8); + pc.buf.addDirtySet(dirtySet); + }); + + describe('add', function() { + + it('can add more points', function() { + expect(pc.add([4, 5])).to.be(4); + expect(pc.buf.getArray()).to.eql([0, 1, 2, 3, 4, 5, NaN, NaN]); + expect(pc.add([6, 7])).to.be(6); + expect(pc.buf.getArray()).to.eql([0, 1, 2, 3, 4, 5, 6, 7]); + }); + + }); + + describe('get', function() { + + it('returns the expected value for the first point', function() { + expect(pc.get(0)).to.eql([0, 1]); + }); + + it('returns the expected value for the second point', function() { + expect(pc.get(2)).to.eql([2, 3]); + }); + + }); + + describe('getCount', function() { + + it('returns the expected value', function() { + expect(pc.getCount()).to.be(2); + }); + + }); + + describe('getExtent', function() { + + it('returns the expected value', function() { + var extent = pc.getExtent(); + expect(extent).to.eql([0, 2, 1, 3]); + }); + + }); + + describe('remove', function() { + + it('can remove the first point', function() { + pc.remove(0); + expect(pc.buf.getArray()).to.eql([NaN, NaN, 2, 3, NaN, NaN, NaN, NaN]); + }); + + it('can remove the second point', function() { + pc.remove(2); + expect(pc.buf.getArray()).to.eql([0, 1, NaN, NaN, NaN, NaN, NaN, NaN]); + }); + + }); + + describe('set', function() { + + it('marks the updated elements as dirty', function() { + pc.set(2, [4, 5]); + expect(pc.buf.getArray()).to.eql([0, 1, 4, 5, NaN, NaN, NaN, NaN]); + expect(dirtySet.getArray()).to.eql([2, 4]); + }); + + }); + + describe('unpack', function() { + + it('returns the expect value', function() { + expect(pc.unpack()).to.eql([[0, 1], [2, 3]]); + }); + + }); + + describe('after removing the first point', function() { + + beforeEach(function() { + pc.remove(0); + }); + + describe('getCount', function() { + + it('returns the expected value', function() { + expect(pc.getCount()).to.be(1); + }); + + }); + + describe('unpack', function() { + + it('returns the expected value', function() { + expect(pc.unpack()).to.eql([[2, 3]]); + }); + + }); + + }); + + }); + + describe('usage example', function() { + + it('works as expected', function() { + var pc = ol.geom2.PointCollection.pack([[0, 1], [2, 3], [4, 5]], 8); + var dirtySet = new ol.structs.IntegerSet(); + pc.buf.addDirtySet(dirtySet); + expect(pc.buf.getArray()).to.eql([0, 1, 2, 3, 4, 5, NaN, NaN]); + expect(pc.unpack()).to.have.length(3); + expect(pc.getCount()).to.be(3); + expect(pc.get(2)).to.eql([2, 3]); + pc.remove(2); + expect(pc.buf.getArray()).to.eql([0, 1, NaN, NaN, 4, 5, NaN, NaN]); + expect(pc.unpack()).to.have.length(2); + expect(pc.getCount()).to.be(2); + expect(pc.add([6, 7])).to.be(2); + expect(pc.buf.getArray()).to.eql([0, 1, 6, 7, 4, 5, NaN, NaN]); + expect(pc.unpack()).to.have.length(3); + expect(pc.getCount()).to.be(3); + expect(dirtySet.getArray()).to.eql([2, 4]); + }); + + }); + +}); + + +goog.require('ol.geom2.PointCollection'); +goog.require('ol.extent'); +goog.require('ol.structs.Buffer'); +goog.require('ol.structs.IntegerSet');