From df6c8dca8f75ea22777281eb2bf1f447c55ee3d1 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Tue, 20 Apr 2021 16:54:56 -0400 Subject: [PATCH] feat: Add a grid feature. This refactors the contourFeature to avoid code duplication, as the grid and contour features are nearly the same. Specifically, the contour feature has data values at each vertex of a mesh and the grid feature has data values on the elements of the mesh. The grid feature has solid colors for each mesh element, while the contour feature has interpolated values between the vertices. This adds a small tutorial. This closes #545. --- scripts/datastore.js | 2 +- scripts/make_thumbnails.py | 2 +- src/contourFeature.js | 101 +------- src/gridFeature.js | 175 +++++++++++++ src/index.js | 1 + src/meshFeature.js | 118 ++++++++- src/tile.js | 3 +- src/util/index.js | 3 +- src/util/mesh.js | 125 ++++++++++ src/webgl/contourFeature.js | 227 +---------------- src/webgl/gridFeature.js | 52 ++++ src/webgl/index.js | 2 + .../{contourFeature.frag => meshColored.frag} | 0 src/webgl/meshColored.js | 236 ++++++++++++++++++ .../{contourFeature.vert => meshColored.vert} | 0 tests/cases/contourFeature.js | 9 +- tests/cases/gridFeature.js | 131 ++++++++++ tests/gl-cases/webglGrid.js | 126 ++++++++++ tutorials/grid/index.pug | 62 +++++ tutorials/grid/thumb.jpg | Bin 0 -> 85979 bytes tutorials/grid/tutorial.json | 10 + 21 files changed, 1054 insertions(+), 331 deletions(-) create mode 100644 src/gridFeature.js create mode 100644 src/util/mesh.js create mode 100644 src/webgl/gridFeature.js rename src/webgl/{contourFeature.frag => meshColored.frag} (100%) create mode 100644 src/webgl/meshColored.js rename src/webgl/{contourFeature.vert => meshColored.vert} (100%) create mode 100644 tests/cases/gridFeature.js create mode 100644 tests/gl-cases/webglGrid.js create mode 100644 tutorials/grid/index.pug create mode 100644 tutorials/grid/thumb.jpg create mode 100644 tutorials/grid/tutorial.json diff --git a/scripts/datastore.js b/scripts/datastore.js index db26118a29..40b22aaef0 100644 --- a/scripts/datastore.js +++ b/scripts/datastore.js @@ -6,7 +6,7 @@ var tar = require('tar'); var registry = { 'AdderallCities2015.csv': 'c3e984482cc6db1193a6dca2a55396a2baad8541a5c8c679f33750b76f766f40a119ec3e63abbabcd095d752c3df8ce21bf24cbe629502121f24ba90b4b0674c', - 'base-images.tgz': '346dcdcf6e88aa9bfde684a311a452431af01b055f007e73839d42aa19ce0846af20d0bc296f7e1acca0af2759896d647dbbdbf07b20428f0e10464a1764c77e', + 'base-images.tgz': '9eed90578ac35e96ba701198e2518cb03bf08447d649375cbf7729524139a9951a5b1b580fc03371532dc128a530794e061a8e05b69aa5b5869efbf98245670e', 'blue.jpg': '867b1f3c568289efc7d0dba97d827a2bc4d83a7465cebcb3b5aec7bac6a38cf70d037d1814402bc97ad1f2f6737cfb5ce97db0a4fb53a716e77fd3ba57a7ab3b', 'cities.csv': '5a665e5feda24f28e5cf4ed0801b67e73bbcf3ea781b2e50d11284214e67b25b68e6a1c48da46e5e4d4d0c54c2ec18f88d292224b4541fb279396cf7b94beac9', 'earthquakes.json': 'f098b6437411384b552419b4a36264c1bb3fed816ccfe9545145175e0b92a0b7ad5ebdcb9dddd0a12a90499143ffa471c02f6e049be5b973db607ff066892500', diff --git a/scripts/make_thumbnails.py b/scripts/make_thumbnails.py index 9d3eab3e66..79493ee453 100755 --- a/scripts/make_thumbnails.py +++ b/scripts/make_thumbnails.py @@ -55,7 +55,7 @@ def process_item(path, opts): - output = (open('/tmp/thumbnail.out', 'ab') + output = (open('/tmp/thumbnail.out', 'a') if opts.get('verbose', 0) >= 1 else open(os.devnull, 'w')) data = json.load(open(path)) if data.get('disabled') and not opts.get('all'): diff --git a/src/contourFeature.js b/src/contourFeature.js index ae5c78b26b..faf015313e 100644 --- a/src/contourFeature.js +++ b/src/contourFeature.js @@ -5,6 +5,7 @@ var meshFeature = require('./meshFeature'); * Contour feature specification. * * @typedef {geo.feature.spec} geo.contourFeature.spec + * @extends geo.feature.spec * @property {object[]} [data=[]] An array of arbitrary objects used to * construct the feature. * @property {geo.contourFeature.styleSpec} [style] An object that contains @@ -71,23 +72,8 @@ var meshFeature = require('./meshFeature'); /** * Computed contour information. * - * @typedef {geo.meshFeature.meshInfo} geo.contourFeature.contourInfo - * @property {number[]} value An array of values that have been normalized to a - * range of [0, steps]. There is one value per vertex. - * @property {number[]} opacity An array of opacities per vertex. - * @property {number} minValue the minimum value used for the contour. If - * `rangeValues` was specified, this is the first entry of that array. - * @property {number} maxValue the maximum value used for the contour. If - * `rangeValues` was specified, this is the last entry of that array. - * @property {number} factor If linear value scaling is used, this is the - * number of color values divided by the difference between the maximum and - * minimum values. It is ignored if non-linear value scaling is used. - * @property {geo.geoColorObject} minColor The color used for values below - * minValue. Includes opacity. - * @property {geo.geoColorObject} maxColor The color used for values above - * maxValue. Includes opacity. - * @property {geo.geoColorObject[]} colorMap The specified `colorRange` and - * `opacityRange` converted into objects that include opacity. + * @typedef {geo.meshFeature.meshColoredInfo} geo.contourFeature.contourInfo + * @extends geo.meshFeature.meshColoredInfo */ /** @@ -110,6 +96,7 @@ var contourFeature = function (arg) { var $ = require('jquery'); var util = require('./util'); + var meshUtil = require('./util/mesh'); arg = arg || {}; meshFeature.call(this, arg); @@ -129,82 +116,7 @@ var contourFeature = function (arg) { * information. */ this._createContours = function () { - var contour = m_this.contour, - valueFunc = m_this.style.get('value'), - usedFunc = m_this.style('used') !== undefined ? - m_this.style.get('used') : - function (d, i) { return util.isNonNullFinite(valueFunc(d, i)); }, - minmax, val, range, i, k; - var result = m_this._createMesh({ - used: usedFunc, - opacity: m_this.style.get('opacity'), - value: valueFunc - }); - if (!result.numVertices || !result.numElements) { - return result; - } - var stepped = contour.get('stepped')(result), - opacityRange = contour.get('opacityRange')(result), - rangeValues = contour.get('rangeValues')(result); - result.stepped = stepped === undefined || stepped ? true : false; - /* Create the min/max colors and the color array */ - result.colorMap = []; - result.minColor = $.extend( - {a: contour.get('minOpacity')(result) || 0}, - util.convertColor(contour.get('minColor')(result))); - result.maxColor = $.extend( - {a: contour.get('maxOpacity')(result) || 0}, - util.convertColor(contour.get('maxColor')(result))); - contour.get('colorRange')(result).forEach(function (clr, idx) { - result.colorMap.push($.extend({ - a: opacityRange && opacityRange[idx] !== undefined ? opacityRange[idx] : 1 - }, util.convertColor(clr))); - }); - /* Get min and max values */ - minmax = util.getMinMaxValues(result.value, contour.get('min')(result), contour.get('max')(result)); - result.minValue = minmax.min; - result.maxValue = minmax.max; - if (!rangeValues || !result.colorMap || - (rangeValues.length !== result.colorMap.length + 1 && ( - stepped || rangeValues.length !== result.colorMap.length))) { - rangeValues = null; - } - if (rangeValues) { /* ensure increasing monotonicity */ - for (k = 1; k < rangeValues.length; k += 1) { - if (rangeValues[k - 1] > rangeValues[k]) { - rangeValues = null; - break; - } - } - } - if (rangeValues) { - result.minValue = rangeValues[0]; - result.maxValue = rangeValues[rangeValues.length - 1]; - } - range = result.maxValue - result.minValue; - if (!range) { - result.colorMap = result.colorMap.slice(0, 1); - range = 1; - rangeValues = null; - } - result.rangeValues = rangeValues; - result.factor = result.colorMap.length / range; - /* Scale values */ - for (i = 0; i < result.numVertices; i += 1) { - val = result.value[i]; - if (rangeValues && val >= result.minValue && val <= result.maxValue) { - for (k = 1; k < rangeValues.length; k += 1) { - if (val <= rangeValues[k]) { - result.value[i] = k - 1 + (val - rangeValues[k - 1]) / - (rangeValues[k] - rangeValues[k - 1]); - break; - } - } - } else { - result.value[i] = (val - result.minValue) * result.factor; - } - } - return result; + return meshUtil.createColoredMesh(m_this, false); }; this.contour = m_this.mesh; @@ -222,8 +134,9 @@ var contourFeature = function (arg) { { opacity: 1.0, value: function (d, i) { - return m_this.position()(d, i).z; + return util.isNonNullFinite(d) ? d : m_this.position()(d, i).z; }, + position: (d) => d || {x: 0, y: 0}, origin: (p) => (p.length >= 3 ? [p[0], p[1], 0] : [0, 0, 0]) }, arg.style === undefined ? {} : arg.style diff --git a/src/gridFeature.js b/src/gridFeature.js new file mode 100644 index 0000000000..ed88aef88c --- /dev/null +++ b/src/gridFeature.js @@ -0,0 +1,175 @@ +var inherit = require('./inherit'); +var meshFeature = require('./meshFeature'); + +/** + * Grid feature specification. + * + * @typedef {geo.feature.spec} geo.gridFeature.spec + * @extends geo.feature.spec + * @property {object[]} [data=[]] An array of arbitrary objects used to + * construct the feature. + * @property {geo.gridFeature.styleSpec} [style] An object that contains + * style values for the feature. + * @property {geo.gridFeature.gridSpec} [grid] The grid specification for the + * feature. + */ + +/** + * Style specification for a grid feature. + * + * @typedef {geo.feature.styleSpec} geo.gridFeature.styleSpec + * @extends geo.feature.styleSpec + * @property {geo.geoPosition|function} [position=data] The position of each + * data element. This defaults to just using `x`, `y`, and `z` properties + * of the data element itself. The position is in the feature's gcs + * coordinates. + * @property {number|function} [value=data.z] The value of each data element. + * This defaults to the `z` property of the data elements. If the value of + * a grid point is `null` or `undefined`, the point and elements that use + * that point won't be included in the results. + * @property {number|function} [opacity=1] The opacity for the whole feature on + * a scale of 0 to 1. + * @property {number[]|function} [origin] Origin in map gcs coordinates used + * for to ensure high precision drawing in this location. When called as a + * function, this is passed the vertex positions as a single continuous array + * in map gcs coordinates. It defaults to the first vertex used in the + * grid. + */ + +/** + * Grid specification. All of these properties can be functions, which get + * passed the {@link geo.meshFeature.meshInfo} object. + * + * @typedef {geo.meshFeature.meshSpec} geo.gridFeature.gridSpec + * @extends geo.meshFeature.meshSpec + * @property {number} [min] Minimum grid value. If unspecified, taken from + * the computed minimum of the `value` style. + * @property {number} [max] Maximum grid value. If unspecified, taken from + * the computed maximum of the `value` style. + * @property {geo.geoColor} [minColor='black'] Color used for any value below + * the minimum. + * @property {number} [minOpacity=0] Opacity used for any value below the + * minimum. + * @property {geo.geoColor} [maxColor='black'] Color used for any value above + * the maximum. + * @property {number} [maxOpacity=0] Opacity used for any value above the + * maximum. + * @property {boolean} [stepped] If falsy but not `undefined`, smooth + * transitions between colors. + * @property {geo.geoColor[]} [colorRange=] An array of colors + * used to show the range of values. The default is a 9-step color table. + * @property {number[]} [opacityRange] An array of opacities used to show the + * range of values. If unspecified, the opacity is 1. If this is a shorter + * list than the `colorRange`, an opacity of 1 is used for the entries near + * the end of the color range. + * @property {number[]} [rangeValues] An array used to map values to the + * `colorRange`. By default, values are spaced linearly. If specified, the + * entries must be increasing weakly monotonic, and there must be one more + * entry then the length of `colorRange`. + */ + +/** + * Computed grid information. + * + * @typedef {geo.meshFeature.meshColoredInfo} geo.gridFeature.gridInfo + * @extends geo.meshFeature.meshColoredInfo + */ + +/** + * Create a new instance of class gridFeature. + * + * @class + * @alias geo.gridFeature + * @extends geo.meshFeature + * + * @borrows geo.gridFeature#mesh as geo.gridFeature#grid + * + * @param {geo.gridFeature.spec} arg + * @returns {geo.gridFeature} + */ +var gridFeature = function (arg) { + 'use strict'; + if (!(this instanceof gridFeature)) { + return new gridFeature(arg); + } + + var $ = require('jquery'); + var util = require('./util'); + var meshUtil = require('./util/mesh'); + + arg = arg || {}; + meshFeature.call(this, arg); + + /** + * @private + */ + var m_this = this, + s_init = this._init; + + /** + * Create a set of vertices and values and opacities inside triangles. + * Create a set of triangles of indices into the vertex array. Create a + * color and opacity map corresponding to the values. + * + * @returns {geo.gridFeature.gridInfo} An object with the grid + * information. + */ + this._createGrids = function () { + return meshUtil.createColoredMesh(m_this, true); + }; + + this.grid = m_this.mesh; + + /** + * Initialize. + * + * @param {geo.gridFeature.spec} arg The grid feature specification. + */ + this._init = function (arg) { + s_init.call(m_this, arg); + + var defaultStyle = $.extend( + {}, + { + opacity: 1.0, + value: function (d, i) { + return util.isNonNullFinite(d) ? d : m_this.position()(d, i).z; + }, + position: (d) => d || {x: 0, y: 0}, + origin: (p) => (p.length >= 3 ? [p[0], p[1], 0] : [0, 0, 0]) + }, + arg.style === undefined ? {} : arg.style + ); + + m_this.style(defaultStyle); + + m_this.grid($.extend({}, { + minColor: 'black', + minOpacity: 0, + maxColor: 'black', + maxOpacity: 0, + /* 9-step based on paraview bwr colortable */ + colorRange: [ + {r: 0.07514311, g: 0.468049805, b: 1}, + {r: 0.468487184, g: 0.588057293, b: 1}, + {r: 0.656658579, g: 0.707001303, b: 1}, + {r: 0.821573924, g: 0.837809045, b: 1}, + {r: 0.943467973, g: 0.943498599, b: 0.943398095}, + {r: 1, g: 0.788626485, b: 0.750707739}, + {r: 1, g: 0.6289553, b: 0.568237474}, + {r: 1, g: 0.472800903, b: 0.404551679}, + {r: 0.916482116, g: 0.236630659, b: 0.209939162} + ] + }, arg.mesh || {}, arg.grid || {})); + + if (arg.mesh || arg.grid) { + m_this.dataTime().modified(); + } + }; + + this._init(arg); + return this; +}; + +inherit(gridFeature, meshFeature); +module.exports = gridFeature; diff --git a/src/index.js b/src/index.js index a9a224e6aa..6bb35d59b5 100644 --- a/src/index.js +++ b/src/index.js @@ -63,6 +63,7 @@ module.exports = $.extend({ imageTile: require('./imageTile'), isolineFeature: require('./isolineFeature'), geojsonReader: require('./geojsonReader'), + gridFeature: require('./gridFeature'), layer: require('./layer'), lineFeature: require('./lineFeature'), map: require('./map'), diff --git a/src/meshFeature.js b/src/meshFeature.js index 93c19dc2f6..74bd8645c1 100644 --- a/src/meshFeature.js +++ b/src/meshFeature.js @@ -5,6 +5,7 @@ var feature = require('./feature'); * Mesh feature specification. * * @typedef {geo.feature.spec} geo.meshFeature.spec + * @extends geo.feature.spec * @property {object[]} [data=[]] An array of arbitrary objects used to * construct the feature. * @property {geo.feature.styleSpec} [style] An object that contains style @@ -80,6 +81,10 @@ var feature = require('./feature'); * For `triangle`, each element stands alone. * @property {number[]} elements A packed array of indices into the `pos` array * defining the elements. Each sequential three values forms a triangle. + * @property {number[]} elementIndex An array that has one value for each + * triplet of values in the `elements` array. The value is the 0-based + * index of the element that can be used to correspond it to element-based + * parameters. * @property {number[]} index An array that references which data index is * associated with each vertex. * @property {number[]} pos A packed array of coordinates in the interface gcs @@ -214,7 +219,7 @@ var meshFeature = function (arg) { /** * Create a set of vertices and elements from a mesh specification. * - * This currently takes a set of `vertexValueFuncs` to detemrine which + * This currently takes a set of `vertexValueFuncs` to determine which * vertices are used (shown) and values at those vertices. It could be * extended with `elementValueFuncs`. * @@ -225,10 +230,18 @@ var meshFeature = function (arg) { * function is passed `(data[idx], idx)` and if it returns a falsy value * for a data point, the vertex associated with that data point is removed * from the resultant mesh. + * @param {object} [elementValueFuncs] A dictionary where the keys are the + * names of properties to include in the results and the values are + * functions that are evaluated at each element with the arguments + * `(data[idx], idx)`. If a key is named `used`, then its function is + * passed `(data[idx], idx)` and if it returns a falsy value for a data + * point, the triangle or square associated with that data point is + * removed from the resultant mesh. * @returns {geo.meshFeature.meshInfo} An object with the mesh information. */ - this._createMesh = function (vertexValueFuncs) { + this._createMesh = function (vertexValueFuncs, elementValueFuncs) { vertexValueFuncs = vertexValueFuncs || {}; + elementValueFuncs = elementValueFuncs || {}; var i, i3, j, k, idx, numPts, usedPts, usePos, item, key, data = m_this.data(), posFunc = m_this.position(), posVal, @@ -241,16 +254,23 @@ var meshFeature = function (arg) { y0 = m_this.mesh.get('y0')(data), dx = m_this.mesh.get('dx')(data), dy = m_this.mesh.get('dy')(data), - calcX, skipColumn, x, origI, /* used for wrapping */ + calcX, calcCol, skipColumn, x, origI, /* used for wrapping */ gridWorig = gridW, /* can be different when wrapping */ result = { shape: 'square', - elements: [] + elements: [], + elementIndex: [] }; /* If we are using a grid, calculate the elements and positions. */ if (!elements) { - if (gridW * gridH > data.length) { - gridH = Math.floor(data.length / gridW); + if (Object.keys(vertexValueFuncs).length) { + if (gridW * gridH > data.length) { + gridH = Math.floor(data.length / gridW); + } + } else if (Object.keys(elementValueFuncs).length) { + if ((gridW - 1) * (gridH - 1) > data.length) { + gridH = Math.floor(data.length / (gridW - 1)) + 1; + } } /* If we are not using the position values (we are using x0, y0, dx, dy), * and wrapLongitude is turned on, and the position spans 180 degrees, @@ -262,6 +282,7 @@ var meshFeature = function (arg) { x0 + dx * (gridW - 1) < -180 || x0 + dx * (gridW - 1) > 180) && dx > -180 && dx < 180 && dx * (gridW - 1) < 360 + 1e-4) { calcX = []; + calcCol = []; for (i = 0; i < gridW; i += 1) { x = x0 + i * dx; while (x < -180) { x += 360; } @@ -270,13 +291,18 @@ var meshFeature = function (arg) { if (x > calcX[calcX.length - 1]) { calcX.push(x - 360); calcX.push(calcX[calcX.length - 2] + 360); + calcCol.push(i); + calcCol.push(i + 1); } else { calcX.push(x + 360); calcX.push(calcX[calcX.length - 2] - 360); + calcCol.push(i); + calcCol.push(i + 1); } skipColumn = i; } calcX.push(x); + calcCol.push(i); } gridW += 2; if (Math.abs(Math.abs(gridWorig * dx) - 360) < 0.01) { @@ -285,6 +311,7 @@ var meshFeature = function (arg) { while (x < -180) { x += 360; } while (x > 180) { x -= 360; } calcX.push(x); + calcCol.push(0); } } /* Calculate the value for point */ @@ -312,6 +339,8 @@ var meshFeature = function (arg) { result.elements.push(idx + gridW + 1); result.elements.push(idx + gridW); result.elements.push(idx + 1); + result.elementIndex.push(j * (gridW - 1) + (calcCol ? calcCol[i] : i)); + result.elementIndex.push(j * (gridW - 1) + (calcCol ? calcCol[i] : i)); } } } @@ -330,15 +359,19 @@ var meshFeature = function (arg) { result.elements.push(elements[i][2]); result.elements.push(elements[i][3]); result.elements.push(elements[i][1]); + result.elementIndex.push(i); + result.elementIndex.push(i); } } else { - for (i = 0; i < elements.length - 3; i += 4) { + for (i = j = 0; i < elements.length - 3; i += 4, j += 1) { result.elements.push(elements[i]); result.elements.push(elements[i + 1]); result.elements.push(elements[i + 3]); result.elements.push(elements[i + 2]); result.elements.push(elements[i + 3]); result.elements.push(elements[i + 1]); + result.elementIndex.push(j); + result.elementIndex.push(j); } } } else { @@ -348,19 +381,50 @@ var meshFeature = function (arg) { result.elements.push(elements[i][0]); result.elements.push(elements[i][1]); result.elements.push(elements[i][2]); + result.elementIndex.push(i); } } else { result.elements = elements.slice(0, elements.length - (elements.length % 3)); + for (i = j = 0; i < elements.length - 2; i += 3, j += 1) { + result.elementIndex.push(j); + } } } numPts = data.length; usePos = true; } + /* If we have an `elementValueFuncs.used` function, remove any unused + * elements. Unused vertices are removed later. */ result.verticesPerElement = result.shape === 'triangle' ? 3 : 6; + var vpe = result.verticesPerElement; + if (elementValueFuncs.used) { + var used = new Array(result.elementIndex[result.elementIndex.length - 1] + 1); + for (i = 0; i < used.length; i += 1) { + used[i] = elementValueFuncs.used(data[i], i); + } + for (i = 0; i < result.elementIndex.length; i += 1) { + if (!used[result.elementIndex[i]]) { + break; + } + } + if (i < result.elementIndex.length) { + for (j = i; i < result.elementIndex.length; i += 1) { + if (used[result.elementIndex[i]]) { + result.elementIndex[j] = result.elementIndex[i]; + for (k = 0; k < 3; k += 1) { + result.elements[j * 3 + k] = result.elements[i * 3 + k]; + } + j += 1; + } + } + result.elements.splice(j * 3); + result.elementIndex.splice(j); + } + } /* If we have a `vertexValueFuncs.used` function, remove any unused * vertices. Then, remove any elements that have a vertex that can't be * used. This could leave vertices that are unused by any element, but - * removing those is expensive so it is not done. */ + * they are removed later. */ if (vertexValueFuncs.used) { for (i = 0; i < numPts; i += 1) { idx = result.index ? result.index[i] : i; @@ -370,8 +434,7 @@ var meshFeature = function (arg) { } if (i !== numPts) { usedPts = i; - var remap = new Array(numPts), - vpe = result.verticesPerElement; + var remap = new Array(numPts); for (j = 0; j < usedPts; j += 1) { remap[j] = j; } @@ -401,13 +464,37 @@ var meshFeature = function (arg) { result.elements[k + j] = remap[result.elements[i + j]]; } if (j === vpe) { + result.elementIndex[Math.floor(k / 3)] = result.elementIndex[Math.floor(i / 3)]; + if (vpe === 6) { + result.elementIndex[Math.floor(k / 3) + 1] = result.elementIndex[Math.floor(i / 3) + 1]; + } k += vpe; } } result.elements.splice(k); + result.elementIndex.splice(Math.floor(k / 3)); numPts = usedPts; } } + /* Remove unused vertices -- this could be disabled to save time. It + * cannot be applied if skipColumn is defined, as in that case some + * vertices are used multiple times but with different coordinates. We + * could also do this when vertexValueFuncs.used is used, but that usually + * has a much smaller reduction in values and isn't worth the time. */ + if (elementValueFuncs.used && skipColumn === undefined) { + var vertexMap = new Array(numPts); + var oldindex = result.index; + result.index = []; + for (i = 0; i < result.elements.length; i += 1) { + k = result.elements[i]; + if (vertexMap[k] === undefined) { + vertexMap[k] = result.index.length; + result.index.push(oldindex ? oldindex[k] : k); + } + result.elements[i] = vertexMap[k]; + } + numPts = result.index.length; + } /* Get point locations and store them in a packed array */ result.pos = new Array(numPts * 3); for (key in vertexValueFuncs) { @@ -439,6 +526,17 @@ var meshFeature = function (arg) { } } } + for (key in elementValueFuncs) { + if (key !== 'used' && elementValueFuncs.hasOwnProperty(key)) { + var func = elementValueFuncs[key]; + result[key] = new Array(result.elementIndex.length); + for (i = 0; i < result.elementIndex.length; i += 1) { + idx = result.elementIndex[i]; + item = data[idx]; + result[key][i] = func(item, idx); + } + } + } result.numVertices = numPts; result.numElements = result.elements.length / result.verticesPerElement; return result; diff --git a/src/tile.js b/src/tile.js index 99912225cc..964400f544 100644 --- a/src/tile.js +++ b/src/tile.js @@ -145,7 +145,8 @@ var tile = function (spec) { /** * Return a unique string representation of the given tile useble as a hash - * key. + * key. Possibly extend later to include url information to make caches + * aware of the tile source. * * @returns {string} */ diff --git a/src/util/index.js b/src/util/index.js index 31f7ffcabe..3860fb9aa7 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -7,6 +7,7 @@ module.exports = Object.assign( /* These modules are added under separate names */ { DistanceGrid: require('./distanceGrid'), - ClusterGroup: require('./clustering') + ClusterGroup: require('./clustering'), + mesh: require('./mesh') } ); diff --git a/src/util/mesh.js b/src/util/mesh.js new file mode 100644 index 0000000000..94f2d1b1a3 --- /dev/null +++ b/src/util/mesh.js @@ -0,0 +1,125 @@ +/** + * Computed colored mesh information. + * + * @typedef {geo.meshFeature.meshInfo} geo.meshFeature.meshColoredInfo + * @extends geo.meshFeature.meshInfo + * @property {number[]} value An array of values that have been normalized to a + * range of [0, steps]. There is one value per vertex or element. + * @property {number[]} opacity An array of opacities per vertex or element. + * @property {number} minValue the minimum value used for the contour. If + * `rangeValues` was specified, this is the first entry of that array. + * @property {number} maxValue the maximum value used for the contour. If + * `rangeValues` was specified, this is the last entry of that array. + * @property {number} factor If linear value scaling is used, this is the + * number of color values divided by the difference between the maximum and + * minimum values. It is ignored if non-linear value scaling is used. + * @property {geo.geoColorObject} minColor The color used for values below + * minValue. Includes opacity. + * @property {geo.geoColorObject} maxColor The color used for values above + * maxValue. Includes opacity. + * @property {geo.geoColorObject[]} colorMap The specified `colorRange` and + * `opacityRange` converted into objects that include opacity. + * @property {boolean} elementValues Truthy if the `value` and `opacity` are + * for elements, falsy for vertices. + */ + +/** + * Create a set of vertices, values at the vertices or elements, and opacities + * at the vertices or elements. Create a set of triangles of indices into the + * vertex array. Create a color and opacity map corresponding to the values. + * + * @param {geo.meshFeature} feature A mesh feature. + * @param {boolean} elementValues Truthy to compute values and opacities at + * elements, falsy for vertices. + * @returns {geo.meshFeature.meshColoredInfo} An object with the colored mesh + * information. + */ +function createColoredMesh(feature, elementValues) { + var $ = require('jquery'); + var util = require('../util'); + + var mesh = feature.mesh, + valueFunc = feature.style.get('value'), + usedFunc = feature.style('used') !== undefined ? + feature.style.get('used') : + function (d, i) { return util.isNonNullFinite(valueFunc(d, i)); }, + minmax, val, range, i, k; + var meshParams = { + used: usedFunc, + opacity: feature.style.get('opacity'), + value: valueFunc + }; + var result = feature._createMesh( + !elementValues ? meshParams : {}, + elementValues ? meshParams : {}); + result.elementValues = !!elementValues; + if (!result.numVertices || !result.numElements) { + return result; + } + var stepped = mesh.get('stepped')(result), + opacityRange = mesh.get('opacityRange')(result), + rangeValues = mesh.get('rangeValues')(result); + result.stepped = stepped === undefined || stepped ? true : false; + /* Create the min/max colors and the color array */ + result.colorMap = []; + result.minColor = $.extend( + {a: mesh.get('minOpacity')(result) || 0}, + util.convertColor(mesh.get('minColor')(result))); + result.maxColor = $.extend( + {a: mesh.get('maxOpacity')(result) || 0}, + util.convertColor(mesh.get('maxColor')(result))); + mesh.get('colorRange')(result).forEach(function (clr, idx) { + result.colorMap.push($.extend({ + a: opacityRange && opacityRange[idx] !== undefined ? opacityRange[idx] : 1 + }, util.convertColor(clr))); + }); + /* Get min and max values */ + minmax = util.getMinMaxValues(result.value, mesh.get('min')(result), mesh.get('max')(result)); + result.minValue = minmax.min; + result.maxValue = minmax.max; + if (!rangeValues || !result.colorMap || + (rangeValues.length !== result.colorMap.length + 1 && ( + stepped || rangeValues.length !== result.colorMap.length))) { + rangeValues = null; + } + if (rangeValues) { /* ensure increasing monotonicity */ + for (k = 1; k < rangeValues.length; k += 1) { + if (rangeValues[k - 1] > rangeValues[k]) { + rangeValues = null; + break; + } + } + } + if (rangeValues) { + result.minValue = rangeValues[0]; + result.maxValue = rangeValues[rangeValues.length - 1]; + } + range = result.maxValue - result.minValue; + if (!range) { + result.colorMap = result.colorMap.slice(0, 1); + range = 1; + rangeValues = null; + } + result.rangeValues = rangeValues; + result.factor = (result.colorMap.length - (stepped ? 0 : 1)) / range; + /* Scale values */ + for (i = 0; i < result.value.length; i += 1) { + val = result.value[i]; + if (rangeValues && val >= result.minValue && val <= result.maxValue) { + for (k = 1; k < rangeValues.length; k += 1) { + if (val <= rangeValues[k]) { + result.value[i] = k - 1 + (val - rangeValues[k - 1]) / + (rangeValues[k] - rangeValues[k - 1]); + break; + } + } + } else { + result.value[i] = (val - result.minValue) * result.factor; + } + } + return result; +} + +module.exports = { + createColoredMesh: createColoredMesh +}; diff --git a/src/webgl/contourFeature.js b/src/webgl/contourFeature.js index 237035b729..392adc3524 100644 --- a/src/webgl/contourFeature.js +++ b/src/webgl/contourFeature.js @@ -8,6 +8,7 @@ var contourFeature = require('../contourFeature'); * @class * @alias geo.webgl.contourFeature * @extends geo.contourFeature + * @extends geo.webgl.meshColored * @param {geo.contourFeature.spec} arg * @returns {geo.webgl.contourFeature} */ @@ -20,237 +21,25 @@ var webgl_contourFeature = function (arg) { arg = arg || {}; contourFeature.call(this, arg); - var vgl = require('vgl'); - var transform = require('../transform'); - var util = require('../util'); - var object = require('./object'); - var fragmentShader = require('./contourFeature.frag'); - var vertexShader = require('./contourFeature.vert'); + var meshColored = require('./meshColored'); + meshColored.call(this, arg); - object.call(this); - - /** - * @private - */ - var m_this = this, - s_exit = this._exit, - m_textureUnit = 7, - m_actor = null, - m_mapper = null, - m_material = null, - m_texture = null, - m_minColorUniform = null, - m_maxColorUniform = null, - m_stepsUniform = null, - m_steppedUniform = null, - m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw, - m_origin, - m_modelViewUniform, - s_init = this._init, - s_update = this._update; - - function createVertexShader() { - var shader = new vgl.shader(vgl.GL.VERTEX_SHADER); - shader.setShaderSource(vertexShader); - return shader; - } - - function createFragmentShader() { - var shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER); - shader.setShaderSource(fragmentShader); - return shader; - } - - /* Create the contours. This calls the base class to generate the geometry, - * color map, and other parameters. The generated geometry is then loaded - * into the various gl uniforms and buffers. - */ - function createGLContours() { - var contour = m_this._createContours(), - numPts = contour.elements.length, - colorTable = [], - i, i3, j, j3, - posBuf, opacityBuf, valueBuf, indicesBuf, - geom = m_mapper.geometryData(); - - m_minColorUniform.set([ - contour.minColor.r, - contour.minColor.g, - contour.minColor.b, - contour.minColor.a]); - m_maxColorUniform.set([ - contour.maxColor.r, - contour.maxColor.g, - contour.maxColor.b, - contour.maxColor.a]); - m_stepsUniform.set(contour.colorMap.length); - m_steppedUniform.set(contour.stepped); - // pad the colortable by repeating the end colors an extra time to ensure - // interpolation never goes off of the colormap. - for (i = -1; i < contour.colorMap.length + 1; i += 1) { - j = Math.max(0, Math.min(contour.colorMap.length - 1, i)); - colorTable.push(contour.colorMap[j].r * 255); - colorTable.push(contour.colorMap[j].g * 255); - colorTable.push(contour.colorMap[j].b * 255); - colorTable.push(contour.colorMap[j].a * 255); - } - m_texture.setColorTable(colorTable); - contour.pos = transform.transformCoordinates( - m_this.gcs(), m_this.layer().map().gcs(), contour.pos, 3); - m_origin = new Float32Array(m_this.style.get('origin')(contour.pos)); - if (m_origin[0] || m_origin[1] || m_origin[2]) { - for (i = 0; i < contour.pos.length; i += 3) { - contour.pos[i] -= m_origin[0]; - contour.pos[i + 1] -= m_origin[1]; - contour.pos[i + 2] -= m_origin[2]; - } - } - m_modelViewUniform.setOrigin(m_origin); - - posBuf = util.getGeomBuffer(geom, 'pos', numPts * 3); - opacityBuf = util.getGeomBuffer(geom, 'opacity', numPts); - valueBuf = util.getGeomBuffer(geom, 'value', numPts); - for (i = i3 = 0; i < numPts; i += 1, i3 += 3) { - j = contour.elements[i]; - j3 = j * 3; - posBuf[i3] = contour.pos[j3]; - posBuf[i3 + 1] = contour.pos[j3 + 1]; - posBuf[i3 + 2] = contour.pos[j3 + 2]; - opacityBuf[i] = contour.opacity[j]; - valueBuf[i] = contour.value[j]; - } - indicesBuf = geom.primitive(0).indices(); - if (!(indicesBuf instanceof Uint16Array) || indicesBuf.length !== numPts) { - indicesBuf = new Uint16Array(numPts); - geom.primitive(0).setIndices(indicesBuf); - } - geom.boundsDirty(true); - m_mapper.modified(); - m_mapper.boundsDirtyTimestamp().modified(); - } - - /** - * Initialize. - * - * @param {geo.contourFeature.spec} arg The contour feature specification. - */ - this._init = function (arg) { - var blend = vgl.blend(), - prog = vgl.shaderProgram(), - mat = vgl.material(), - tex = vgl.lookupTable(), - geom = vgl.geometryData(), - projectionUniform = new vgl.projectionUniform('projectionMatrix'), - samplerUniform = new vgl.uniform(vgl.GL.INT, 'sampler2d'), - vertexShader = createVertexShader(), - fragmentShader = createFragmentShader(), - posAttr = vgl.vertexAttribute('pos'), - valueAttr = vgl.vertexAttribute('value'), - opacityAttr = vgl.vertexAttribute('opacity'), - sourcePositions = vgl.sourceDataP3fv({'name': 'pos'}), - sourceValues = vgl.sourceDataAnyfv( - 1, vgl.vertexAttributeKeysIndexed.One, {'name': 'value'}), - sourceOpacity = vgl.sourceDataAnyfv( - 1, vgl.vertexAttributeKeysIndexed.Two, {'name': 'opacity'}), - primitive = new vgl.triangles(); - m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix'); - - s_init.call(m_this, arg); - m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw}); - - prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position); - prog.addVertexAttribute(valueAttr, vgl.vertexAttributeKeysIndexed.One); - prog.addVertexAttribute(opacityAttr, vgl.vertexAttributeKeysIndexed.Two); - - prog.addUniform(m_modelViewUniform); - prog.addUniform(projectionUniform); - m_minColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'minColor'); - prog.addUniform(m_minColorUniform); - m_maxColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'maxColor'); - prog.addUniform(m_maxColorUniform); - /* steps is always an integer, but it is more efficient if we use a float - */ - m_stepsUniform = new vgl.uniform(vgl.GL.FLOAT, 'steps'); - prog.addUniform(m_stepsUniform); - m_steppedUniform = new vgl.uniform(vgl.GL.BOOL, 'stepped'); - prog.addUniform(m_steppedUniform); - - prog.addShader(fragmentShader); - prog.addShader(vertexShader); - - prog.addUniform(samplerUniform); - tex.setTextureUnit(m_textureUnit); - samplerUniform.set(m_textureUnit); - - m_material = mat; - m_material.addAttribute(prog); - m_material.addAttribute(blend); - m_texture = tex; - m_material.addAttribute(m_texture); - - m_actor = vgl.actor(); - m_actor.setMaterial(m_material); - m_actor.setMapper(m_mapper); - - geom.addSource(sourcePositions); - geom.addSource(sourceValues); - geom.addSource(sourceOpacity); - geom.addPrimitive(primitive); - /* We don't need vgl to compute bounds, so make the geo.computeBounds just - * set them to 0. */ - geom.computeBounds = function () { - geom.setBounds(0, 0, 0, 0, 0, 0); - }; - m_mapper.setGeometryData(geom); - }; - - /** - * List vgl actors. - * - * @returns {vgl.actor[]} The list of actors. - */ - this.actors = function () { - return m_actor ? [m_actor] : []; - }; + var m_this = this; /** * Build. */ this._build = function () { - if (m_actor) { - m_this.renderer().contextRenderer().removeActor(m_actor); + if (m_this.actors()[0]) { + m_this.renderer().contextRenderer().removeActor(m_this.actors()[0]); } - createGLContours(); + m_this.createGLMeshColored(m_this._createContours()); - m_this.renderer().contextRenderer().addActor(m_actor); + m_this.renderer().contextRenderer().addActor(m_this.actors()[0]); m_this.buildTime().modified(); }; - /** - * Update. - */ - this._update = function () { - s_update.call(m_this); - - if (m_this.dataTime().timestamp() >= m_this.buildTime().timestamp() || - m_this.updateTime().timestamp() <= m_this.timestamp()) { - m_this._build(); - } - - m_actor.setVisible(m_this.visible()); - m_actor.material().setBinNumber(m_this.bin()); - m_this.updateTime().modified(); - }; - - /** - * Destroy. - */ - this._exit = function () { - m_this.renderer().contextRenderer().removeActor(m_actor); - s_exit(); - }; - this._init(arg); return this; }; diff --git a/src/webgl/gridFeature.js b/src/webgl/gridFeature.js new file mode 100644 index 0000000000..e4f2dfd6c8 --- /dev/null +++ b/src/webgl/gridFeature.js @@ -0,0 +1,52 @@ +var inherit = require('../inherit'); +var registerFeature = require('../registry').registerFeature; +var gridFeature = require('../gridFeature'); + +/** + * Create a new instance of gridFeature. + * + * @class + * @alias geo.webgl.gridFeature + * @extends geo.gridFeature + * @extends geo.webgl.meshColored + * @param {geo.gridFeature.spec} arg + * @returns {geo.webgl.gridFeature} + */ +var webgl_gridFeature = function (arg) { + 'use strict'; + + if (!(this instanceof webgl_gridFeature)) { + return new webgl_gridFeature(arg); + } + arg = arg || {}; + gridFeature.call(this, arg); + + var meshColored = require('./meshColored'); + meshColored.call(this, arg); + + var m_this = this; + + /** + * Build. + */ + this._build = function () { + if (m_this.actors()[0]) { + m_this.renderer().contextRenderer().removeActor(m_this.actors()[0]); + } + + m_this.createGLMeshColored(m_this._createGrids()); + + m_this.renderer().contextRenderer().addActor(m_this.actors()[0]); + m_this.buildTime().modified(); + }; + + this._init(arg); + return this; +}; + +inherit(webgl_gridFeature, gridFeature); + +// Now register it +registerFeature('webgl', 'grid', webgl_gridFeature); + +module.exports = webgl_gridFeature; diff --git a/src/webgl/index.js b/src/webgl/index.js index 0d38e5b8b8..65d6a99412 100644 --- a/src/webgl/index.js +++ b/src/webgl/index.js @@ -4,10 +4,12 @@ module.exports = { choroplethFeature: require('./choroplethFeature'), contourFeature: require('./contourFeature'), + gridFeature: require('./gridFeature'), isolineFeature: require('./isolineFeature'), layer: require('./layer'), lineFeature: require('./lineFeature'), markerFeature: require('./markerFeature'), + meshColored: require('./meshColored'), pointFeature: require('./pointFeature'), polygonFeature: require('./polygonFeature'), quadFeature: require('./quadFeature'), diff --git a/src/webgl/contourFeature.frag b/src/webgl/meshColored.frag similarity index 100% rename from src/webgl/contourFeature.frag rename to src/webgl/meshColored.frag diff --git a/src/webgl/meshColored.js b/src/webgl/meshColored.js new file mode 100644 index 0000000000..d3480e8f49 --- /dev/null +++ b/src/webgl/meshColored.js @@ -0,0 +1,236 @@ +/** + * Create a new instance of meshColored. + * + * @class + * @alias geo.webgl.meshColored + * @param {geo.meshColored.spec} arg + * @returns {geo.webgl.meshColored} + */ +var webgl_meshColored = function (arg) { + 'use strict'; + + arg = arg || {}; + + var vgl = require('vgl'); + var transform = require('../transform'); + var util = require('../util'); + var fragmentShader = require('./meshColored.frag'); + var vertexShader = require('./meshColored.vert'); + + var object = require('./object'); + object.call(this); + + /** + * @private + */ + var m_this = this, + s_exit = this._exit, + m_textureUnit = 7, + m_actor = null, + m_mapper = null, + m_material = null, + m_texture = null, + m_minColorUniform = null, + m_maxColorUniform = null, + m_stepsUniform = null, + m_steppedUniform = null, + m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw, + m_origin, + m_modelViewUniform, + s_init = this._init, + s_update = this._update; + + function createVertexShader() { + var shader = new vgl.shader(vgl.GL.VERTEX_SHADER); + shader.setShaderSource(vertexShader); + return shader; + } + + function createFragmentShader() { + var shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER); + shader.setShaderSource(fragmentShader); + return shader; + } + + /** + * Create the colored mesh. The generated geometry is loaded into the + * various gl uniforms and buffers. + * + * @param {geo.meshFeature.meshColoredInfo} mesh The mesh to draw. + */ + this.createGLMeshColored = function (mesh) { + var numPts = mesh.elements.length, + colorTable = [], + i, i3, j, j3, e, + posBuf, opacityBuf, valueBuf, indicesBuf, + geom = m_mapper.geometryData(); + + m_minColorUniform.set([ + mesh.minColor.r, + mesh.minColor.g, + mesh.minColor.b, + mesh.minColor.a]); + m_maxColorUniform.set([ + mesh.maxColor.r, + mesh.maxColor.g, + mesh.maxColor.b, + mesh.maxColor.a]); + m_stepsUniform.set(mesh.colorMap.length); + m_steppedUniform.set(mesh.stepped); + // pad the colortable by repeating the end colors an extra time to ensure + // interpolation never goes off of the colormap. + for (i = -1; i < mesh.colorMap.length + 1; i += 1) { + j = Math.max(0, Math.min(mesh.colorMap.length - 1, i)); + colorTable.push(mesh.colorMap[j].r * 255); + colorTable.push(mesh.colorMap[j].g * 255); + colorTable.push(mesh.colorMap[j].b * 255); + colorTable.push(mesh.colorMap[j].a * 255); + } + m_texture.setColorTable(colorTable); + mesh.pos = transform.transformCoordinates( + m_this.gcs(), m_this.layer().map().gcs(), mesh.pos, 3); + m_origin = new Float32Array(m_this.style.get('origin')(mesh.pos)); + if (m_origin[0] || m_origin[1] || m_origin[2]) { + for (i = 0; i < mesh.pos.length; i += 3) { + mesh.pos[i] -= m_origin[0]; + mesh.pos[i + 1] -= m_origin[1]; + mesh.pos[i + 2] -= m_origin[2]; + } + } + m_modelViewUniform.setOrigin(m_origin); + + posBuf = util.getGeomBuffer(geom, 'pos', numPts * 3); + opacityBuf = util.getGeomBuffer(geom, 'opacity', numPts); + valueBuf = util.getGeomBuffer(geom, 'value', numPts); + for (i = i3 = 0; i < numPts; i += 1, i3 += 3) { + j = mesh.elements[i]; + j3 = j * 3; + posBuf[i3] = mesh.pos[j3]; + posBuf[i3 + 1] = mesh.pos[j3 + 1]; + posBuf[i3 + 2] = mesh.pos[j3 + 2]; + e = mesh.elementValues ? Math.floor(i / 3) : j; + opacityBuf[i] = mesh.opacity[e]; + valueBuf[i] = mesh.value[e]; + } + indicesBuf = geom.primitive(0).indices(); + if (!(indicesBuf instanceof Uint16Array) || indicesBuf.length !== numPts) { + indicesBuf = new Uint16Array(numPts); + geom.primitive(0).setIndices(indicesBuf); + } + geom.boundsDirty(true); + m_mapper.modified(); + m_mapper.boundsDirtyTimestamp().modified(); + }; + + /** + * Initialize. + * + * @param {geo.meshColored.spec} arg The contour feature specification. + */ + this._init = function (arg) { + var blend = vgl.blend(), + prog = vgl.shaderProgram(), + mat = vgl.material(), + tex = vgl.lookupTable(), + geom = vgl.geometryData(), + projectionUniform = new vgl.projectionUniform('projectionMatrix'), + samplerUniform = new vgl.uniform(vgl.GL.INT, 'sampler2d'), + vertexShader = createVertexShader(), + fragmentShader = createFragmentShader(), + posAttr = vgl.vertexAttribute('pos'), + valueAttr = vgl.vertexAttribute('value'), + opacityAttr = vgl.vertexAttribute('opacity'), + sourcePositions = vgl.sourceDataP3fv({'name': 'pos'}), + sourceValues = vgl.sourceDataAnyfv( + 1, vgl.vertexAttributeKeysIndexed.One, {'name': 'value'}), + sourceOpacity = vgl.sourceDataAnyfv( + 1, vgl.vertexAttributeKeysIndexed.Two, {'name': 'opacity'}), + primitive = new vgl.triangles(); + m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix'); + + s_init.call(m_this, arg); + m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw}); + + prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position); + prog.addVertexAttribute(valueAttr, vgl.vertexAttributeKeysIndexed.One); + prog.addVertexAttribute(opacityAttr, vgl.vertexAttributeKeysIndexed.Two); + + prog.addUniform(m_modelViewUniform); + prog.addUniform(projectionUniform); + m_minColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'minColor'); + prog.addUniform(m_minColorUniform); + m_maxColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'maxColor'); + prog.addUniform(m_maxColorUniform); + /* steps is always an integer, but it is more efficient if we use a float + */ + m_stepsUniform = new vgl.uniform(vgl.GL.FLOAT, 'steps'); + prog.addUniform(m_stepsUniform); + m_steppedUniform = new vgl.uniform(vgl.GL.BOOL, 'stepped'); + prog.addUniform(m_steppedUniform); + + prog.addShader(fragmentShader); + prog.addShader(vertexShader); + + prog.addUniform(samplerUniform); + tex.setTextureUnit(m_textureUnit); + samplerUniform.set(m_textureUnit); + + m_material = mat; + m_material.addAttribute(prog); + m_material.addAttribute(blend); + m_texture = tex; + m_material.addAttribute(m_texture); + + m_actor = vgl.actor(); + m_actor.setMaterial(m_material); + m_actor.setMapper(m_mapper); + + geom.addSource(sourcePositions); + geom.addSource(sourceValues); + geom.addSource(sourceOpacity); + geom.addPrimitive(primitive); + /* We don't need vgl to compute bounds, so make the geo.computeBounds just + * set them to 0. */ + geom.computeBounds = function () { + geom.setBounds(0, 0, 0, 0, 0, 0); + }; + m_mapper.setGeometryData(geom); + }; + + /** + * List vgl actors. + * + * @returns {vgl.actor[]} The list of actors. + */ + this.actors = function () { + return m_actor ? [m_actor] : []; + }; + + /** + * Update. + */ + this._update = function () { + s_update.call(m_this); + + if (m_this.dataTime().timestamp() >= m_this.buildTime().timestamp() || + m_this.updateTime().timestamp() <= m_this.timestamp()) { + m_this._build(); + } + + m_actor.setVisible(m_this.visible()); + m_actor.material().setBinNumber(m_this.bin()); + m_this.updateTime().modified(); + }; + + /** + * Destroy. + */ + this._exit = function () { + m_this.renderer().contextRenderer().removeActor(m_actor); + s_exit(); + }; + + return this; +}; + +module.exports = webgl_meshColored; diff --git a/src/webgl/contourFeature.vert b/src/webgl/meshColored.vert similarity index 100% rename from src/webgl/contourFeature.vert rename to src/webgl/meshColored.vert diff --git a/tests/cases/contourFeature.js b/tests/cases/contourFeature.js index 351f651217..96083912c4 100644 --- a/tests/cases/contourFeature.js +++ b/tests/cases/contourFeature.js @@ -208,7 +208,8 @@ describe('Contour Feature', function () { dx: 6, dy: 6, rangeValues: [1, -1, 1, -1, 1, -1, 1, -1, 1, -1], - values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], + stepped: true }; var contour = layer.createFeature('contour', { contour: contour1, style: {value: 0}}).data(contour1.values); @@ -304,15 +305,15 @@ describe('Contour Feature', function () { y0: -30, dx: 6, dy: 6, - colorRange: ['red', 'black', 'blue'], - rangeValues: [0, 6, 15], + colorRange: ['red', 'black', 'green', 'blue'], + rangeValues: [0, 6, 12, 15], values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], stepped: false }; var contour = layer.createFeature('contour', { contour: contour1, style: {value: 0}}).data(contour1.values); var result = contour._createContours(); - expect(result.rangeValues.length).toBe(3); + expect(result.rangeValues.length).toBe(4); expect(result.factor).toBe(0.2); }); }); diff --git a/tests/cases/gridFeature.js b/tests/cases/gridFeature.js new file mode 100644 index 0000000000..78ff924f4a --- /dev/null +++ b/tests/cases/gridFeature.js @@ -0,0 +1,131 @@ +describe('Grid Feature', function () { + 'use strict'; + + var map, layer; + var geo = require('../test-utils').geo; + var createMap = require('../test-utils').createMap; + var destroyMap = require('../test-utils').destroyMap; + var mockWebglRenderer = geo.util.mockWebglRenderer; + var restoreWebglRenderer = geo.util.restoreWebglRenderer; + + beforeEach(function () { + mockWebglRenderer(); + map = createMap({ + 'center': [0, 0], + 'zoom': 3 + }, {width: '500px', height: '300px'}); + layer = map.createLayer('feature', {'renderer': 'webgl'}); + }); + + afterEach(function () { + destroyMap(); + restoreWebglRenderer(); + }); + + describe('create', function () { + it('direct create', function () { + var grid = geo.gridFeature({layer: layer}); + expect(grid instanceof geo.gridFeature).toBe(true); + expect(grid instanceof geo.meshFeature).toBe(true); + var mesh = geo.meshFeature({layer: layer}); + expect(mesh instanceof geo.meshFeature).toBe(true); + }); + }); + + describe('Check public class methods', function () { + it('grid/mesh get and set', function () { + var grid = geo.gridFeature({layer: layer}); + expect(grid.grid().minColor).toEqual('black'); + expect(grid.mesh().minColor).toEqual('black'); + expect(grid.grid('minColor')).toEqual('black'); + expect(grid.grid.get('minColor')()).toEqual('black'); + expect(grid.grid.get().minColor()).toEqual('black'); + expect(grid.grid('minColor', 'white')).toBe(grid); + expect(grid.grid('minColor')).toEqual('white'); + expect(grid.grid({minColor: 'red'})).toBe(grid); + expect(grid.grid('minColor')).toEqual('red'); + }); + it('grid gridWidth and gridHeight', function () { + var grid = geo.gridFeature({layer: layer}); + grid.data(new Array(400)); + expect(grid.grid.get('gridWidth')()).toBe(20); + expect(grid.grid.get('gridHeight')()).toBe(20); + delete grid.grid().gridHeight; + grid.grid({gridWidth: 40}); + expect(grid.grid.get('gridWidth')()).toBe(40); + expect(grid.grid.get('gridHeight')()).toBe(10); + delete grid.grid().gridWidth; + grid.grid({gridHeight: 5}); + expect(grid.grid.get('gridWidth')()).toBe(80); + expect(grid.grid.get('gridHeight')()).toBe(5); + grid.data(new Array(200)); + expect(grid.grid.get('gridWidth')()).toBe(40); + expect(grid.grid.get('gridHeight')()).toBe(5); + }); + it('actors', function () { + var grid = geo.webgl.gridFeature({layer: layer}); + expect(grid.actors().length).toBe(1); + }); + }); + + describe('Create grids', function () { + it('simple', function () { + var grid1 = { + gridWidth: 8, + gridHeight: 3, + x0: -30, + y0: -30, + dx: 6, + dy: 6, + values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + }; + var grid = layer.createFeature('grid').data( + grid1.values).grid(grid1).style({ + value: function (d) { return d; }}); + var result = grid._createGrids(); + expect(result.minValue).toBe(0); + expect(result.maxValue).toBe(13); + expect(result.elements.length).toBe(84); /* 14 sq. * 2 tri. * 3 pts. */ + expect(result.pos.length).toBe(72); /* 24 distinct points * 3 coor. */ + }); + + it('empty values', function () { + var grid1 = { + gridWidth: 8, + gridHeight: 3, + x0: -30, + y0: -30, + dx: 6, + dy: 6, + values: [0, 1, null, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, null] + }; + var grid = layer.createFeature('grid', {grid: grid1}).data(grid1.values); + var result = grid._createGrids(); + expect(result.minValue).toBe(0); + expect(result.maxValue).toBe(12); + expect(result.elements.length).toBe(72); /* 12 sq. * 2 tri. * 3 pts. */ + expect(result.pos.length).toBe(69); /* 23 distinct points * 3 coor. */ + }); + + it('short data', function () { + var grid1 = { + gridWidth: 6, + gridHeight: 3, + x0: 30, + y0: -30, + dx: -60, + dy: 60, + values: [0, 1, 2, 3, 4, 5, 6, 7, 8] + }; + var grid = layer.createFeature('grid').data( + grid1.values).grid(grid1).style({ + value: function (d) { return d; }}); + var result = grid._createGrids(); + /* This will appear to have only two rows */ + expect(result.elements.length).toBe(42); /* 5 + 2 sq. * 2 tri. * 3 pts. */ + expect(result.pos.length).toBe(54); /* 12 + 6 distinct points * 3 coor. */ + expect(result.pos[48]).toBe(90); + expect(result.pos[51]).toBe(30); + }); + }); +}); diff --git a/tests/gl-cases/webglGrid.js b/tests/gl-cases/webglGrid.js new file mode 100644 index 0000000000..03d8de4659 --- /dev/null +++ b/tests/gl-cases/webglGrid.js @@ -0,0 +1,126 @@ +var $ = require('jquery'); + +describe('webglGrid', function () { + var imageTest = require('../image-test'); + var common = require('../test-common'); + + var myMap; + + beforeEach(function () { + imageTest.prepareImageTest(); + }); + + afterEach(function () { + myMap.exit(); + }); + + /** Test grids + * + * @param {string} imageName: name used for the image test. + * @param {object} opts: display options, including: + * url: the url to load. Defaults to oahu.json. + * range: one of false, true, 'nonlinear', or 'iso'. Default false. + * stepped: boolean, default true. + * @param {function} done: function to call when the test is complete. + */ + function testGrid(imageName, opts, done) { + var mapOptions = {center: {x: -157.965, y: 21.482}, zoom: 10}; + myMap = common.createOsmMap(mapOptions, {}, true); + + var layer = myMap.createLayer('feature', {renderer: 'webgl'}); + var url = '/data/' + (opts.url || 'oahu-dense.json'); + $.getJSON(url, {format: 'json'}).done(function (data) { + + var grid = layer.createFeature('grid') + .data(data.position || data.values) + .style({ + opacity: 0.75, + value: function (d) { return d > -9999 ? d : null; } + }) + .grid({ + gridWidth: data.gridWidth + 1, + gridHeight: data.gridHeight + 1, + min: 0, + x0: data.x0, + y0: data.y0, + dx: data.dx, + dy: data.dy + }); + if (opts.range) { + grid + .style({ + opacity: 1 + }) + .grid({ + minColor: 'blue', + minOpacity: 0.5, + maxColor: 'red', + maxOpacity: 0.5 + }); + switch (opts.range) { + case 'nonlinear': + grid + .grid({ + rangeValues: [0, 25, 50, 75, 100, 125, 250, 500, 750, 2000] + }); + break; + case 'iso': + grid + .grid({ + rangeValues: [100, 100, 200, 200, 300, 300, 400, 400, 500, 500], + opacityRange: [1, 0, 1, 0, 1, 0, 1, 0, 1], + minOpacity: 0, + maxOpacity: 0 + }); + break; + default: + grid + .grid({ + min: 100, + max: 500, + colorRange: [ + '#FF00FF', '#CC33CC', '#996699', + '#669966', '#33CC33', '#00FF00' + ], + opacityRange: [0.5, 0.6, 0.7, 0.8, 0.9, 1.0] + }); + break; + } + } + if (opts.stepped === false) { + grid + .grid({ + stepped: false + }); + } + myMap.draw(); + + imageTest.imageTest(imageName, null, 0.0015, done, myMap.onIdle, 5000, 2); + }); + } + + it('grids with options', function (done) { + // geo from x0, specified min-max, set color range, smooth + testGrid('webglGridOptions', { + url: 'oahu-dense.json', + range: true, + stepped: false + }, done); + }, 30000); + + it('grids with nonlinear range', function (done) { + // geo from x0, non-linear range + testGrid('webglGridRange', { + url: 'oahu-dense.json', + range: 'nonlinear' + }, done); + }, 30000); + + it('grids with iso range', function (done) { + // geo from x0, iso-like range + testGrid('webglGridIso', { + url: 'oahu-dense.json', + range: 'iso' + }, done); + }, 30000); +}); diff --git a/tutorials/grid/index.pug b/tutorials/grid/index.pug new file mode 100644 index 0000000000..3d0fb0540d --- /dev/null +++ b/tutorials/grid/index.pug @@ -0,0 +1,62 @@ +extends ../common/index.pug + +block mainTutorial + :markdown-it + # Tutorial - Grid Data + First, let's create our map and add a base map and a feature layer. + + +codeblock('javascript', 1). + var map = geo.map({ + node: '#map', + center: { x: -77, y: 43 }, + zoom: 10 + }); + map.createLayer('osm'); + var layer = map.createLayer('feature', {features: ['grid']}); + + :markdown-it + Create a grid feature with a small amount of data. The grid width and height are the number of vertices in the grid. The grid data is for the elements, so it will have one less row and one less column than the vertex information. + +codeblock('javascript', 2, 1, true). + var grid = layer.createFeature('grid', { + grid: { + gridWidth: 8, + gridHeight: 7, + x0: -77.35, + y0: 42.76, + dx: 0.1, + dy: 0.08, + stepped: false + } + }).data([ + 0, 1, 2, 3, 2, 1, 0, + 1, 2, 3, 4, 3, 2, 1, + 2, 3, 4, 5, 4, 3, 2, + 3, 4, 5, 6, 5, 4, 3, + 2, 3, 4, 5, 6, 5, 4, + 1, 2, 3, 4, 5, 6, 5 + ]).draw(); + + :markdown-it + Data can have missing values, which will result in gaps in the rendered grid. + +codeblock('javascript', 3, 2). + grid + .data([ + 0, 1, 2, 3, 2, 1, 0, + 1, 2, 3, 4, 3, 2, 1, + 2, 3, 4, 5, 4, undefined, 2, + 3, 4, 5, 6, 5, 4, 3, + 2, 3, 4, 5, 6, 5, 4, + null, 2, 3, 4, 5, 6, 5 + ]) + .draw(); + + :markdown-it + Similar to the contour feature, we can change the color range and whether colors are in discrete steps. + + +codeblock('javascript', 4, 3). + grid + .grid({ + colorRange: ['rgb(224,130,20)', 'rgb(254,224,182)', 'rgb(178,171,210)', 'rgb(84,39,136)'], + stepped: true, + }) + .draw(); diff --git a/tutorials/grid/thumb.jpg b/tutorials/grid/thumb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..68dfa634102087f93150600618d3195219efb9c2 GIT binary patch literal 85979 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

ClO+MKac%Psp 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 YUj9PUQLob?q)Z+~