基于WebGL的大数据二三维可视化--uber的deck.gl介绍

deck.gl是由uber开发并开源出来的基于WebGL的大数据量可视化框架。它具有提供不同类型可视化图层,GPU渲染的高性能,React和Mapbox GL集成,结合地理信息数据(GPS)的特点。下面我们就举两个例子探索一下这个神奇的库。

1.第一站:将源码的例子跑起来

源码在github里,首先克隆出来。

npm 下载,在项目根目录下

1
npm install

(友情提醒,如果npm下载速度比较慢,可以使用淘宝镜像,详细请自行搜索)

下面我们来到例子中,和上不一样在example文件夹下下载安装

1
npm install

安装成功之后,要申请mapbox的一个key。方法如下:
mapbox网站注册后进入https://www.mapbox.com/studio/account/tokens/,我们便可以在Default Public Token
找到我们需要的mapbox key

在例子文件夹下命令行下输入

1
export MAPBOX_ACCESS_TOKEN=<上面申请到的key>

注意:cmd会不认export,可以使用git命令行,或者直接在代码中加入

1
2
//const MAPBOX_TOKEN = process.env.MAPBOX_ACCESS_TOKEN; //改为
const MAPBOX_TOKEN = <上面申请到的key>

然后执行

1
npm run start

浏览器会自动打开,地址为localhost:3030

第一例子3d-heatmap,我们看到
对应官网上的例子http://uber.github.io/deck.gl/#/examples/core-layers/hexagon-layer

这是一个HexagonLayer的例子。
第二个例子是利用geojson
对应官网上的例子http://uber.github.io/deck.gl/#/examples/core-layers/geojson-layer

2.第二站:先拿一个例子看看

第一例子3d-heatmap,显示效果上面有给出。

源码路径

https://github.com/uber/deck.gl/tree/master/examples/3d-heatmap

deck.gl 是基于地理信息数据的,所以可视化很多都会分层两层,一层是地图数据底图,一层是可视化的数据。这个观念我们要记住,带进去看整个例子。对于这个例子组件MapGL是地里数据底图,组件DeckGLOverlay是可视化成的数据。

基本结构很见简单。

由于是使用React框架搭建,html很简单

  • index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html>
<head>
<meta charset='UTF-8' />
<title>deck.gl example</title>
<style>
body {margin: 0; padding: 0; overflow: hidden;}
</style>
</head>
<body>
<script src='bundle.js'></script>
</body>
</html>
  • deckgl-overlay.js

首先引入React和deckgl。

1
2
import React, {Component} from 'react';
import DeckGL, {HexagonLayer} from 'deck.gl';

几个变量先放出来,光线设置(LIGHT_SETTINGS),颜色范围设置(colorRange),高度显示范围(elevationScale),一些默认的属性(defaultProps)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const LIGHT_SETTINGS = {
lightsPosition: [-0.144528, 49.739968, 8000, -3.807751, 54.104682, 8000],
ambientRatio: 0.4,
diffuseRatio: 0.6,
specularRatio: 0.2,
lightsStrength: [0.8, 0.0, 0.8, 0.0],
numberOfLights: 2
};

const colorRange = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78]
];

const elevationScale = {min: 1, max: 50};

const defaultProps = {
radius: 1000,
upperPercentile: 100,
coverage: 1
};

我们建立叫DeckGLOverlay的组件

1
2
export default class DeckGLOverlay extends Component {
}

组件中提供了两个静态的属性,静态属性可以使用[类名].[方法名]调用,例如:DeckGLOverlay.defaultColorRange

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static get defaultColorRange() {//默认的颜色范围
return colorRange;
}

static get defaultViewport() {//默认的视图
return {
longitude: -1.4157267858730052,
latitude: 52.232395363869415,
zoom: 6.6,
minZoom: 5,
maxZoom: 15,
pitch: 40.5,
bearing: -27.396674584323023
};
}

constructor是构造函数,主要看的是_animateHeight对组件state的设置,这个是用来控制高度的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
constructor(props) {
super(props);
this.startAnimationTimer = null;
this.intervalTimer = null;
this.state = {
elevationScale: elevationScale.min
};

this._startAnimate = this._startAnimate.bind(this);
this._animateHeight = this._animateHeight.bind(this);

}

componentDidMount() {
this._animate();
}

componentWillReceiveProps(nextProps) {
if (nextProps.data.length !== this.props.data.length) {
this._animate();
}
}

componentWillUnmount() {
this._stopAnimate();
}

_animate() {
this._stopAnimate();

// wait 1.5 secs to start animation so that all data are loaded
this.startAnimationTimer = window.setTimeout(this._startAnimate, 1500);
}

_startAnimate() {
this.intervalTimer = window.setInterval(this._animateHeight, 20);
}

_stopAnimate() {
window.clearTimeout(this.startAnimationTimer);
window.clearTimeout(this.intervalTimer);
}

_animateHeight() {
if (this.state.elevationScale === elevationScale.max) {
this._stopAnimate();
} else {
this.setState({elevationScale: this.state.elevationScale + 1});
}
}

_initialize(gl) {
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
}

render渲染方法,layers是可视化渲染的图层定义变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
render() {
const {viewport, data, radius, coverage, upperPercentile} = this.props;

if (!data) {
return null;
}

const layers = [
new HexagonLayer({
id: 'heatmap',
colorRange,
coverage,
data,
elevationRange: [0, 3000],
elevationScale: this.state.elevationScale, //高度
extruded: true,
getPosition: d => d, //位置
lightSettings: LIGHT_SETTINGS,
onHover: this.props.onHover,
opacity: 1,
pickable: Boolean(this.props.onHover),
radius,
upperPercentile
})
];

return <DeckGL {...viewport} layers={layers} onWebGLInitialized={this._initialize} />;
}

  • app.js

引入:

1
2
3
4
5
6
import React, {Component} from 'react';
import {render} from 'react-dom';
import MapGL from 'react-map-gl';
import DeckGLOverlay from './deckgl-overlay.js';

import {csv as requestCsv} from 'd3-request';//加载csv文件

mapbox key:

1
const MAPBOX_TOKEN = process.env.MAPBOX_ACCESS_TOKEN;

组件Root
在构造函数中,我们将两个关键的变量viewport和data均放入到state中。这里的MapGL组件就是地理底图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Root extends Component {

constructor(props) {
super(props);
this.state = {
viewport: {
...DeckGLOverlay.defaultViewport,
width: 500,
height: 500
},
data: null
};
//请求csv数据
requestCsv('./data/heatmap-data.csv', (error, response) => {
if (!error) {
const data = response.map(d => ([Number(d.lng), Number(d.lat)]));
this.setState({data});
}
});
}

componentDidMount() {
window.addEventListener('resize', this._resize.bind(this));
this._resize();
}

_resize() {
this._onChangeViewport({
width: window.innerWidth,
height: window.innerHeight
});
}

_onChangeViewport(viewport) {
this.setState({
viewport: {...this.state.viewport, ...viewport}
});
}

render() {
const {viewport, data} = this.state;

return (
<MapGL
{...viewport}
mapStyle="mapbox://styles/mapbox/dark-v9"
perspectiveEnabled={true}
onChangeViewport={this._onChangeViewport.bind(this)}
mapboxApiAccessToken={MAPBOX_TOKEN}>
<DeckGLOverlay
viewport={viewport}
data={data || []}
/>
</MapGL>
);
}
}

渲染:

1
render(<Root />, document.body.appendChild(document.createElement('div')));

MapGL组件我们来修改下mapStyle来重新生成一张图。

1
mapStyle="mapbox://styles/mapbox/satellite-v9"

来看下显示效果,地图就变成了卫星影像。

mapbox提供了几款基础的样式,我们还可以自定义样式使用。

3.第三站:总结一下

通过上面我们就分析了一个简单的例子,使用Deck.gl,MapGL组件,利用两百多行代码就可以渲染14万多条数据,而且显示效果让我们惊叹,还支持交互,性能比较好,基于WebGL的大数据二三维可视化一个值得研究的方向。

参考阅读: