
当使用DeckGL提供的图层还无法满足需求时(https://deck.gl/docs/api-reference/layers),可能就需要自定义图层了。在DeckGL中有常见的三种自定义图层的方式
为了理解每个 deck.gl Layer的子类都可以定义在其生命周期中的特定点调用的特定方法
[layer.updateState()](https://deck.gl/docs/api-reference/core/layer#updatestate)生命周期函数将会被调用layer.shouldUpdateState() 以确定图层是否需要更新。
layer.updateState() ,通常在这一阶段通过调用[state.attributeManager.invalidate](https://deck.gl/docs/api-reference/core/attribute-manager#invalidate) 来重新计算attribute 和调用model.setUniforms来更新uniforms 变量/默认情况下,当 props.data 更改时,所有attribute都会失效并重新计算[layer.draw()](https://deck.gl/docs/api-reference/core/layer#draw)函数在此阶段将被调用layer.getPickingInfo() 以生成有关已拾取内容的信息对象。然后将此对象传递给图层的 onHover 或 onClick 回调。复合层是一种特殊的图层,可以通过组合现有层来构建新的层(顾名思义:通过组合多个层来构成一个层)如GeoJsonLayer就是复合层
复合层中分为适配层Adaptor Layers和集合层Collection Layers
定义一个继承于CompositeLayer的layer
使用复合“适配”层更改现有层的接口和行为通常很方便,而不是修改层本身(例如 S2Layer 是 PolygonLayer 之上的简单适配层)
简单的说也就是,根据现有层添加一些适配(如数据)
对于复合层主要的意义我估计就是使用相同的数据,来给原来的多个层使用渲染,然后组合为一个层
应用举例:
通常,一些复杂的可视化层是由使用一组具有通用属性的多个层合成的。例如:
创建一个集合层的优势:
例如:实现一个有icon和text的layer(通过组合IconLayer和TextLayer)
import {CompositeLayer, IconLayer, TextLayer} from 'deck.gl';
class LabeledIconLayer extends CompositeLayer {
//一个组合图层需要实现renderLayers()方法并返回一个子图层数组
renderLayers() {
return [
new IconLayer({
//由于复合层不直接绘制到画布上,它通过设置其子层的props来控制渲染结果,而且子图层并不知道复合图层的,所以需要将复合图层的props映射到子图层相应的props
id: `${this.props.id}-icon`,
data: this.props.data,
iconAtlas: this.props.iconAtlas,
iconMapping: this.props.iconMapping,
getPosition: this.props.getPosition,
getIcon: this.props.getIcon,
getSize: this.props.getIconSize,
getColor: this.props.getIconColor
//为了在需要重新计算访问器时使 updateTriggers 起作用
updateTriggers: {
getPosition: this.props.updateTriggers.getPosition,
getIcon: this.props.updateTriggers.getIcon,
getSize: this.props.updateTriggers.getIconSize,
getColor: this.props.updateTriggers.getIconColor
}
}),
//由于复合层不直接绘制到画布上,它通过设置其子层的 props 来控制渲染结果
new TextLayer({
id: `${this.props.id}-label`,
data: this.props.data,
fontFamily: this.props.fontFamily,
fontWeight: this.props.fontWeight,
getPosition: this.props.getPosition,
getText: this.props.getText,
getSize: this.props.getTextSize
getColor: this.props.getTextColor
//为了在需要重新计算访问器时使 updateTriggers 起作用
updateTriggers: {
getPosition: this.props.updateTriggers.getPosition,
getText: this.props.updateTriggers.getText,
getSize: this.props.updateTriggers.getTextSize,
getColor: this.props.updateTriggers.getTextColor
}
})
];
}
}
LabeledIconLayer.layerName = 'LabeledIconLayer';
//定义组合图层属性
LabeledIconLayer.defaultProps = {
// Shared accessors
getPosition: {type: 'accessor', value: x => x.position},
// Icon properties
iconAtlas: null,
iconMapping: {type: 'object', value: {}, async: true},
// Icon accessors
getIcon: {type: 'accessor', value: x => x.icon},
getIconSize: {type: 'accessor', value: 20},
getIconColor: {type: 'accessor', value: [0, 0, 0, 255]},
// Text properties
fontFamily: DEFAULT_FONT_FAMILY,
fontWeight: DEFAULT_FONT_WEIGHT,
// Text accessors
getText: {type: 'accessor', value: x => x.text},
getTextSize: {type: 'accessor', value: 12}
getTextColor: {type: 'accessor', value: [0, 0, 0, 255]}
}
子层 id 必须根据其父层的 id 动态生成,否则当有多个 LabeledIconLayers 实例时,它们的子层 id 将发生冲突。
例如还可以实现labeldGeoJsonLayer
构建复合层的通用步骤为(和SubClass差不多)
api(继承你要使用的图层的defaultProps,然后自定义添加一些而外的props)renderLayers()渲染逻辑,因为CompositeLayer并不是开箱即用的层,而是需要使用renderLayers()来组合其他层的渲染逻辑Layer要求的数据格式是不一样的),那么就可以需要使用CompositeLayer的数据解析逻辑“适配”你的数据格式,使用CompositeLayer的另外的好处是它允许多个子层共享同一个数据集,这就大程度的提升了数据传输性能。
updateState()来处理当数据发送变化时,在该函数下对数据进行处理,并会自动将处理结果存储在复合层的状态中。这样它也可以被子层访问。例如根据geoJsonLayer定义一个CompositeLayer(给几何添加一些文本标注)
const {CompositeLayer, GeoJsonLayer, TextLayer} = deck;
//定义面向用户的prop API
const defaultProps = {
//继承GeoJsonLayer props API
...GeoJsonLayer.defaultProps,
//获取每个feature的label
getLabel: {type: 'accessor', value: x => x.text},
// 每个feature的label的大小
getLabelSize: {type: 'accessor', value: 32},
// 每个feature的label的颜色
getLabelColor: {type: 'accessor', value: [0, 0, 0, 255]},
// label始终面向相机
billboard: true,
// label大小单位
labelSizeUnits: 'pixels',
// Label background color
labelBackground: {type: 'color', value: null, optional: true},
// Label font
fontFamily: 'Monaco, monospace'
}
class LabeledGeoJsonLayer extends CompositeLayer {
//图层的生命周期函数,图层在初始化时被调用,对geojson数据做处理,提取取label文本数据
updateState({changeFlags}) {
const {data} = this.props;
if (changeFlags.dataChanged && data) {
const labelData = (data.features || data)
.flatMap((feature, index) => {
const labelAnchors = getLabelAnchors(feature);
return labelAnchors.map(p => this.getSubLayerRow({position: p}, feature, index));
});
this.setState({labelData});
}
}
//渲染逻辑(使用同一个数据将GeoJsonLayer和TextLayer的渲染逻辑组合到一起)
renderLayers() {
const {
getLabel,
getLabelSize,
getLabelColor,
labelSizeUnits,
labelBackground,
billboard,
fontFamily
} = this.props;
return [
new GeoJsonLayer(this.props, this.getSubLayerProps({id: 'geojson'}), {
data: this.props.data
}),
new TextLayer(this.getSubLayerProps({id: 'text'}), {
data: this.state.labelData,
billboard,
sizeUnits: labelSizeUnits,
backgroundColor: labelBackground,
getPosition: d => d.position,
getText: this.getSubLayerAccessor(getLabel),
getSize: this.getSubLayerAccessor(getLabelSize),
getColor: this.getSubLayerAccessor(getLabelColor)
})
];
}
}
LabeledGeoJsonLayer.layerName = 'LabeledGeoJsonLayer';
LabeledGeoJsonLayer.defaultProps = defaultProps;
const layer = new LabeledGeoJsonLayer({
id: `countries-${Date.now()}`,
data,
filled: false,
billboard: false,
getLineColor: [180, 180, 180],
getLabel: f => f.properties.name,
getLabelSize: f => Math.pow(2, Math.log10(turf.area(f))) * 20,
getLabelColor: [0, 64, 128],
labelSizeUnits: 'meters',
lineWidthMinPixels: 1
});
deckgl.setProps({layers: [layer]});
deck.gl Layer被设计为易于扩展以添加功能。子类化允许重新定义层生命周期方法以及顶点和/或片段着色器。
扩展子类的示例:https://observablehq.com/d/ca5bcbd3d740693b
如果层中缺少一个小功能,子类化通常是添加它的好方法,常见的有:
通常写一个Subclassed Layers分为如下步骤:
Layer class from @deck.gl/core. By default, the layer does not do anything.AttributeManager来将要传递给attribute的数据上传到webgl缓冲区中(this.getAttributeManager())attribute的话,可以调用this.attributeManager.add()或this.attributeManager.addInstanced(),然后将其添加到Layer的生命周期initializeState()中uniform则可以通过this.state.model.setUniforms()来设定可以创建attribute变量,以供后面添加着色其代码时传输给着色其,在生命周期函数initializeState()中添加
export default class MultiColorPathLayer extends PathLayer {
initializeState() {
super.initializeState();
this.getAttributeManager().addInstanced({
instanceFrequency: {
size: 1,
accessor: 'getFrequency',
defaultValue: 1
},
})
}
}
着色器注入语句:
DECKGL_FILTER_SIZE(inout vec3 size, VertexGeometry geometry)DECKGL_FILTER_GL_POSITION(inout vec4 position, VertexGeometry geometry)额外的Uniforms传递给自定义着色器的最佳方法是重写 draw() 方法:
对于CompositeLayer通常采用添加图层扩展而不直接继承为子类图层去添加一个功能或效果,因为CompositeLayer通常是由几个图层组合而成的,所以在CompositeLayer继承进行添加自定义效果时,要确保在每个图层都生效就要通过对每个子图层进行子类扩展并添加相同的内容,这样就会比较麻且代码重复了,而图层扩展直接给CompositeLayer图层添加一个扩展变可以对其CompositeLayer的所有组成Layer都生效了。
有时我们需要对多个层进行子类化以添加类似的功能。层扩展是一种概括、重用和共享子类层代码的方法
直接扩展Layer基类来自定义一个layer
着色器注入语句:
**vs:#decl****:**向顶点着色器顶部(声明)注入代码**vs:#main-start****:**向顶点着色器主函数开始的地方注入代码**vs:#main-end****:**向顶点着色器主函数结束的地方注入代码**vs:DeckGL_FILTER_SIZE****:**顶点着色器中的一个函数,用于操纵几何体的大小,在投影计算前注入
DECKGL_FILTER_SIZE(inout vec3 size, VertexGeometry geometry)**vs:DeckGL_FILTER_GL_POSITION****:**向顶点着色器最终坐标计算上注入重写代码:顶点着色器中的一个函数,用于操作当前顶点的投影位置。投影后调用
DECKGL_FILTER_GL_POSITION(inout vec4 position, VertexGeometry geometry)**vs:DeckGL_FILTER_COLOR****:**向顶点着色器注入顶点颜色重写代码。**fs:#decl****:**向片元着色器注入声明代码。**fs:#main-start****:**向片元着色器主函数开始的地方注入代码。**fs:#main-end****:**向片元着色器主函数结束的地方注入代码。**fs:DeckGL_FILTER_COLOR****:**向片元着色器注入最终的颜色重写代码。此部分为对官方文档的翻译
把地理坐标中各种投影坐标系看做渲染引擎中的世界空间即可
数据集的世界坐标系。它通常由数据源决定,例如生成数据的设备,以及存储在磁盘或云数据仓库中的格式
为了将来自不同世界空间的数据正确地组合在一起,deck.gl 将它们转换为公共空间(Common space),一个统一的中间层 3D 空间,它是一个右手笛卡尔坐标系。一旦位置位于公共空间中,就可以安全地使用标准线性代数将它们添加、减去、旋转、缩放和拉伸为 3D 向量。这是 deck.gl 图层中所有几何处理的基础。
世界空间和公共空间之间的转换在 deck.gl 文档中称为“project”(世界空间到公共空间)和“unproject”(公共空间到世界空间),由世界空间规范(例如 WGS84)和投影方式(例如 Web Mercator)控制的过程。投影是作为 deck.gl 核心的一部分实现的
也就是像素坐标,对于给定的数据集,公共空间中的位置通常不会随着用户交互而改变,而它们在屏幕空间中的外观会随着用户平移、缩放和旋转相机而频繁变化
其源码结构类似于常见的常见的地图引擎,很多继承类的关系。
MapboxLayer其实是对mapbox中的customLayer的进一步封装,实例化其实就是customLayer图层
在DeckGL绘制地理数据时因为地理数据的坐标系是很多种类的,所以在实际绘制时需要先对地理坐标进行转换,需要将经纬度为单位的地理进行投影为米为单位的,然后在进行类似于常见渲染引擎中的变换,如观测变换、投影变换等。
而因为地理数据的地理坐标的坐标系的种类是非常多的,所以为了方便计算和提升性能,大多数WebGIS引擎(如DeckGL、openlayers、mapbox)都默认只支持一种地理坐标系和一种投影坐标系,也就是WGS84 WKID=4326和web墨卡托 WKID=3857。
所以如果要使用其他坐标系的地理数据(如地方坐标系)时,就比较麻烦了,可以先查看有没有相应的API提供,如没有则自己写或者调用网络上其他开源的工具(如proj)来对坐标进行装换计算了。
Layer 类是所有 deck.gl Layers的基类,它对许多Layers提供了一些基础属性
在DeckGL中MapboxLayer类位于源码目录中的modules/mapbox/src/mapbox-layers.ts中
//实现了MapBox的CustomLayerInterface接口
export default class MapboxLayer<LayerT extends Layer> implements CustomLayerInterface {
id: string;
type: 'custom';
renderingMode: '2d' | '3d';
map: Map | null;
deck: Deck | null;
props: MapboxLayerProps<LayerT>;
constructor(props: MapboxLayerProps<LayerT>) {
if (!props.id) {
throw new Error('Layer must have an unique id');
}
this.id = props.id;
this.type = 'custom';
this.renderingMode = props.renderingMode || '3d';
this.map = null;
this.deck = null;
this.props = props;
}
/* deck对onAdd方法的实现主要是创建deck实例,并添加设置一些props给deck实例,然后将 */
onAdd(map: Map, gl: WebGLRenderingContext): void {
获取mapbox的map实例,主要
this.map = map;
//这里通过Layer构建
this.deck = getDeckInstance({map, gl, deck: this.props.deck});
addLayer(this.deck, this);
}
onRemove(): void {
if (this.deck) {
removeLayer(this.deck, this);
}
}
setProps(props: MapboxLayerProps<LayerT>) {
// id cannot be changed
Object.assign(this.props, props, {id: this.id});
// safe guard in case setProps is called before onAdd
if (this.deck) {
updateLayer(this.deck, this);
}
}
render() {
drawLayer(this.deck!, this.map!, this);
}
}
因为上面代码是基于Mapbox 代码为
下面以MVTLayer为里继续深入阅读源码,以了解其渲染机制:
安装整个deckGL库
npm install deck.gl --save
选择性安装模块,直接安装DeckGL库的话其包含如下库
@deck.gl/core@deck.gl/layers@deck.gl/aggregation-layers - Advanced layers that aggregate data into alternative representations, e.g. heatmap, contour, hex bins, etc.@deck.gl/geo-layers - Additional layers that handle geospatial use cases and GIS formats.@deck.gl/mesh-layers - Additional layers that render 3D meshes and scene graphs.
@deck.gl/json - Declarative interface that supports specifying deck.gl layers and views using a JSON format.@deck.gl/mapbox - An integration with the Mapbox custom layer API.@deck.gl/react - React wrapper of deck.gl.@deck.gl/test-utils - Testing utilities.为了减小包的大小可以按需选择上面的包进行安装