Skip to content

Commit

Permalink
Aggregation-layers: Add unit tests, fix bugs.
Browse files Browse the repository at this point in the history
  • Loading branch information
1chandu committed May 16, 2019
1 parent eb3a125 commit ebc257a
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 18 deletions.
19 changes: 15 additions & 4 deletions modules/aggregation-layers/src/grid-layer/grid-aggregator.js
Expand Up @@ -17,6 +17,9 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import {createIterable} from '@deck.gl/core';

const R_EARTH = 6378000;

/**
Expand All @@ -43,14 +46,17 @@ export function pointToDensityGridData(points, cellSize, getPosition) {
* @param {function} getPosition - position accessor
* @returns {object} - grid hash and cell dimension
*/
/* eslint-disable max-statements */
function _pointsToGridHashing(points = [], cellSize, getPosition) {
// find the geometric center of sample points
let latMin = Infinity;
let latMax = -Infinity;
let pLat;

for (const pt of points) {
pLat = getPosition(pt)[1];
const {iterable, objectInfo} = createIterable(points);
for (const pt of iterable) {
objectInfo.index++;
pLat = getPosition(pt, objectInfo)[1];
if (Number.isFinite(pLat)) {
latMin = pLat < latMin ? pLat : latMin;
latMax = pLat > latMax ? pLat : latMax;
Expand All @@ -67,8 +73,12 @@ function _pointsToGridHashing(points = [], cellSize, getPosition) {

// calculate count per cell
const gridHash = {};
for (const pt of points) {
const [lng, lat] = getPosition(pt);

// Iterating over again, reset index
objectInfo.index = -1;
for (const pt of iterable) {
objectInfo.index++;
const [lng, lat] = getPosition(pt, objectInfo);

if (Number.isFinite(lat) && Number.isFinite(lng)) {
const latIdx = Math.floor((lat + 90) / gridOffset.yOffset);
Expand All @@ -83,6 +93,7 @@ function _pointsToGridHashing(points = [], cellSize, getPosition) {

return {gridHash, gridOffset};
}
/* eslint-enable max-statements */

function _getGridLayerDataFromGridHash(gridHash, gridOffset) {
return Object.keys(gridHash).reduce((accu, key, i) => {
Expand Down
Expand Up @@ -512,9 +512,9 @@ export default class GPUGridAggregator {
}
/* eslint-disable max-statements */

updateResultBuffer({gl, bufferName, id, data, result}) {
updateCPUResultBuffer({gl, bufferName, id, data, result}) {
const {resources} = this.state;
const resourceName = `${id}-${bufferName}`;
const resourceName = `cpu-result-${id}-${bufferName}`;
result[bufferName] = result[bufferName] || resources[resourceName];
if (result[bufferName]) {
result[bufferName].subData({data});
Expand All @@ -534,15 +534,15 @@ export default class GPUGridAggregator {
const {aggregationData, minData, maxData, maxMinData} = results[id];
const {needMin, needMax} = weights[id];
const combineMaxMin = needMin && needMax && weights[id].combineMaxMin;
this.updateResultBuffer({
this.updateCPUResultBuffer({
gl: this.gl,
bufferName: 'aggregationBuffer',
id,
data: aggregationData,
result: results[id]
});
if (combineMaxMin) {
this.updateResultBuffer({
this.updateCPUResultBuffer({
gl: this.gl,
bufferName: 'maxMinBuffer',
id,
Expand All @@ -551,7 +551,7 @@ export default class GPUGridAggregator {
});
} else {
if (needMin) {
this.updateResultBuffer({
this.updateCPUResultBuffer({
gl: this.gl,
bufferName: 'minBuffer',
id,
Expand All @@ -560,7 +560,7 @@ export default class GPUGridAggregator {
});
}
if (needMax) {
this.updateResultBuffer({
this.updateCPUResultBuffer({
gl: this.gl,
bufferName: 'maxBuffer',
id,
Expand Down Expand Up @@ -613,6 +613,7 @@ export default class GPUGridAggregator {
}
}
}
this.trackGPUResultBuffers(results, weights);
return results;
}

Expand Down Expand Up @@ -922,6 +923,27 @@ export default class GPUGridAggregator {
}
}

// GPU Aggregation results are provided in Buffers, if new Buffer objects are created track them for later deletion.
trackGPUResultBuffers(results, weights) {
const bufferNames = ['aggregationBuffer', 'maxMinBuffer', 'minBuffer', 'maxBuffer'];
const {resources} = this.state;
for (const id in results) {
if (results[id]) {
bufferNames.forEach(bufferName => {
if (results[id][bufferName] && weights[id][bufferName] !== results[id][bufferName]) {
// No result buffer is provided in weights object, `readPixelsToBuffer` has created a new Buffer object
// collect the new buffer for garabge collection
const name = `gpu-result-${id}-${bufferName}`;
if (resources[name]) {
resources[name].delete();
}
resources[name] = results[id][bufferName];
}
});
}
}
}

/* eslint-disable max-statements */
updateModels(opts) {
const {gl} = this;
Expand Down
Expand Up @@ -6,6 +6,10 @@ const {fp64LowPart} = fp64Utils;

const R_EARTH = 6378000;

function toFinite(n) {
return Number.isFinite(n) ? n : 0;
}

// Takes data and aggregation params and returns aggregated data.
export function pointToDensityGridData({
data,
Expand Down Expand Up @@ -131,7 +135,12 @@ function parseGridData(data, getPosition, weightParams) {
}
}

const boundingBox = {xMin, xMax, yMin, yMax};
const boundingBox = {
xMin: toFinite(xMin),
xMax: toFinite(xMax),
yMin: toFinite(yMin),
yMax: toFinite(yMax)
};
return {
positions,
positions64xyLow,
Expand Down
Expand Up @@ -22,13 +22,32 @@ import {makeSpy} from '@probe.gl/test-utils';

import * as FIXTURES from 'deck.gl-test/data';

import {testLayer, testInitializeLayer} from '@deck.gl/test-utils';
import {testLayer, testInitializeLayer, generateLayerTests} from '@deck.gl/test-utils';

import {LineLayer, SolidPolygonLayer} from '@deck.gl/layers';
import {ContourLayer} from '@deck.gl/aggregation-layers';

const getPosition = d => d.COORDINATES;

test('GridLayer', t => {
const testCases = generateLayerTests({
Layer: ContourLayer,
sampleProps: {
data: FIXTURES.points.slice(0, 3),
getPosition
},
assert: t.ok,
onBeforeUpdate: ({testCase}) => t.comment(testCase.title),
onAfterUpdate({layer}) {
t.ok(layer.state.countsData, 'should update state.countsData');
}
});

testLayer({Layer: ContourLayer, testCases, onError: t.notOk});

t.end();
});

test('ContourLayer#renderSubLayer', t => {
makeSpy(ContourLayer.prototype, '_onGetSublayerColor');

Expand Down
@@ -0,0 +1,53 @@
// Copyright (c) 2015 - 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import test from 'tape-catch';
import * as FIXTURES from 'deck.gl-test/data';
import {testLayer, generateLayerTests} from '@deck.gl/test-utils';
import {_GPUGridLayer as GPUGridLayer} from '@deck.gl/aggregation-layers';
import {gl} from '@deck.gl/test-utils';
import {isWebGL2} from '@luma.gl/core';

const getPosition = d => d.COORDINATES;

test('GPUGridLayer', t => {
if (!isWebGL2(gl)) {
t.comment('GPUGridLayer not supported, skipping');
t.end();
return;
}
const sampleProps = {
data: FIXTURES.points.slice(0, 3),
getPosition
};
const testCases = generateLayerTests({
Layer: GPUGridLayer,
sampleProps,
assert: t.ok,
onBeforeUpdate: ({testCase}) => t.comment(testCase.title),
onAfterUpdate({layer}) {
t.ok(layer.state.weights, 'should update state.weights');
}
});

testLayer({Layer: GPUGridLayer, testCases, onError: t.notOk});

t.end();
});
Expand Up @@ -22,7 +22,7 @@ import {makeSpy} from '@probe.gl/test-utils';

import * as FIXTURES from 'deck.gl-test/data';

import {testLayer, testInitializeLayer} from '@deck.gl/test-utils';
import {testLayer, testInitializeLayer, generateLayerTests} from '@deck.gl/test-utils';

import {GridCellLayer} from '@deck.gl/layers';
import {GridLayer} from '@deck.gl/aggregation-layers';
Expand All @@ -31,6 +31,25 @@ const getColorValue = points => points.length;
const getElevationValue = points => points.length;
const getPosition = d => d.COORDINATES;

test('GridLayer', t => {
const testCases = generateLayerTests({
Layer: GridLayer,
sampleProps: {
data: FIXTURES.points.slice(0, 3),
getPosition
},
assert: t.ok,
onBeforeUpdate: ({testCase}) => t.comment(testCase.title),
onAfterUpdate({layer}) {
t.ok(layer.state.layerData, 'should update state.layerData');
}
});

testLayer({Layer: GridLayer, testCases, onError: t.notOk});

t.end();
});

test('GridLayer#renderSubLayer', t => {
makeSpy(GridLayer.prototype, '_onGetSublayerColor');
makeSpy(GridLayer.prototype, '_onGetSublayerElevation');
Expand Down
23 changes: 22 additions & 1 deletion test/modules/aggregation-layers/hexagon-layer.spec.js
Expand Up @@ -22,7 +22,7 @@ import test from 'tape-catch';
import {makeSpy} from '@probe.gl/test-utils';

import * as data from 'deck.gl-test/data';
import {testLayer, testInitializeLayer} from '@deck.gl/test-utils';
import {testLayer, testInitializeLayer, generateLayerTests} from '@deck.gl/test-utils';

import {ColumnLayer} from '@deck.gl/layers';
import {HexagonLayer} from '@deck.gl/aggregation-layers';
Expand All @@ -31,6 +31,27 @@ const getColorValue = points => points.length;
const getElevationValue = points => points.length;
const getPosition = d => d.COORDINATES;

test('HexagonLayer', t => {
const testCases = generateLayerTests({
Layer: HexagonLayer,
sampleProps: {
data: data.points.slice(0, 3),
getPosition
},
assert: t.ok,
onBeforeUpdate: ({testCase}) => t.comment(testCase.title),
onAfterUpdate({layer}) {
if (layer.props.data && layer.props.data.length) {
t.ok(layer.state.hexagons.length > 0, 'should update state.hexagons');
}
}
});

testLayer({Layer: HexagonLayer, testCases, onError: t.notOk});

t.end();
});

// props to initialize layer with
// update props
// asserts on the resulting layer
Expand Down
10 changes: 6 additions & 4 deletions test/modules/aggregation-layers/index.js
Expand Up @@ -18,11 +18,13 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import './grid-layer.spec';
import './contour-layer/contour-layer.spec';
import './contour-layer/marching-squares.spec';
import './gpu-grid-layer/gpu-grid-cell-layer-vertex.spec';
import './gpu-grid-layer/gpu-grid-layer.spec';
import './grid-layer/grid-layer.spec';
import './grid-aggregator.spec';
import './hexagon-layer.spec';
import './hexagon-aggregator.spec';
import './contour-layer/marching-squares.spec';
import './contour-layer/contour-layer.spec';
import './screen-grid-layer.spec';
import './utils/scale-utils.spec';
import './gpu-grid-layer/gpu-grid-cell-layer-vertex.spec';
45 changes: 45 additions & 0 deletions test/modules/aggregation-layers/screen-grid-layer.spec.js
@@ -0,0 +1,45 @@
// Copyright (c) 2015 - 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import test from 'tape-catch';
import * as FIXTURES from 'deck.gl-test/data';
import {testLayer, generateLayerTests} from '@deck.gl/test-utils';
import {ScreenGridLayer} from '@deck.gl/aggregation-layers';

const getPosition = d => d.COORDINATES;

test('ScreenGridLayer', t => {
const testCases = generateLayerTests({
Layer: ScreenGridLayer,
sampleProps: {
data: FIXTURES.points.slice(0, 3),
getPosition
},
assert: t.ok,
onBeforeUpdate: ({testCase}) => t.comment(testCase.title),
onAfterUpdate({layer}) {
t.ok(layer.state.aggregationResults !== null, 'should update state.aggregationResults');
}
});

testLayer({Layer: ScreenGridLayer, testCases, onError: t.notOk});

t.end();
});

0 comments on commit ebc257a

Please sign in to comment.