ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

deck.gl ScreenGridLayer 完全指南:屏幕空间网格聚合图层的原理、参数与实战

deck.gl ScreenGridLayer 完全指南:屏幕空间网格聚合图层的原理、参数与实战 deck.gl ScreenGridLayer 完全指南屏幕空间网格聚合图层的原理、参数与实战【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.glScreenGridLayer 是 deck.gl aggregation-layers 模块提供的屏幕空间聚合图层它将输入数据点投影到屏幕坐标系后按固定像素尺寸划分直方图网格bin对落入每个网格的数据对象权重执行求和、均值等聚合运算并以彩色网格叠加在地图上。本文以官方 API 文档 docs/api-reference/aggregation-layers/screen-grid-layer.md 为主体骨架结合仓库源码与测试完整讲解其安装、全部配置参数、拾取交互以及 CPU/GPU 聚合的底层实现读完后你可以在自己的地图应用中直接落地一个高性能的密度/强度热力网格图层。什么是 ScreenGridLayerScreenGridLayer的核心思路是把地理聚合转化为屏幕聚合数据在屏幕空间而非经纬度空间被聚合为直方图分箱histogram bins每个分箱即一个固定像素尺寸的网格单元cell单元宽度/高度由cellSizePixels决定所有落入同一网格的数据对象的权重按aggregation指定的运算SUM / MEAN / MIN / MAX / COUNT汇总为单元值单元值再通过colorDomaincolorRangecolorScaleType映射为颜色最终以叠加的彩色网格渲染出来。由于聚合发生在屏幕空间官方文档明确给出一个重要提示只要地图发生缩放或平移层就必须重新聚合数据详见 docs/api-reference/aggregation-layers/screen-grid-layer.md。这意味着该图层最适合中小规模数据集——配合合适的数据与配色其视觉效果可以非常出色常用于展示停车位分布、交通站点密度、事件热点等场景。安装与引入使用 npm 安装聚合图层依赖npm install deck.gl # 或按需拆分安装 npm install deck.gl/core deck.gl/layers deck.gl/aggregation-layersTypeScript 下同时引入类型定义import {ScreenGridLayer} from deck.gl/aggregation-layers; import type {ScreenGridLayerProps, ScreenGridLayerPickingInfo} from deck.gl/aggregation-layers; new ScreenGridLayerDataT(...props: ScreenGridLayerPropsDataT[]);使用预打包脚本pre-bundled scripts时script srchttps://unpkg.com/deck.gl^9.0.0/dist.min.js/script !-- 或 -- script srchttps://unpkg.com/deck.gl/core^9.0.0/dist.min.js/script script srchttps://unpkg.com/deck.gl/layers^9.0.0/dist.min.js/script script srchttps://unpkg.com/deck.gl/aggregation-layers^9.0.0/dist.min.js/scriptnew deck.ScreenGridLayer({});仓库中该图层的类定义位于 modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.tsstatic layerName ScreenGridLayer可直接在 JSON 配置如 playground中以type: ScreenGridLayer引用。快速上手JavaScript / TypeScript / React官方文档提供了三套等价的最小可运行示例数据为旧金山自行车停车位数据。JavaScript 版本import {Deck} from deck.gl/core; import {ScreenGridLayer} from deck.gl/aggregation-layers; const layer new ScreenGridLayer({ id: ScreenGridLayer, data: https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json, gpuAggregation: true, cellSizePixels: 50, colorRange: [ [0, 25, 0, 25], [0, 85, 0, 85], [0, 127, 0, 127], [0, 170, 0, 170], [0, 190, 0, 190], [0, 255, 0, 255] ], getPosition: d d.COORDINATES, getWeight: d d.SPACES, opacity: 0.8 }); new Deck({ initialViewState: { longitude: -122.4, latitude: 37.74, zoom: 11 }, controller: true, getTooltip: ({object}) object Count: ${object.value}, layers: [layer] });TypeScript 版本带数据对象类型与拾取信息类型import {Deck} from deck.gl/core; import {ScreenGridLayer, ScreenGridLayerPickingInfo} from deck.gl/aggregation-layers; type BikeRack { ADDRESS: string; SPACES: number; COORDINATES: [longitude: number, latitude: number]; }; const layer new ScreenGridLayerBikeRack({ id: ScreenGridLayer, data: https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json, gpuAggregation: true, cellSizePixels: 50, colorRange: [ [0, 25, 0, 25], [0, 85, 0, 85], [0, 127, 0, 127], [0, 170, 0, 170], [0, 190, 0, 190], [0, 255, 0, 255] ], getPosition: (d: BikeRack) d.COORDINATES, getWeight: (d: BikeRack) d.SPACES, opacity: 0.8 }); new Deck({ initialViewState: {longitude: -122.4, latitude: 37.74, zoom: 11}, controller: true, getTooltip: ({object}: ScreenGridLayerPickingInfoBikeRack) object Count: ${object.value}, layers: [layer] });React 版本基于deck.gl/react的DeckGL组件import React from react; import {DeckGL} from deck.gl/react; import {ScreenGridLayer, ScreenGridLayerPickingInfo} from deck.gl/aggregation-layers; type BikeRack { ADDRESS: string; SPACES: number; COORDINATES: [longitude: number, latitude: number]; }; function App() { const layer new ScreenGridLayerBikeRack({ id: ScreenGridLayer, data: https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json, gpuAggregation: true, cellSizePixels: 50, colorRange: [ [0, 25, 0, 25], [0, 85, 0, 85], [0, 127, 0, 127], [0, 170, 0, 170], [0, 190, 0, 190], [0, 255, 0, 255] ], getPosition: (d: BikeRack) d.COORDINATES, getWeight: (d: BikeRack) d.SPACES, opacity: 0.8 }); return DeckGL initialViewState{{longitude: -122.4, latitude: 37.74, zoom: 11}} controller getTooltip{({object}: ScreenGridLayerPickingInfoBikeRack) object Count: ${object.value}} layers{[layer]} /; }ScreenGridLayer继承自聚合图层基类AggregationLayer继承链为ScreenGridLayer → AggregationLayer → CompositeLayer → Layer因此继承所有 Base Layer 属性id、data、opacity、visible、pickable、updateTriggers等详见 modules/aggregation-layers/src/common/aggregation-layer.ts。聚合选项Aggregation OptionsgpuAggregationboolean可选默认值true当设为true且浏览器支持时聚合在 GPU 上执行通过WebGLAggregator。源码 screen-grid-layer.ts 中的getAggregatorType()展示了真实的决策逻辑getAggregatorType(): string { return this.props.gpuAggregation WebGLAggregator.isSupported(this.context.device) ? gpu : cpu; }即在gpuAggregation: true且当前设备支持 WebGL 聚合时走 GPU 路径否则自动回退到CPUAggregator。在合适的场景下开启 GPU 聚合能显著加速应用但它对输入数据性质和所需功能各有取舍详见下文CPU 与 GPU 聚合的取舍以及 overview.md 的 CPU vs GPU Aggregation 章节。cellSizePixelsnumber可选支持过渡动画默认值100网格单元bin的像素宽/高。源码中该属性的类型定义为{type: number, value: 100, min: 1}即最小值被限制为 1 像素。设置过小会导致网格数量爆炸GPU 聚合时尤其明显见下文 binIdRange 推导设置过大则会丢失空间细节。aggregationstring可选默认值SUM定义把落入同一网格的所有数据对象权重汇总为单元值的运算。合法取值SUM单元格内所有点权重的总和MEAN单元格内所有点权重的均值MIN单元格内所有点权重的最小值MAX单元格内所有点权重的最大值COUNT落入单元格的点数量。getWeight与aggregation共同决定每个网格单元的值也即后续渲染的高度/强度依据。源码中该值直接作为operations传给聚合器aggregator.setProps({operations: [aggregation], ...})聚合类型AggregationOperation定义于 modules/aggregation-layers/src/common/aggregator/index.ts。渲染选项Render OptionscellMarginPixelsnumber可选支持过渡动画默认值2会被钳制在[0, 5]区间网格单元之间的间距像素。注意设置该属性不会影响数据如何分箱——它只影响渲染时每个单元格的实际绘制尺寸。源码 screen-grid-cell-layer.ts 中体现为const cellSize Math.max(gridSize - cellMarginPixels, 0);即最终绘制单元尺寸 cellSizePixels - cellMarginPixels下限 0再换算为裁剪空间尺寸cellSizeClipspace传给着色器。因此cellMarginPixels越大网格之间缝隙越明显视觉上越像砖格。colorScaleTypestring可选默认值linear颜色比例尺负责把连续的数值区间colorDomain映射为一组离散颜色colorRange。值为colorDomain[0]的单元格渲染为colorRange[0]的颜色值为colorDomain[1]的单元格渲染为colorRange最后一个颜色。支持的取值linear按值在colorDomain中的位置对colorRange做线性插值quantize把colorDomain等分为colorRange.length段每段映射到colorRange中的一个离散颜色。实现层面源码把colorRange与colorScaleType烘焙进一张 1D 纹理createColorRangeTexture并在顶点着色器里用texture(range, vec2(r, 0.5))采样取色linear对应线性过滤、quantize对应最近邻过滤参见 modules/aggregation-layers/src/common/utils/color-utils.ts 与 screen-grid-layer-vertex.glsl.ts。colorDomainnumber[2]可选默认值null自动若未提供图层在运行时把colorDomain设为所有网格单元的实际最小值与最大值源码中通过aggregator.getResultDomain(0)在draw()时刻求值colorDomain: () this.props.colorDomain || aggregator.getResultDomain(0)。显式提供colorDomain可以控制数值到颜色的映射关系适合希望用同一套配色渲染不同数据输入以便相互比较的场景。例如固定[0, 1000]后两个数据集的相同数值会呈现相同颜色。colorRangeColor[6]可选默认值colorbrewer 6 级顺序色板YlOrRd黄-橙-红由 6 个颜色组成的数组[color1, ..., color6]。每个颜色是 3 或 4 个值的数组[R, G, B]或[R, G, B, A]分别表示红、绿、蓝和透明度通道强度取值范围0~255省略 Alpha 时视为255。源码中默认色板定义于 color-utils.tsexport const defaultColorRange: Color[] [ [255, 255, 178], [254, 217, 118], [254, 178, 76], [253, 141, 60], [240, 59, 32], [189, 0, 38] ];展平时colorRangeToFlatArray缺失的 Alpha 通道统一补255。上面的示例代码使用了绿色系色板深绿 → 亮绿来呈现停车位数量与本图层的密度主题非常契合。数据访问器Data Accessors访问器的通用约定见 开发者指南Accessors。getPositionAccessorPosition可选默认值object object.position用于取回每个数据对象位置的函数返回经纬度坐标。源码中该属性被注册为positions属性size: 3、type: float64并在启用 64 位精度时使用fp64补偿见 screen-grid-layer.ts 的initializeState()。getWeightAccessornumber可选默认值1每个数据对象的权重传入数字时该数字作为所有对象的统一权重传入函数时函数在每个对象上被调用以取回其权重。该属性在 GPU 路径下注册为counts属性size: 1其值作为顶点着色器中的in float counts参与聚合。拾取Picking与交互ScreenGridLayer的 hover/click 事件返回的 PickingInfo.object 代表一个被聚合的网格单元而非单个数据点。官方文档与源码中的ScreenGridLayerPickingInfoDataT类型screen-grid-layer.ts定义了以下字段字段类型说明colnumber被拾取单元的列索引从视口最左侧的 0 开始rownumber被拾取单元的行索引从视口最顶部的 0 开始valuenumber聚合值由getWeight与aggregation共同决定countnumber落入该单元的数据点数量pointIndicesnumber[]落入该单元的数据对象索引数组仅 CPU 聚合时可用pointsobject[]落入该单元的数据对象数组仅 CPU 聚合且 layer data 为数组时可用源码getPickingInfo()的实现screen-grid-layer.ts展示了字段来源col bin.id[0]、row bin.id[1]、value bin.value[0]、count bin.count而pointIndices/points只有在bin.pointIndices存在即 CPU 聚合路径时才填充。前端示例中常见的getTooltip: ({object}) object \Count: ${object.value}正是利用value 字段。底层原理CPU 与 GPU 两条聚合路径从源码结构看ScreenGridLayer是一个复合图层它先用聚合器Aggregator把原始数据折叠为网格单元 单元权重两个属性getBin、getWeight再交给子图层ScreenGridCellLayerscreen-grid-cell-layer.ts以 instanced 方式绘制几何体为 triangle-strip 单位四边形。CPU 路径createAggregator()中 CPU 分支使用CPUAggregator维度 2其分箱逻辑清晰展示了屏幕空间的含义const viewport this.context.viewport; const p viewport.project(positions); const cellSizePixels: number opts.cellSizePixels; if (p[0] 0 || p[0] viewport.width || p[1] 0 || p[1] viewport.height) { // Not on screen return null; } return [Math.floor(p[0] / cellSizePixels), Math.floor(p[1] / cellSizePixels)];即先把经纬度经viewport.project投影为屏幕像素坐标越界点被丢弃然后按floor(像素坐标 / cellSizePixels)取整得到网格行列号。这也从实现层面印证了文档的提示——每次视口变化都必须重新聚合源码在updateState()中检测到changeFlags.viewportChanged时会调用this.state.aggregator.setNeedsUpdate()强制重跑聚合。GPU 路径GPU 分支使用WebGLAggregator配合project32与binOptionsUniforms两个 shader 模块顶点着色器内计算 binvoid getBin(out ivec2 binId) { vec4 pos project_position_to_clipspace(positions, positions64Low, vec3(0.0)); vec2 screenCoords vec2(pos.x / pos.w 1.0, 1.0 - pos.y / pos.w) / 2.0 * project.viewportSize.xy / project.devicePixelRatio; vec2 gridCoords floor(screenCoords / binOptions.cellSizePixels); binId ivec2(gridCoords); }GPU 聚合需要预先声明网格总范围binIdRange源码依据当前视口尺寸推导binIdRange: [ [0, Math.ceil(width / cellSizePixels)], [0, Math.ceil(height / cellSizePixels)] ]注意这会覆盖所有可能存在的网格包括空网格其内存占用与最大网格数量成正比而非与有点的网格数量成正比——这是判断 GPU 聚合是否划算的关键因素。子图层渲染时每个单元格被展开为单位四边形顶点着色器screen-grid-layer-vertex.glsl.ts根据instanceWeights、screenGrid.colorDomain与colorRange纹理插值出颜色并对NaN权重无数据网格直接丢弃绘制片段着色器screen-grid-layer-fragment.glsl.ts输出颜色并预留DECKGL_FILTER_COLOR钩子供扩展注入。CPU 与 GPU 聚合的取舍重要官方文档在 overview.md 中给出了深入分析要点如下兼容性GPU 聚合所需的客户端特性已被主流常青浏览器普遍支持覆盖全球 95% 市场但个别设备/芯片的驱动差异可能影响结果数据规模CPU 聚合耗时与输入数据量大致呈线性关系GPU 聚合有搭建 shader 与上传缓冲区的前置开销但处理更多数据的边际成本很小。大数据集100KGPU 明显更快小数据集 GPU 可能反而更慢数据分布CPU 聚合内存与含点网格数成正比GPU 聚合内存与全部可能网格数含空网格成正比。数据密集集中时 GPU 表现更好稀疏分散时 GPU 优势减弱过滤扩展基于 GPU 的扩展如 DataFilterExtension、MaskExtension 仅与 GPU 聚合协同工作精度GPU shader 仅支持 32 位浮点。虽然本图层实现了缓解精度损失的补偿措施但 GPU 聚合结果与 CPU 未必完全一致仓库测试保证二者具有可接受的近似一致性访问分箱内的点GPU 聚合不暴露某个单元包含哪些数据点。若你需要例如点击网格后列出位置清单要么使用 CPU 聚合要么自行对数据做即时过滤。overview.md 中给出了基于随机数据的实测性能对比2016 款 15 英寸 MacBook ProCPU 2.8 GHz Intel Core i7GPU AMD Radeon R9 M370X 2 GB对象数量CPU迭代/秒GPU迭代/秒备注25K535359GPU 慢约 33%100K119437GPU 快约 267%1M12.7158GPU 快约 1144%JSON 配置用法Playground 示例仓库的 playground JSON 示例 examples/playground/json-examples/screen-grid.json 展示了以声明式 JSON 使用该图层的方式数据为加州公交站点cellSizePixels: 20、opacity: 0.8、6 级 YlOrRd 色板{ layers: [ { type: ScreenGridLayer, id: grid, data: https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/screen-grid/ca-transit-stops.json, opacity: 0.8, cellSizePixels: 20, colorRange: [ [255, 255, 178, 25], [254, 217, 118, 85], [254, 178, 76, 127], [253, 141, 60, 170], [240, 59, 32, 212], [189, 0, 38, 255] ], gpuAggregation: true } ] }注意此例的colorRange每个颜色都带显式 Alpha 通道实现从低密度到高密度由淡到浓的叠加效果。完整的交互式示例还可在 examples/website/screen-grid 中找到React 实现 旧金山自行车数据。测试验证仓库使用 Vitest 对该图层做了系统测试见 test/modules/aggregation-layers/screen-grid-layer.spec.tsimport {ScreenGridLayer, WebGLAggregator, CPUAggregator} from deck.gl/aggregation-layers; import {testLayer, generateLayerTests} from deck.gl/test-utils/vitest; test(ScreenGridLayer, () { const testCases generateLayerTests({ Layer: ScreenGridLayer, sampleProps: {data: FIXTURES.points.slice(0, 3), getPosition}, assert: (cond, msg) expect(cond, msg).toBeTruthy(), onBeforeUpdate: ({testCase}) console.log(testCase.title) }); testLayer({Layer: ScreenGridLayer, testCases, onError: err expect(err).toBeFalsy()}); });该测试通过generateLayerTests自动生成覆盖各属性组合的用例再经testLayer逐条渲染验证涵盖 WebGL 与 CPU 两条聚合路径是排查自定义配置时的重要参考。小结ScreenGridLayer是 deck.gl 中屏幕空间聚合的代表实现核心要点可归纳为空间语义分箱在屏幕像素空间完成视口变化必然触发重新聚合适合中小规模数据三条控制链getPositioncellSizePixels决定怎么分箱getWeightaggregation决定每个格子值多少colorDomaincolorRangecolorScaleType决定值映射成什么颜色双聚合引擎gpuAggregation默认开启但会在设备不支持时自动回退 CPU大数据集、数据集中、需要 GPU 扩展DataFilter/Mask时选 GPU需要访问格内点列表pointIndices/points或追求小数据量性能时选 CPU拾取即单元交互回调拿到的对象是聚合后的网格单元col/row/value/count而非原始数据点。如需进一步深入可继续阅读聚合图层家族的其他成员 ContourLayer、GridLayer、HeatmapLayer、HexagonLayer以及高级用法中的 AggregationLayer、Aggregator 接口由 CPUAggregator 与 WebGLAggregator 实现。【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表