Skip to content

Commit

Permalink
HeatmapLayer: Add tests and whats-new
Browse files Browse the repository at this point in the history
  • Loading branch information
1chandu committed Jul 27, 2019
1 parent 231960f commit f443050
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 15 deletions.
4 changes: 2 additions & 2 deletions docs/layers/heatmap-layer.md
Expand Up @@ -75,7 +75,7 @@ Inherits from all [Base Layer](/docs/api-reference/layer.md) and [CompositeLayer

* Default: `30`

Radius of the circle in pixels, to which weight of an object is distributed.
Radius of the circle in pixels, to which the weight of an object is distributed.

##### `colorRange` (Array, optional)

Expand All @@ -88,7 +88,7 @@ Specified as an array of 6 colors [color1, color2, ... color6]. Each color is an

* Default: `1`

Value that is multiplied with weight of every object to obtain the final weight.
Value that is multiplied with the weight of every object to obtain the final weight.

##### `enhanceFactor` (Number, optional)

Expand Down
20 changes: 20 additions & 0 deletions docs/whats-new.md
Expand Up @@ -2,6 +2,26 @@

This page contains highlights of each deck.gl release. Also check our [vis.gl blog](https://medium.com/vis-gl) for news about new releases and features in deck.gl.

## deck.gl v7.2

Release Date: Aug XX, 2019

<table style="border: 0;" align="center">
<tbody>
<tr>
<td>
<img style="max-height:200px" src="https://raw.github.com/uber-common/deck.gl-data/master/images/whats-new/heatmap-layer.gif" />
<p><i>HeatmapLayer</i></p>
</td>
</tr>
</tbody>
</table>

### HeatmapLayer

deck.gl's `aggregation-layers` module now offers `HeatmapLayer` as experimental layer. It performs density distribution on the GPU to provide fast dynamic heatmaps.


## deck.gl v7.1

Release Date: 2019
Expand Down
4 changes: 2 additions & 2 deletions modules/test-utils/src/lifecycle-test.js
Expand Up @@ -173,7 +173,7 @@ function runLayerTests(layerManager, deckRenderer, layer, testCases, spies, onEr
// Run successive update tests
for (let i = 0; i < testCases.length; i++) {
const testCase = testCases[i];
const {props, updateProps, onBeforeUpdate, onAfterUpdate} = testCase;
const {props, updateProps, onBeforeUpdate, onAfterUpdate, viewport = testViewport} = testCase;

spies = testCase.spies || spies;

Expand Down Expand Up @@ -204,7 +204,7 @@ function runLayerTests(layerManager, deckRenderer, layer, testCases, spies, onEr
`drawing ${layer.id}`,
() =>
deckRenderer.renderLayers({
viewports: [testViewport],
viewports: [viewport],
layers: layerManager.getLayers(),
activateViewport: layerManager.activateViewport
}),
Expand Down
12 changes: 2 additions & 10 deletions test/apps/heatmap-layer/app.js
Expand Up @@ -15,21 +15,14 @@ const DATA_URL =
const INITIAL_VIEW_STATE = {
longitude: -73.75,
latitude: 40.73,
zoom: 9.6,
zoom: 9,
maxZoom: 16,
pitch: 0,
bearing: 0
};

const MAP_STYLE = 'mapbox://styles/mapbox/dark-v9';
const colorRange = [
[33, 102, 172, 0],
[103, 169, 207],
[209, 229, 240],
[253, 219, 199],
[239, 138, 98],
[178, 24, 43]
];

class Root extends PureComponent {
constructor(props) {
super(props);
Expand All @@ -49,7 +42,6 @@ class Root extends PureComponent {
pickable: false,
getPosition: d => [d[0], d[1]],
getWeight: d => d[2],
colorRange,
enhanceFactor: 100
})
]}
Expand Down
1 change: 1 addition & 0 deletions test/browser.js
Expand Up @@ -49,6 +49,7 @@ test('deck.gl', t => {
require('./modules/main/bundle');
require('./modules/aggregation-layers/utils/gpu-grid-aggregator.spec');
require('./modules/aggregation-layers/utils/grid-aggregation-utils.spec');
require('./modules/aggregation-layers/heatmap-layer/heatmap-layer.spec');
require('./modules/core/lib/pick-layers.spec');

require('./render');
Expand Down
220 changes: 220 additions & 0 deletions test/modules/aggregation-layers/heatmap-layer/heatmap-layer.spec.js
@@ -0,0 +1,220 @@
// Copyright (c) 2015 - 2017 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, testInitializeLayer, generateLayerTests} from '@deck.gl/test-utils';
import {MapView} from '@deck.gl/core';
import {HeatmapLayer} from '@deck.gl/aggregation-layers';
import {default as TriangleLayer} from '@deck.gl/aggregation-layers/heatmap-layer/triangle-layer';

const getPosition = d => d.COORDINATES;

const viewport1 = new MapView().makeViewport({
width: 100,
height: 100,
viewState: {longitude: 0, latitude: 10, zoom: 10}
});

const viewport2_slightChange = new MapView().makeViewport({
width: 100,
height: 100,
viewState: {longitude: 0.01, latitude: 10, zoom: 10}
});

const viewport3_bigChange = new MapView().makeViewport({
width: 100,
height: 100,
viewState: {longitude: 10, latitude: 0, zoom: 10}
});

const viewport4_zoomChange = new MapView().makeViewport({
width: 100,
height: 100,
viewState: {longitude: 10, latitude: 0, zoom: 9}
});

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

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

t.end();
});

test('HeatmapLayer#renderSubLayer', t => {
const layer = new HeatmapLayer({
id: 'contourLayer',
data: FIXTURES.points,
getPosition
});

testInitializeLayer({layer, onError: t.notOk});

// render sublayer
const subLayer = layer.renderLayers();
testInitializeLayer({layer: subLayer, onError: t.notOk});

t.ok(subLayer instanceof TriangleLayer, 'Sublayer Triangle layer rendered');

t.end();
});

test('HeatmapLayer#updates', t => {
testLayer({
Layer: HeatmapLayer,
onError: t.notOk,
testCases: [
{
props: {
data: FIXTURES.points,
getPosition,
pickable: false
},
onAfterUpdate({layer}) {
const {worldBounds, commonBounds} = layer.state;

t.ok(worldBounds, 'should compute worldBounds');
t.ok(commonBounds, 'should compute commonBounds');
}
},
{
updateProps: {
colorRange: HeatmapLayer.defaultProps.colorRange.slice()
},
spies: ['_updateColorTexture', '_updateBounds'],
onAfterUpdate({layer, subLayers, spies}) {
t.ok(subLayers.length === 1, 'Sublayer rendered');

t.ok(spies._updateColorTexture.called, 'should update color texture');
t.notOk(
spies._updateBounds.called,
'viewport not changed, should not call _updateBounds'
);
spies._updateColorTexture.restore();
spies._updateBounds.restore();
}
},
{
viewport: viewport1, // viewport changed
spies: [
'_updateColorTexture',
'_updateBounds',
'_updateWeightmapAttributes',
'_updateWeightmap',
'_updateTextureRenderingBounds'
],
onAfterUpdate({layer, subLayers, spies}) {
const {zoom} = layer.state;
t.notOk(spies._updateColorTexture.called, 'should not update color texture');
t.ok(spies._updateBounds.called, 'viewport changed, should call _updateBounds');
t.notOk(
spies._updateWeightmapAttributes.called,
'data not changed, should not call _updateWeightmapAttributes'
);
t.ok(spies._updateWeightmap.called, 'boundsChanged changed, should _updateWeightmap');
t.ok(
spies._updateTextureRenderingBounds.called,
'vieport changed, should call _updateTextureRenderingBounds'
);
t.equal(zoom, viewport1.zoom, 'should update state.zoom');
spies._updateColorTexture.restore();
spies._updateBounds.restore();
spies._updateWeightmapAttributes.restore();
spies._updateWeightmap.restore();
spies._updateTextureRenderingBounds.restore();
}
},
{
viewport: viewport2_slightChange, // panned slightly, no zoom change
spies: ['_updateBounds', '_updateWeightmap', '_updateTextureRenderingBounds'],
onAfterUpdate({layer, subLayers, spies}) {
t.ok(spies._updateBounds.called, 'viewport changed slightly, should call _updateBounds');
t.notOk(
spies._updateWeightmap.called,
'viewport changed slightly, should not call _updateWeightmap'
);
t.ok(
spies._updateTextureRenderingBounds.called,
'viewport changed slightly, should call _updateTextureRenderingBounds'
);
spies._updateBounds.restore();
spies._updateWeightmap.restore();
spies._updateTextureRenderingBounds.restore();
}
},
{
viewport: viewport3_bigChange, // panned too far, no zoom change
spies: ['_updateBounds', '_updateWeightmap'],
onAfterUpdate({layer, subLayers, spies}) {
t.ok(spies._updateBounds.called, 'viewport panned too far, should call _updateBounds');
t.ok(
spies._updateWeightmap.called,
'viewport panned too far, should call _updateWeightmap'
);
spies._updateBounds.restore();
spies._updateWeightmap.restore();
}
},
{
viewport: viewport4_zoomChange, // only zoom change
spies: ['_updateBounds', '_updateWeightmap'],
onAfterUpdate({layer, subLayers, spies}) {
const {zoom} = layer.state;
t.ok(spies._updateBounds.called, 'viewport zoom changed, should call _updateBounds');
t.ok(
spies._updateWeightmap.called,
'viewport zoom changed, should call _updateWeightmap'
);
spies._updateBounds.restore();
spies._updateWeightmap.restore();
t.equal(
zoom,
viewport4_zoomChange.zoom,
'viewport zoom changed, should update state.zoom'
);
}
},
{
updateProps: {
radiusPixels: 2.123 // change from default value (30)
},
viewport: viewport4_zoomChange, // keep the same viewport
spies: ['_updateWeightmap'],
onAfterUpdate({layer, subLayers, spies}) {
t.ok(spies._updateWeightmap.called, 'should update weight map on uniform change');
spies._updateWeightmap.restore();
}
}
]
});

t.end();
});
Binary file added test/render/golden-images/heatmap-lnglat.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 26 additions & 1 deletion test/render/test-cases.js
Expand Up @@ -41,7 +41,8 @@ import {
ContourLayer,
ScreenGridLayer,
CPUGridLayer,
HexagonLayer
HexagonLayer,
HeatmapLayer
} from '@deck.gl/aggregation-layers';
import {H3HexagonLayer, H3ClusterLayer, S2Layer, TripsLayer} from '@deck.gl/geo-layers';

Expand Down Expand Up @@ -95,6 +96,18 @@ const HEXAGON_LAYER_INFO = {
}
};

const HEATMAP_LAYER_INFO = {
props: {
data: dataSamples.points,
pickable: false,
getPosition: d => d.COORDINATES,
radiusPixels: 35,
enhanceFactor: 25
},
goldenImage: './test/render/golden-images/heatmap-lnglat.png'
};
HEATMAP_LAYER_INFO.viewState = GRID_LAYER_INFO.viewsState;

function getMean(pts, key) {
const filtered = pts.filter(pt => Number.isFinite(pt[key]));

Expand Down Expand Up @@ -924,6 +937,18 @@ export const TEST_CASES = [
],
goldenImage: './test/render/golden-images/hexagon-lnglat.png'
},
{
name: 'heatmap-lnglat',
viewState: HEATMAP_LAYER_INFO.viewState,
layers: [
new HeatmapLayer(
Object.assign({}, HEATMAP_LAYER_INFO.props, {
id: 'heatmap-lnglat'
})
)
],
goldenImage: HEATMAP_LAYER_INFO.goldenImage
},
{
name: 'pointcloud-lnglat',
viewState: {
Expand Down

0 comments on commit f443050

Please sign in to comment.