From 1690cb9cae243a5c093a95843a8cded0f4c4d8e4 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 1 May 2013 16:17:18 +0200 Subject: [PATCH] Add ol.geom2.LineStringCollection --- src/ol/geom2/linestringcollection.exports | 2 + src/ol/geom2/linestringcollection.js | 183 +++++++++++++ test/spec/ol/geom2/linecollection.test.js | 303 ++++++++++++++++++++++ 3 files changed, 488 insertions(+) create mode 100644 src/ol/geom2/linestringcollection.exports create mode 100644 src/ol/geom2/linestringcollection.js create mode 100644 test/spec/ol/geom2/linecollection.test.js diff --git a/src/ol/geom2/linestringcollection.exports b/src/ol/geom2/linestringcollection.exports new file mode 100644 index 0000000000..6f7b393e71 --- /dev/null +++ b/src/ol/geom2/linestringcollection.exports @@ -0,0 +1,2 @@ +@exportSymbol ol.geom2.LineStringCollection +@exportSymbol ol.geom2.LineStringCollection.pack diff --git a/src/ol/geom2/linestringcollection.js b/src/ol/geom2/linestringcollection.js new file mode 100644 index 0000000000..9981ec7100 --- /dev/null +++ b/src/ol/geom2/linestringcollection.js @@ -0,0 +1,183 @@ +goog.provide('ol.geom2.LineString'); +goog.provide('ol.geom2.LineStringCollection'); + +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('ol.geom2'); +goog.require('ol.structs.Buffer'); + + +/** + * @typedef {Array.>} + */ +ol.geom2.LineString; + + + +/** + * @constructor + * @param {ol.structs.Buffer} buf Buffer. + * @param {Object.>=} opt_ranges Ranges. + * @param {number=} opt_dim Dimension. + */ +ol.geom2.LineStringCollection = function(buf, opt_ranges, opt_dim) { + + /** + * @type {ol.structs.Buffer} + */ + this.buf = buf; + + /** + * @type {Object.>} + */ + this.ranges = goog.isDef(opt_ranges) ? opt_ranges : {}; + + /** + * @type {number} + */ + this.dim = goog.isDef(opt_dim) ? opt_dim : 2; + +}; + + +/** + * @param {number} capacity Capacity. + * @param {number=} opt_dim Dimension. + * @return {ol.geom2.LineStringCollection} Line string collection. + */ +ol.geom2.LineStringCollection.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.LineStringCollection(buf, undefined, dim); +}; + + +/** + * @param {Array.} unpackedLineStrings Unpacked line + * strings. + * @param {number=} opt_capacity Capacity. + * @param {number=} opt_dim Dimension. + * @return {ol.geom2.LineStringCollection} Line string collection. + */ +ol.geom2.LineStringCollection.pack = + function(unpackedLineStrings, opt_capacity, opt_dim) { + var i; + var n = unpackedLineStrings.length; + var dim = goog.isDef(opt_dim) ? opt_dim : + n > 0 ? unpackedLineStrings[0][0].length : 2; + var capacity; + if (goog.isDef(opt_capacity)) { + capacity = opt_capacity; + } else { + capacity = 0; + for (i = 0; i < n; ++i) { + capacity += unpackedLineStrings[i].length; + } + } + capacity *= dim; + var arr = new Array(capacity); + /** @type {Object.>} */ + var ranges = {}; + var offset = 0; + var start; + for (i = 0; i < n; ++i) { + goog.asserts.assert(unpackedLineStrings[i].length > 1); + start = offset; + offset = ol.geom2.packPoints(arr, offset, unpackedLineStrings[i], dim); + ranges[start] = [start, offset]; + } + goog.asserts.assert(offset <= capacity); + var buf = new ol.structs.Buffer(arr, offset); + return new ol.geom2.LineStringCollection(buf, ranges, dim); +}; + + +/** + * @param {ol.geom2.LineString} lineString Line string. + * @return {number} Offset. + */ +ol.geom2.LineStringCollection.prototype.add = function(lineString) { + var n = lineString.length * this.dim; + var offset = this.buf.allocate(n); + goog.asserts.assert(offset != -1); + this.ranges[offset] = [offset, offset + n]; + ol.geom2.packPoints(this.buf.getArray(), offset, lineString, this.dim); + return offset; +}; + + +/** + * @param {number} offset Offset. + * @return {ol.geom2.LineString} Line string. + */ +ol.geom2.LineStringCollection.prototype.get = function(offset) { + goog.asserts.assert(offset in this.ranges); + var range = this.ranges[offset]; + return ol.geom2.unpackPoints( + this.buf.getArray(), range[0], range[1], this.dim); +}; + + +/** + * @return {number} Count. + */ +ol.geom2.LineStringCollection.prototype.getCount = function() { + return goog.object.getCount(this.ranges); +}; + + +/** + * @return {ol.Extent} Extent. + */ +ol.geom2.LineStringCollection.prototype.getExtent = function() { + return ol.geom2.getExtent(this.buf, this.dim); +}; + + +/** + * @param {number} offset Offset. + */ +ol.geom2.LineStringCollection.prototype.remove = function(offset) { + goog.asserts.assert(offset in this.ranges); + var range = this.ranges[offset]; + this.buf.remove(range[1] - range[0], range[0]); + delete this.ranges[offset]; +}; + + +/** + * @param {number} offset Offset. + * @param {ol.geom2.LineString} lineString Line string. + * @return {number} Offset. + */ +ol.geom2.LineStringCollection.prototype.set = function(offset, lineString) { + var dim = this.dim; + goog.asserts.assert(offset in this.ranges); + var range = this.ranges[offset]; + if (lineString.length * dim == range[1] - range[0]) { + ol.geom2.packPoints(this.buf.getArray(), range[0], lineString, dim); + this.buf.markDirty(range[1] - range[0], range[0]); + return offset; + } else { + this.remove(offset); + return this.add(lineString); + } +}; + + +/** + * @return {Array.} Line strings. + */ +ol.geom2.LineStringCollection.prototype.unpack = function() { + var dim = this.dim; + var n = this.getCount(); + var lineStrings = new Array(n); + var i = 0; + var offset, range; + for (offset in this.ranges) { + range = this.ranges[Number(offset)]; + lineStrings[i++] = ol.geom2.unpackPoints( + this.buf.getArray(), range[0], range[1], dim); + } + return lineStrings; +}; diff --git a/test/spec/ol/geom2/linecollection.test.js b/test/spec/ol/geom2/linecollection.test.js new file mode 100644 index 0000000000..a90b51a110 --- /dev/null +++ b/test/spec/ol/geom2/linecollection.test.js @@ -0,0 +1,303 @@ +goog.provide('ol.test.geom2.LineStringCollection'); + + +describe('ol.geom2.LineStringCollection', function() { + + describe('createEmpty', function() { + + it('creates an empty instance with the specified capacity', function() { + var lsc = ol.geom2.LineStringCollection.createEmpty(16); + expect(lsc.getCount()).to.be(0); + expect(lsc.buf.getArray()).to.have.length(32); + }); + + it('can create empty collections for higher dimensions', function() { + var lsc = ol.geom2.LineStringCollection.createEmpty(16, 3); + expect(lsc.getCount()).to.be(0); + expect(lsc.buf.getArray()).to.have.length(48); + }); + + }); + + describe('pack', function() { + + it('packs an empty array', function() { + var lsc = ol.geom2.LineStringCollection.pack([]); + expect(lsc.buf.getArray()).to.be.empty(); + expect(lsc.ranges).to.be.empty(); + expect(lsc.dim).to.be(2); + }); + + it('packs an empty array with a capacity', function() { + var lsc = ol.geom2.LineStringCollection.pack([], 4); + expect(lsc.buf.getArray()).to.eql( + [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN]); + expect(lsc.ranges).to.be.empty(); + expect(lsc.dim).to.be(2); + }); + + it('packs an array of line strings', function() { + var lsc = ol.geom2.LineStringCollection.pack( + [[[0, 1], [2, 3], [4, 5]], [[6, 7], [8, 9]]]); + expect(lsc.buf.getArray()).to.eql([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + expect(lsc.getCount()).to.be(2); + expect(lsc.ranges[0]).to.eql([0, 6]); + expect(lsc.ranges[6]).to.eql([6, 10]); + expect(lsc.dim).to.be(2); + }); + + it('packs an array of line strings with a different dimension', function() { + var lsc = ol.geom2.LineStringCollection.pack( + [[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]]]); + expect(lsc.buf.getArray()).to.eql([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + expect(lsc.getCount()).to.be(2); + expect(lsc.ranges[0]).to.eql([0, 6]); + expect(lsc.ranges[6]).to.eql([6, 12]); + expect(lsc.dim).to.be(3); + }); + + it('packs an array of line strings with extra capacity', function() { + var lsc = ol.geom2.LineStringCollection.pack( + [[[0, 1], [2, 3], [4, 5]], [[6, 7], [8, 9]]], 16); + expect(lsc.buf.getArray().slice(0, 10)).to.eql( + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + expect(lsc.buf.getArray()).to.have.length(32); + expect(lsc.getCount()).to.be(2); + expect(lsc.ranges[0]).to.eql([0, 6]); + expect(lsc.ranges[6]).to.eql([6, 10]); + expect(lsc.dim).to.be(2); + }); + + it('throws an error when dimensions are inconsistent', function() { + expect(function() { + var lsc = ol.geom2.LineStringCollection.pack([[0, 1], [2, 3, 4]]); + lsc = lsc; // suppress gjslint warning about unused variable + }).to.throwException(); + }); + + it('throws an error when a line string is too short', function() { + expect(function() { + var lsc = ol.geom2.LineStringCollection.pack([[0, 1]]); + lsc = lsc; // suppress gjslint warning about unused variable + }).to.throwException(); + }); + + it('throws an error when the capacity is too small', function() { + expect(function() { + var lsc = ol.geom2.LineStringCollection.pack( + [[[0, 1], [2, 3], [4, 5]], [[6, 7], [8, 9]]], 4); + lsc = lsc; // suppress gjslint warning about unused variable + }).to.throwException(); + }); + + }); + + describe('with an empty instance with spare capacity', function() { + + var lsc; + beforeEach(function() { + var buf = new ol.structs.Buffer(new Array(8), 0); + lsc = new ol.geom2.LineStringCollection(buf); + }); + + describe('add', function() { + + it('adds a line string', function() { + var offset = lsc.add([[0, 1], [2, 3]]); + expect(offset).to.be(0); + expect(lsc.getCount()).to.be(1); + expect(lsc.ranges[0]).to.eql([0, 4]); + expect(lsc.dim).to.be(2); + }); + + }); + + describe('getCount', function() { + + it('returns zero', function() { + expect(lsc.getCount()).to.be(0); + }); + + }); + + describe('getExtent', function() { + + it('returns an empty extent', function() { + expect(ol.extent.isEmpty(lsc.getExtent())).to.be(true); + }); + + }); + + describe('remove', function() { + + it('throws an exception', function() { + expect(function() { + lsc.remove(0); + }).to.throwException(); + }); + + }); + + }); + + describe('with an initial line string', function() { + + var lsc, offset; + beforeEach(function() { + var buf = new ol.structs.Buffer(new Array(8), 0); + lsc = new ol.geom2.LineStringCollection(buf); + offset = lsc.add([[0, 1], [2, 3]]); + }); + + describe('add', function() { + + it('can add a second line string', function() { + var offset2 = lsc.add([[4, 5], [6, 7]]); + expect(offset2).to.be(4); + expect(lsc.getCount()).to.be(2); + expect(lsc.ranges[0]).to.eql([0, 4]); + expect(lsc.ranges[4]).to.eql([4, 8]); + expect(lsc.dim).to.be(2); + }); + + }); + + describe('get', function() { + + it('returns the expected line string', function() { + expect(lsc.get(0)).to.eql([[0, 1], [2, 3]]); + }); + + }); + + describe('getCount', function() { + + it('returns the expected value', function() { + expect(lsc.getCount()).to.be(1); + }); + + }); + + describe('getExtent', function() { + + it('returns the expected extent', function() { + expect(lsc.getExtent()).to.eql([0, 2, 1, 3]); + }); + + }); + + describe('remove', function() { + + it('removes the line string', function() { + lsc.remove(0); + expect(lsc.getCount()).to.be(0); + }); + + }); + + describe('set', function() { + + it('can update the line string in place', function() { + expect(lsc.set(0, [[4, 5], [6, 7]])).to.be(0); + expect(lsc.buf.getArray()).to.eql([4, 5, 6, 7, NaN, NaN, NaN, NaN]); + }); + + it('can replace the line string with a shorter one', function() { + expect(lsc.set(0, [[4, 5]])).to.be(0); + expect(lsc.buf.getArray()).to.eql([4, 5, NaN, NaN, NaN, NaN, NaN, NaN]); + }); + + it('can replace the line string with a longer one', function() { + expect(lsc.set(0, [[4, 5], [6, 7], [8, 9], [10, 11]])).to.be(0); + expect(lsc.buf.getArray()).to.eql([4, 5, 6, 7, 8, 9, 10, 11]); + }); + + }); + + describe('unpack', function() { + + it('returns the expected value', function() { + expect(lsc.unpack()).to.eql([[[0, 1], [2, 3]]]); + }); + + }); + + }); + + describe('with multiple initial line strings', function() { + + var lsc; + beforeEach(function() { + lsc = ol.geom2.LineStringCollection.pack( + [[[0, 1], [2, 3]], [[4, 5], [6, 7], [8, 9]]], 16); + }); + + describe('get', function() { + + it('returns the expected values', function() { + expect(lsc.get(0)).to.eql([[0, 1], [2, 3]]); + expect(lsc.get(4)).to.eql([[4, 5], [6, 7], [8, 9]]); + }); + + }); + + describe('getCount', function() { + + it('returns the expected value', function() { + expect(lsc.getCount()).to.be(2); + }); + + }); + + describe('getExtent', function() { + + it('returns the expected value', function() { + expect(lsc.getExtent()).to.eql([0, 8, 1, 9]); + }); + + }); + + describe('remove', function() { + + it('can remove the first line string', function() { + lsc.remove(0); + expect(lsc.getCount()).to.be(1); + expect(lsc.get(4)).to.eql([[4, 5], [6, 7], [8, 9]]); + }); + + it('can remove the second line string', function() { + lsc.remove(4); + expect(lsc.getCount()).to.be(1); + expect(lsc.get(0)).to.eql([[0, 1], [2, 3]]); + }); + + }); + + describe('usage examples', function() { + + it('allows the first line string to be replaced', function() { + lsc.remove(0); + expect(lsc.getCount()).to.be(1); + expect(lsc.add([[10, 11], [12, 13]])).to.be(0); + expect(lsc.getCount()).to.be(2); + expect(lsc.get(0)).to.eql([[10, 11], [12, 13]]); + }); + + it('will allocate at the end of the array', function() { + lsc.remove(0); + expect(lsc.getCount()).to.be(1); + expect(lsc.add([[10, 11], [12, 13], [14, 15]])).to.be(10); + expect(lsc.getCount()).to.be(2); + expect(lsc.get(10)).to.eql([[10, 11], [12, 13], [14, 15]]); + }); + + }); + + }); + +}); + + +goog.require('ol.geom2.LineStringCollection'); +goog.require('ol.extent'); +goog.require('ol.structs.Buffer');