diff --git a/examples/ten-thousand-points.html b/examples/ten-thousand-points.html new file mode 100644 index 0000000000..b7953bcb89 --- /dev/null +++ b/examples/ten-thousand-points.html @@ -0,0 +1,55 @@ + + + + + + + + + + Ten thousand points example + + + + + +
+ +
+
+
+
+
+ +
+ +
+

Ten thousand points example

+

Example of a map with ten thousand points.

+
+

See the ten-thousand-points.js source to see how this is done.

+
+
points, vector, openstreetmap
+
+ +
+ +
+ + + + + + diff --git a/examples/ten-thousand-points.js b/examples/ten-thousand-points.js new file mode 100644 index 0000000000..87552e26e0 --- /dev/null +++ b/examples/ten-thousand-points.js @@ -0,0 +1,68 @@ +goog.require('ol.Map'); +goog.require('ol.RendererHint'); +goog.require('ol.View2D'); +goog.require('ol.control.MousePosition'); +goog.require('ol.control.defaults'); +goog.require('ol.geom2.LineStringCollection'); +goog.require('ol.geom2.PointCollection'); +goog.require('ol.layer.TileLayer'); +goog.require('ol.layer.VectorLayer2'); +goog.require('ol.source.OSM'); +goog.require('ol.source.VectorSource2'); + + +// WARNING +// This example is an experimental testbed for WebGL vector work. The function +// calls used here are internal and low-level and are not representative of the +// final API. + + +var pointCollection = ol.geom2.PointCollection.createEmpty(101 * 101); +var i, j, x, y; +for (i = 0; i < 101; ++i) { + for (j = 0; j < 101; ++j) { + x = 20000000 * (i - 50) / 50; + y = 20000000 * (j - 50) / 50; + pointCollection.add([x, y]); + } +} + +var k = 1000000; +var lineStringCollection = ol.geom2.LineStringCollection.pack([ + [[-20 * k, -20 * k], [20 * k, 20 * k]], + [[-20 * k, 20 * k], [20 * k, -20 * k]], + [[0 * k, 15 * k], + [10 * k, 5 * k], + [5 * k, 5 * k], + [5 * k, -15 * k], + [-5 * k, -15 * k], + [-5 * k, 5 * k], + [-10 * k, 5 * k], + [0 * k, 15 * k]] +]); + +var map = new ol.Map({ + controls: ol.control.defaults({}, [ + new ol.control.MousePosition({ + undefinedHTML: ' ' + }) + ]), + layers: [ + new ol.layer.TileLayer({ + source: new ol.source.OSM() + }), + new ol.layer.VectorLayer2({ + source: new ol.source.VectorSource2({ + lineStringCollections: [lineStringCollection], + projection: 'EPSG:3857', + pointCollections: [pointCollection] + }) + }) + ], + renderer: ol.RendererHint.WEBGL, + target: 'map', + view: new ol.View2D({ + center: [0, 0], + zoom: 0 + }) +}); diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index f5be363af5..e2e1245013 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -486,6 +486,17 @@ * of `url` when the WMS supports multiple urls for GetMap requests. */ +/** + * @typedef {Object} ol.source.VectorSource2Options + * @property {Array.|undefined} attributions Attributions. + * @property {ol.Extent|undefined} extent Extent. + * @property {Array.|undefined} + * lineStringCollections Line string collections. + * @property {Array.|undefined} pointCollections + * Point collections. + * @property {ol.ProjectionLike} projection Projection. + */ + /** * @typedef {Object} ol.source.WMTSOptions * @property {Array.|undefined} attributions Attributions. diff --git a/src/ol/geom2/geom2.js b/src/ol/geom2/geom2.js new file mode 100644 index 0000000000..a2ce5de71d --- /dev/null +++ b/src/ol/geom2/geom2.js @@ -0,0 +1,71 @@ +goog.provide('ol.geom2'); + +goog.require('goog.asserts'); +goog.require('ol.Extent'); + + +/** + * @param {ol.structs.Buffer} buf Buffer. + * @param {number} dim Dimension. + * @return {ol.Extent} Extent. + */ +ol.geom2.getExtent = function(buf, dim) { + var extent = new Array(2 * dim); + var extentIndex = 0; + var i; + for (i = 0; i < dim; ++i) { + extent[extentIndex++] = Infinity; + extent[extentIndex++] = -Infinity; + } + var bufArr = buf.getArray(); + buf.forEachRange(function(start, stop) { + var extentIndex, i, j; + for (i = start; i < stop; i += dim) { + extentIndex = 0; + for (j = 0; j < dim; ++j) { + extent[extentIndex++] = Math.min(extent[2 * j], bufArr[i + j]); + extent[extentIndex++] = Math.max(extent[2 * j + 1], bufArr[i + j]); + } + } + }); + return extent; +}; + + +/** + * @param {Array.} arr Array. + * @param {number} offset Offset. + * @param {Array.>} unpackedPoints Unpacked points. + * @param {number} dim Dimension. + * @return {number} Offset. + */ +ol.geom2.packPoints = function(arr, offset, unpackedPoints, dim) { + var n = unpackedPoints.length; + var i, j, point; + for (i = 0; i < n; ++i) { + point = unpackedPoints[i]; + goog.asserts.assert(point.length == dim); + for (j = 0; j < dim; ++j) { + arr[offset++] = point[j]; + } + } + return offset; +}; + + +/** + * @param {Array.} arr Array. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} dim Dimension. + * @return {Array.>} Unpacked points. + */ +ol.geom2.unpackPoints = function(arr, offset, end, dim) { + var unpackedPoints = new Array((end - offset) / dim); + var i = 0; + var j; + for (j = offset; j < end; j += dim) { + unpackedPoints[i++] = arr.slice(j, j + dim); + } + return unpackedPoints; +}; 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..06a49b13a9 --- /dev/null +++ b/src/ol/geom2/linestringcollection.js @@ -0,0 +1,208 @@ +goog.provide('ol.geom2.LineString'); +goog.provide('ol.geom2.LineStringCollection'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('ol.geom2'); +goog.require('ol.structs.Buffer'); + + +/** + * @typedef {Array.>} + */ +ol.geom2.LineString; + + + +/** + * This is an internal class that will be removed from the API. + * @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); +}; + + +/** + * This is an internal function that will be removed from the API. + * @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); +}; + + +/** + * @return {Uint16Array} Indices. + */ +ol.geom2.LineStringCollection.prototype.getIndices = function() { + // FIXME cache and track dirty / track output length + var dim = this.dim; + var offsets = goog.array.map(goog.object.getKeys(this.ranges), Number); + goog.array.sort(offsets); + var n = offsets.length; + var indices = []; + var i, j, range, stop; + for (i = 0; i < n; ++i) { + range = this.ranges[offsets[i]]; + stop = range[1] / dim - 1; + for (j = range[0] / dim; j < stop; ++j) { + indices.push(j, j + 1); + } + } + return new Uint16Array(indices); +}; + + +/** + * @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/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..c9ba6e8336 --- /dev/null +++ b/src/ol/geom2/pointcollection.js @@ -0,0 +1,145 @@ +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; + + + +/** + * This is an internal class that will be removed from the API. + * @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; + +}; + + +/** + * This is an internal function that will be removed from the API. + * @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); +}; + + +/** + * This is an internal function that will be removed from the API. + * @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/src/ol/layer/vectorlayer2.exports b/src/ol/layer/vectorlayer2.exports new file mode 100644 index 0000000000..84c34826f8 --- /dev/null +++ b/src/ol/layer/vectorlayer2.exports @@ -0,0 +1 @@ +@exportSymbol ol.layer.VectorLayer2 diff --git a/src/ol/layer/vectorlayer2.js b/src/ol/layer/vectorlayer2.js new file mode 100644 index 0000000000..471fdb8b75 --- /dev/null +++ b/src/ol/layer/vectorlayer2.js @@ -0,0 +1,25 @@ +goog.provide('ol.layer.VectorLayer2'); + +goog.require('ol.layer.Layer'); +goog.require('ol.source.VectorSource2'); + + + +/** + * This is an internal class that will be removed from the API. + * @constructor + * @extends {ol.layer.Layer} + * @param {ol.layer.LayerOptions} options Options. + */ +ol.layer.VectorLayer2 = function(options) { + goog.base(this, options); +}; +goog.inherits(ol.layer.VectorLayer2, ol.layer.Layer); + + +/** + * @return {ol.source.VectorSource2} Source. + */ +ol.layer.VectorLayer2.prototype.getVectorSource = function() { + return /** @type {ol.source.VectorSource2} */ (this.getSource()); +}; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 68126fe41c..d250365299 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -18,9 +18,11 @@ goog.require('ol.Tile'); goog.require('ol.css'); goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); +goog.require('ol.layer.VectorLayer2'); goog.require('ol.renderer.Map'); goog.require('ol.renderer.webgl.ImageLayer'); goog.require('ol.renderer.webgl.TileLayer'); +goog.require('ol.renderer.webgl.VectorLayer2'); goog.require('ol.renderer.webgl.map.shader.Color'); goog.require('ol.renderer.webgl.map.shader.Default'); goog.require('ol.size'); @@ -300,6 +302,8 @@ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { return new ol.renderer.webgl.TileLayer(this, layer); } else if (layer instanceof ol.layer.ImageLayer) { return new ol.renderer.webgl.ImageLayer(this, layer); + } else if (layer instanceof ol.layer.VectorLayer2) { + return new ol.renderer.webgl.VectorLayer2(this, layer); } else { goog.asserts.fail(); return null; diff --git a/src/ol/renderer/webgl/webglvectorlayer2linestringcollection.glsl b/src/ol/renderer/webgl/webglvectorlayer2linestringcollection.glsl new file mode 100644 index 0000000000..0877de55b1 --- /dev/null +++ b/src/ol/renderer/webgl/webglvectorlayer2linestringcollection.glsl @@ -0,0 +1,19 @@ +//! NAMESPACE=ol.renderer.webgl.vectorlayer2.shader.LineStringCollection +//! CLASS=ol.renderer.webgl.vectorlayer2.shader.LineStringCollection + + +//! VERTEX +attribute vec2 a_position; +uniform mat4 u_modelViewMatrix; + +void main(void) { + gl_Position = u_modelViewMatrix * vec4(a_position, 0., 1.); +} + + +//! FRAGMENT +uniform vec4 u_color; + +void main(void) { + gl_FragColor = u_color; +} diff --git a/src/ol/renderer/webgl/webglvectorlayer2linestringcollectionshader.js b/src/ol/renderer/webgl/webglvectorlayer2linestringcollectionshader.js new file mode 100644 index 0000000000..209412877e --- /dev/null +++ b/src/ol/renderer/webgl/webglvectorlayer2linestringcollectionshader.js @@ -0,0 +1,77 @@ +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.vectorlayer2.shader.LineStringCollection'); +goog.require('ol.webgl.shader'); +/** + * @constructor + * @extends {ol.webgl.shader.Fragment} + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment = function() { + goog.base(this, ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment.SOURCE); +}; +goog.inherits(ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment); +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment.DEBUG_SOURCE = 'precision mediump float;\n//! NAMESPACE=ol.renderer.webgl.vectorlayer2.shader.LineStringCollection\n//! CLASS=ol.renderer.webgl.vectorlayer2.shader.LineStringCollection\n\n\n//! FRAGMENT\nuniform vec4 u_color;\n\nvoid main(void) {\n gl_FragColor = u_color;\n}\n\n'; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 b;void main(){gl_FragColor=b;}'; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment.SOURCE = goog.DEBUG ? + ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment.DEBUG_SOURCE : + ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionFragment.OPTIMIZED_SOURCE; +/** + * @constructor + * @extends {ol.webgl.shader.Vertex} + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex = function() { + goog.base(this, ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex.SOURCE); +}; +goog.inherits(ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex); +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex.DEBUG_SOURCE = '//! NAMESPACE=ol.renderer.webgl.vectorlayer2.shader.LineStringCollection\n//! CLASS=ol.renderer.webgl.vectorlayer2.shader.LineStringCollection\n\n\n//! VERTEX\nattribute vec2 a_position;\nuniform mat4 u_modelViewMatrix;\n\nvoid main(void) {\n gl_Position = u_modelViewMatrix * vec4(a_position, 0., 1.);\n}\n\n\n'; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex.OPTIMIZED_SOURCE = 'attribute vec2 b;uniform mat4 a;void main(){gl_Position=a*vec4(b,0,1);}'; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex.SOURCE = goog.DEBUG ? + ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex.DEBUG_SOURCE : + ol.renderer.webgl.vectorlayer2.shader.LineStringCollectionVertex.OPTIMIZED_SOURCE; +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + */ +ol.renderer.webgl.vectorlayer2.shader.LineStringCollection.Locations = function(gl, program) { + /** + * @type {WebGLUniformLocation} + */ + this.u_modelViewMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_modelViewMatrix' : 'a'); + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, goog.DEBUG ? 'u_color' : 'b'); + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'b'); +}; diff --git a/src/ol/renderer/webgl/webglvectorlayer2pointcollection.glsl b/src/ol/renderer/webgl/webglvectorlayer2pointcollection.glsl new file mode 100644 index 0000000000..07f09ef42e --- /dev/null +++ b/src/ol/renderer/webgl/webglvectorlayer2pointcollection.glsl @@ -0,0 +1,21 @@ +//! NAMESPACE=ol.renderer.webgl.vectorlayer2.shader.PointCollection +//! CLASS=ol.renderer.webgl.vectorlayer2.shader.PointCollection + + +//! VERTEX +attribute vec2 a_position; +uniform float u_pointSize; +uniform mat4 u_modelViewMatrix; + +void main(void) { + gl_Position = u_modelViewMatrix * vec4(a_position, 0., 1.); + gl_PointSize = u_pointSize; +} + + +//! FRAGMENT +uniform vec4 u_color; + +void main(void) { + gl_FragColor = u_color; +} diff --git a/src/ol/renderer/webgl/webglvectorlayer2pointcollectionshader.js b/src/ol/renderer/webgl/webglvectorlayer2pointcollectionshader.js new file mode 100644 index 0000000000..825697a28f --- /dev/null +++ b/src/ol/renderer/webgl/webglvectorlayer2pointcollectionshader.js @@ -0,0 +1,82 @@ +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.vectorlayer2.shader.PointCollection'); +goog.require('ol.webgl.shader'); +/** + * @constructor + * @extends {ol.webgl.shader.Fragment} + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment = function() { + goog.base(this, ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment.SOURCE); +}; +goog.inherits(ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment); +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment.DEBUG_SOURCE = 'precision mediump float;\n//! NAMESPACE=ol.renderer.webgl.vectorlayer2.shader.PointCollection\n//! CLASS=ol.renderer.webgl.vectorlayer2.shader.PointCollection\n\n\n//! FRAGMENT\nuniform vec4 u_color;\n\nvoid main(void) {\n gl_FragColor = u_color;\n}\n\n'; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 c;void main(){gl_FragColor=c;}'; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment.SOURCE = goog.DEBUG ? + ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment.DEBUG_SOURCE : + ol.renderer.webgl.vectorlayer2.shader.PointCollectionFragment.OPTIMIZED_SOURCE; +/** + * @constructor + * @extends {ol.webgl.shader.Vertex} + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex = function() { + goog.base(this, ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex.SOURCE); +}; +goog.inherits(ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex); +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex.DEBUG_SOURCE = '//! NAMESPACE=ol.renderer.webgl.vectorlayer2.shader.PointCollection\n//! CLASS=ol.renderer.webgl.vectorlayer2.shader.PointCollection\n\n\n//! VERTEX\nattribute vec2 a_position;\nuniform float u_pointSize;\nuniform mat4 u_modelViewMatrix;\n\nvoid main(void) {\n gl_Position = u_modelViewMatrix * vec4(a_position, 0., 1.);\n gl_PointSize = u_pointSize;\n}\n\n\n'; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex.OPTIMIZED_SOURCE = 'attribute vec2 c;uniform float a;uniform mat4 b;void main(){gl_Position=b*vec4(c,0,1);gl_PointSize=a;}'; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex.SOURCE = goog.DEBUG ? + ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex.DEBUG_SOURCE : + ol.renderer.webgl.vectorlayer2.shader.PointCollectionVertex.OPTIMIZED_SOURCE; +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + */ +ol.renderer.webgl.vectorlayer2.shader.PointCollection.Locations = function(gl, program) { + /** + * @type {WebGLUniformLocation} + */ + this.u_pointSize = gl.getUniformLocation( + program, goog.DEBUG ? 'u_pointSize' : 'a'); + /** + * @type {WebGLUniformLocation} + */ + this.u_modelViewMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_modelViewMatrix' : 'b'); + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, goog.DEBUG ? 'u_color' : 'c'); + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'c'); +}; diff --git a/src/ol/renderer/webgl/webglvectorlayer2renderer.js b/src/ol/renderer/webgl/webglvectorlayer2renderer.js new file mode 100644 index 0000000000..436732107c --- /dev/null +++ b/src/ol/renderer/webgl/webglvectorlayer2renderer.js @@ -0,0 +1,219 @@ +goog.provide('ol.renderer.webgl.VectorLayer2'); + +goog.require('goog.vec.Mat4'); +goog.require('goog.webgl'); +goog.require('ol.math'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.renderer.webgl.vectorlayer2.shader.LineStringCollection'); +goog.require('ol.renderer.webgl.vectorlayer2.shader.PointCollection'); + + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.Map} mapRenderer Map renderer. + * @param {ol.layer.VectorLayer2} vectorLayer2 Vector layer. + */ +ol.renderer.webgl.VectorLayer2 = function(mapRenderer, vectorLayer2) { + + goog.base(this, mapRenderer, vectorLayer2); + + goog.vec.Mat4.makeIdentity(this.projectionMatrix); + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.modelViewMatrix_ = goog.vec.Mat4.createNumberIdentity(); + + /** + * @private + * @type + * {ol.renderer.webgl.vectorlayer2.shader.LineStringCollection.Locations} + */ + this.lineStringCollectionLocations_ = null; + + /** + * @private + * @type {ol.renderer.webgl.vectorlayer2.shader.PointCollection.Locations} + */ + this.pointCollectionLocations_ = null; + +}; +goog.inherits(ol.renderer.webgl.VectorLayer2, ol.renderer.webgl.Layer); + + +/** + * @return {ol.layer.VectorLayer2} Vector layer. + */ +ol.renderer.webgl.VectorLayer2.prototype.getVectorLayer = function() { + return /** @type {ol.layer.VectorLayer2} */ (this.getLayer()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer2.prototype.handleWebGLContextLost = function() { + goog.base(this, 'handleWebGLContextLost'); + this.pointCollectionLocations_ = null; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer2.prototype.renderFrame = + function(frameState, layerState) { + + var mapRenderer = this.getWebGLMapRenderer(); + var gl = mapRenderer.getGL(); + + var view2DState = frameState.view2DState; + + var vectorLayer = this.getVectorLayer(); + var vectorSource = vectorLayer.getVectorSource(); + + var size = frameState.size; + var framebufferDimension = ol.math.roundUpToPowerOfTwo( + Math.max(size[0], size[1])); + + this.bindFramebuffer(frameState, framebufferDimension); + gl.viewport(0, 0, framebufferDimension, framebufferDimension); + + gl.clearColor(0, 0, 0, 0); + gl.clear(goog.webgl.COLOR_BUFFER_BIT); + gl.enable(goog.webgl.BLEND); + + goog.vec.Mat4.makeIdentity(this.modelViewMatrix_); + if (view2DState.rotation !== 0) { + goog.vec.Mat4.rotateZ(this.modelViewMatrix_, -view2DState.rotation); + } + goog.vec.Mat4.scale(this.modelViewMatrix_, + 2 / (framebufferDimension * view2DState.resolution), + 2 / (framebufferDimension * view2DState.resolution), + 1); + goog.vec.Mat4.translate(this.modelViewMatrix_, + -view2DState.center[0], + -view2DState.center[1], + 0); + + var pointCollections = vectorSource.getPointCollections(); + if (pointCollections.length > 0) { + this.renderPointCollections(pointCollections); + } + var lineStringCollections = vectorSource.getLineStringCollections(); + if (lineStringCollections.length > 0) { + this.renderLineStringCollections(lineStringCollections); + } + + goog.vec.Mat4.makeIdentity(this.texCoordMatrix); + goog.vec.Mat4.translate(this.texCoordMatrix, + 0.5, + 0.5, + 0); + goog.vec.Mat4.scale(this.texCoordMatrix, + size[0] / framebufferDimension, + size[1] / framebufferDimension, + 1); + goog.vec.Mat4.translate(this.texCoordMatrix, + -0.5, + -0.5, + 0); + +}; + + +/** + * @param {Array.} lineStringCollections Line + * string collections. + */ +ol.renderer.webgl.VectorLayer2.prototype.renderLineStringCollections = + function(lineStringCollections) { + + var mapRenderer = this.getWebGLMapRenderer(); + var gl = mapRenderer.getGL(); + + var fragmentShader = ol.renderer.webgl.vectorlayer2.shader. + LineStringCollectionFragment.getInstance(); + var vertexShader = ol.renderer.webgl.vectorlayer2.shader. + LineStringCollectionVertex.getInstance(); + var program = mapRenderer.getProgram(fragmentShader, vertexShader); + gl.useProgram(program); + if (goog.isNull(this.lineStringCollectionLocations_)) { + this.lineStringCollectionLocations_ = + new ol.renderer.webgl.vectorlayer2.shader.LineStringCollection. + Locations(gl, program); + } + + gl.uniformMatrix4fv(this.lineStringCollectionLocations_.u_modelViewMatrix, + false, this.modelViewMatrix_); + + var buf, dim, i, indexBuffer, indices, lineStringCollection; + for (i = 0; i < lineStringCollections.length; ++i) { + lineStringCollection = lineStringCollections[i]; + buf = lineStringCollection.buf; + dim = lineStringCollection.dim; + mapRenderer.bindBuffer(goog.webgl.ARRAY_BUFFER, buf); + // FIXME re-use index buffer + // FIXME use mapRenderer.bindBuffer + indices = lineStringCollection.getIndices(); + indexBuffer = gl.createBuffer(); + gl.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, indexBuffer); + gl.bufferData( + goog.webgl.ELEMENT_ARRAY_BUFFER, indices, goog.webgl.DYNAMIC_DRAW); + gl.enableVertexAttribArray(this.lineStringCollectionLocations_.a_position); + gl.vertexAttribPointer(this.lineStringCollectionLocations_.a_position, 2, + goog.webgl.FLOAT, false, 4 * dim, 0); + gl.uniform4fv(this.lineStringCollectionLocations_.u_color, [1, 1, 0, 0.75]); + gl.drawElements( + goog.webgl.LINES, indices.length, goog.webgl.UNSIGNED_SHORT, 0); + gl.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, null); + gl.deleteBuffer(indexBuffer); + } + +}; + + +/** + * @param {Array.} pointCollections Point collections. + */ +ol.renderer.webgl.VectorLayer2.prototype.renderPointCollections = + function(pointCollections) { + + var mapRenderer = this.getWebGLMapRenderer(); + var gl = mapRenderer.getGL(); + + var fragmentShader = ol.renderer.webgl.vectorlayer2.shader. + PointCollectionFragment.getInstance(); + var vertexShader = ol.renderer.webgl.vectorlayer2.shader. + PointCollectionVertex.getInstance(); + var program = mapRenderer.getProgram(fragmentShader, vertexShader); + gl.useProgram(program); + if (goog.isNull(this.pointCollectionLocations_)) { + this.pointCollectionLocations_ = + new ol.renderer.webgl.vectorlayer2.shader.PointCollection.Locations( + gl, program); + } + + gl.uniformMatrix4fv(this.pointCollectionLocations_.u_modelViewMatrix, false, + this.modelViewMatrix_); + + var buf, dim, i, pointCollection; + for (i = 0; i < pointCollections.length; ++i) { + pointCollection = pointCollections[i]; + buf = pointCollection.buf; + dim = pointCollection.dim; + mapRenderer.bindBuffer(goog.webgl.ARRAY_BUFFER, buf); + gl.enableVertexAttribArray(this.pointCollectionLocations_.a_position); + gl.vertexAttribPointer(this.pointCollectionLocations_.a_position, 2, + goog.webgl.FLOAT, false, 4 * dim, 0); + gl.uniform4fv(this.pointCollectionLocations_.u_color, [1, 0, 0, 0.75]); + gl.uniform1f(this.pointCollectionLocations_.u_pointSize, 3); + buf.forEachRange(function(start, stop) { + gl.drawArrays(goog.webgl.POINTS, start / dim, (stop - start) / dim); + }); + } + +}; diff --git a/src/ol/source/vectorsource2.exports b/src/ol/source/vectorsource2.exports new file mode 100644 index 0000000000..1d3cf6c8ed --- /dev/null +++ b/src/ol/source/vectorsource2.exports @@ -0,0 +1 @@ +@exportSymbol ol.source.VectorSource2 diff --git a/src/ol/source/vectorsource2.js b/src/ol/source/vectorsource2.js new file mode 100644 index 0000000000..e5e72f37bd --- /dev/null +++ b/src/ol/source/vectorsource2.js @@ -0,0 +1,54 @@ +goog.provide('ol.source.VectorSource2'); + +goog.require('ol.geom2.LineStringCollection'); +goog.require('ol.geom2.PointCollection'); +goog.require('ol.source.Source'); + + + +/** + * This is an internal class that will be removed from the API. + * @constructor + * @extends {ol.source.Source} + * @param {ol.source.VectorSource2Options} options Options. + */ +ol.source.VectorSource2 = function(options) { + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + projection: options.projection + }); + + /** + * @private + * @type {Array.} + */ + this.pointCollections_ = goog.isDef(options.pointCollections) ? + options.pointCollections : []; + + /** + * @private + * @type {Array.} + */ + this.lineStringCollections_ = goog.isDef(options.lineStringCollections) ? + options.lineStringCollections : []; + +}; +goog.inherits(ol.source.VectorSource2, ol.source.Source); + + +/** + * @return {Array.} Line string collections. + */ +ol.source.VectorSource2.prototype.getLineStringCollections = function() { + return this.lineStringCollections_; +}; + + +/** + * @return {Array.} Point collections. + */ +ol.source.VectorSource2.prototype.getPointCollections = function() { + return this.pointCollections_; +}; diff --git a/src/ol/structs/buffer.js b/src/ol/structs/buffer.js index add2494b9f..91848768c2 100644 --- a/src/ol/structs/buffer.js +++ b/src/ol/structs/buffer.js @@ -62,6 +62,18 @@ ol.structs.Buffer = function(opt_arr, opt_used, opt_usage) { } } + /** + * @private + * @type {?Float32Array} + */ + this.split32_ = null; + + /** + * @private + * @type {ol.structs.IntegerSet} + */ + this.split32DirtySet_ = null; + /** * @private * @type {number} @@ -72,24 +84,31 @@ ol.structs.Buffer = function(opt_arr, opt_used, opt_usage) { }; +/** + * @param {number} size Size. + * @return {number} Offset. + */ +ol.structs.Buffer.prototype.allocate = function(size) { + goog.asserts.assert(size > 0); + var offset = this.freeSet_.findRange(size); + goog.asserts.assert(offset != -1); // FIXME + this.freeSet_.removeRange(offset, offset + size); + return offset; +}; + + /** * @param {Array.} values Values. * @return {number} Offset. */ ol.structs.Buffer.prototype.add = function(values) { var size = values.length; - goog.asserts.assert(size > 0); - var offset = this.freeSet_.findRange(size); - goog.asserts.assert(offset != -1); // FIXME - this.freeSet_.removeRange(offset, offset + size); + var offset = this.allocate(size); var i; for (i = 0; i < size; ++i) { this.arr_[offset + i] = values[i]; } - var ii; - for (i = 0, ii = this.dirtySets_.length; i < ii; ++i) { - this.dirtySets_[i].addRange(offset, offset + size); - } + this.markDirty(size, offset); return offset; }; @@ -139,6 +158,45 @@ ol.structs.Buffer.prototype.getFreeSet = function() { }; +/** + * Returns a Float32Array twice the length of the buffer containing each value + * split into two 32-bit floating point values that, when added together, + * approximate the original value. Even indicies contain the high bits, odd + * indicies contain the low bits. + * @see http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/ + * @return {Float32Array} Split. + */ +ol.structs.Buffer.prototype.getSplit32 = function() { + var arr = this.arr_; + var n = arr.length; + if (goog.isNull(this.split32DirtySet_)) { + this.split32DirtySet_ = new ol.structs.IntegerSet([0, n]); + this.addDirtySet(this.split32DirtySet_); + } + if (goog.isNull(this.split32_)) { + this.split32_ = new Float32Array(2 * n); + } + var split32 = this.split32_; + this.split32DirtySet_.forEachRange(function(start, stop) { + var doubleHigh, i, j, value; + for (i = start, j = 2 * start; i < stop; ++i, j += 2) { + value = arr[i]; + if (value < 0) { + doubleHigh = 65536 * Math.floor(-value / 65536); + split32[j] = -doubleHigh; + split32[j + 1] = value + doubleHigh; + } else { + doubleHigh = 65536 * Math.floor(value / 65536); + split32[j] = doubleHigh; + split32[j + 1] = value - doubleHigh; + } + } + }); + this.split32DirtySet_.clear(); + return this.split32_; +}; + + /** * @return {number} Usage. */ @@ -147,6 +205,18 @@ ol.structs.Buffer.prototype.getUsage = function() { }; +/** + * @param {number} size Size. + * @param {number} offset Offset. + */ +ol.structs.Buffer.prototype.markDirty = function(size, offset) { + var i, ii; + for (i = 0, ii = this.dirtySets_.length; i < ii; ++i) { + this.dirtySets_[i].addRange(offset, offset + size); + } +}; + + /** * @param {number} size Size. * @param {number} offset Offset. @@ -187,8 +257,5 @@ ol.structs.Buffer.prototype.set = function(values, offset) { for (i = 0; i < n; ++i) { arr[offset + i] = values[i]; } - var ii; - for (i = 0, ii = this.dirtySets_.length; i < ii; ++i) { - this.dirtySets_[i].addRange(offset, offset + n); - } + this.markDirty(n, offset); }; diff --git a/test/expect-0.2.0-ol3/expect.js b/test/expect-0.2.0-ol3/expect.js index 052f9a1df2..d8be00a0f1 100644 --- a/test/expect-0.2.0-ol3/expect.js +++ b/test/expect-0.2.0-ol3/expect.js @@ -371,6 +371,20 @@ return this; }; + /** + * Checks if the array sort of equals another array. + * + * @api public + */ + + Assertion.prototype.arreql = function (obj) { + this.assert( + goog.array.equals(this.obj, obj) + , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) } + , function(){ return 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj) }); + return this; + }; + /** * Checks if the obj sortof equals another. * diff --git a/test/spec/ol/geom2/geom2.test.js b/test/spec/ol/geom2/geom2.test.js new file mode 100644 index 0000000000..d894bc65af --- /dev/null +++ b/test/spec/ol/geom2/geom2.test.js @@ -0,0 +1,71 @@ +goog.provide('ol.test.geom2'); + + +describe('ol.geom2', function() { + + var buf, dim; + beforeEach(function() { + buf = new ol.structs.Buffer([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12); + dim = 2; + }); + + describe('ol.geom2.getExtent', function() { + + it('returns the expected extent', function() { + var extent = ol.geom2.getExtent(buf, dim); + expect(extent).to.eql([0, 10, 1, 11]); + }); + + it('returns the expect extent in three dimensions', function() { + var extent = ol.geom2.getExtent(buf, 3); + expect(extent).to.eql([0, 9, 1, 10, 2, 11]); + }); + + it('returns the expect extent in four dimensions', function() { + var extent = ol.geom2.getExtent(buf, 4); + expect(extent).to.eql([0, 8, 1, 9, 2, 10, 3, 11]); + }); + + it('returns the expect extent in six dimensions', function() { + var extent = ol.geom2.getExtent(buf, 6); + expect(extent).to.eql([0, 6, 1, 7, 2, 8, 3, 9, 4, 10, 5, 11]); + }); + + }); + + describe('ol.geom2.packPoints', function() { + + it('packs points as expected', function() { + var arr = []; + var offset = ol.geom2.packPoints(arr, 0, [[0, 1], [2, 3], [4, 5]], 2); + expect(offset).to.be(6); + expect(arr).to.eql([0, 1, 2, 3, 4, 5]); + }); + + it('raises an exception if dimensions do not match', function() { + expect(function() { + ol.geom2.packPoints([], 0, [[0, 1, 2]], 2); + }).to.throwException(); + }); + + }); + + describe('ol.geom2.unpackPoints', function() { + + it('unpacks points in two dimensions', function() { + var unpackedPoints = ol.geom2.unpackPoints([0, 1, 2, 3, 4, 5], 0, 6, 2); + expect(unpackedPoints).to.eql([[0, 1], [2, 3], [4, 5]]); + }); + + it('unpacks points in three dimensions', function() { + var unpackedPoints = ol.geom2.unpackPoints([0, 1, 2, 3, 4, 5], 0, 6, 3); + expect(unpackedPoints).to.eql([[0, 1, 2], [3, 4, 5]]); + }); + + }); + +}); + + +goog.require('ol.geom2'); +goog.require('ol.structs.Buffer'); diff --git a/test/spec/ol/geom2/linecollection.test.js b/test/spec/ol/geom2/linecollection.test.js new file mode 100644 index 0000000000..8fe7c4eddb --- /dev/null +++ b/test/spec/ol/geom2/linecollection.test.js @@ -0,0 +1,330 @@ +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('getIndices', function() { + + it('returns the expected value', function() { + expect(lsc.getIndices()).to.be.empty(); + }); + + }); + + 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('getIndices', function() { + + it('returns the expected value', function() { + expect(lsc.getIndices()).to.arreql([0, 1]); + }); + + }); + + 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('getIndices', function() { + + it('returns the expected value', function() { + expect(lsc.getIndices()).to.arreql([0, 1, 2, 3, 3, 4]); + }); + + }); + + 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]]); + expect(lsc.getIndices()).to.arreql([2, 3, 3, 4]); + }); + + 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]]); + expect(lsc.getIndices()).to.arreql([0, 1]); + }); + + }); + + 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]]); + expect(lsc.getIndices()).to.arreql([2, 3, 3, 4, 5, 6, 6, 7]); + }); + + }); + + }); + +}); + + +goog.require('ol.geom2.LineStringCollection'); +goog.require('ol.extent'); +goog.require('ol.structs.Buffer'); 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'); diff --git a/test/spec/ol/structs/buffer.test.js b/test/spec/ol/structs/buffer.test.js index 2e3710cfa3..d91e37a4d7 100644 --- a/test/spec/ol/structs/buffer.test.js +++ b/test/spec/ol/structs/buffer.test.js @@ -220,6 +220,41 @@ describe('ol.structs.Buffer', function() { }); + describe('with a populated instance', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer([1234567.1234567, -7654321.7654321]); + }); + + describe('getSplit32', function() { + + it('returns the expected value', function() { + var split32 = b.getSplit32(); + expect(split32).to.be.a(Float32Array); + expect(split32).to.have.length(4); + expect(split32[0]).to.roughlyEqual(1179648.0, 1e1); + expect(split32[1]).to.roughlyEqual(54919.12345670001, 1e-2); + expect(split32[2]).to.roughlyEqual(-7602176.0, 1e1); + expect(split32[3]).to.roughlyEqual(-52145.76543209981, 1e-2); + }); + + it('tracks updates', function() { + b.getSplit32(); + b.getArray()[0] = 0; + b.markDirty(1, 0); + var split32 = b.getSplit32(); + expect(split32).to.be.a(Float32Array); + expect(split32).to.have.length(4); + expect(split32[0]).to.be(0); + expect(split32[1]).to.be(0); + expect(split32[2]).to.roughlyEqual(-7602176.0, 1e1); + expect(split32[3]).to.roughlyEqual(-52145.76543209981, 1e-2); + }); + + }); + }); + describe('usage tests', function() { it('allows multiple adds and removes', function() {