deck.gl ScreenGridLayer 独立示例实战指南:用 React + MapLibre 构建屏幕网格聚合可视化
2026/9/15 15:44:51 网站建设 项目流程

deck.gl ScreenGridLayer 独立示例实战指南:用 React + MapLibre 构建屏幕网格聚合可视化

【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl

ScreenGridLayer 是 deck.gl 中把海量点数据在屏幕空间聚合为网格直方图并叠加渲染的聚合图层,非常适合展示出行热度、人口密度、事件分布等空间聚集场景。本篇文章以仓库中 examples/website/screen-grid 这个最小可运行的独立示例为骨架,完整讲解如何从零搭建一个基于 React、MapLibre 与 Vite 的 ScreenGridLayer 应用,并深入其官方 API 文档与源码实现,帮助你掌握数据格式、核心配置项、GPU/CPU 聚合取舍与拾取交互,最终能在自己的项目中直接复制、修改和运行。

示例概览:一个最小可运行的 ScreenGridLayer 应用

examples/website/screen-grid是 deck.gl 官网 ScreenGridLayer 演示的独立精简版。整个目录结构非常简洁,只有 5 个文件:

examples/website/screen-grid/ ├── app.tsx # React 组件:定义图层与视图状态 ├── index.html # HTML 入口,挂载 React 应用 ├── package.json # 依赖与脚本定义 ├── tsconfig.json # TypeScript 配置 └── README.md # 本示例的使用说明

其核心逻辑集中在app.tsx中:通过@deck.gl/reactDeckGL组件渲染地图与图层,用react-map-gl/maplibre提供 MapLibre 底图,图层本体则由@deck.gl/aggregation-layers导出的ScreenGridLayer承担。示例数据是纽约市 Uber 上下车点(pickup locations),展示点数据按屏幕网格聚合后的热度分布。

快速启动:将示例接入你的项目

按 README 的说明,启动该示例只需三步:

  1. 复制目录:将examples/website/screen-grid整个文件夹的内容拷贝到你的项目;
  2. 安装依赖:使用npm installyarn(项目使用 Vite 作为构建与开发服务器,详见 package.json 中的 scripts);
  3. 启动应用:执行npm start(等价于vite --open,会自动打开浏览器)。
# install dependencies npm install # or yarn # bundle and serve the app with vite npm start

package.json中的关键依赖如下,版本均来自仓库当前配置:

依赖版本范围用途
deck.gl^9.0.0deck.gl 全家桶(含 core / layers / aggregation-layers)
@deck.gl/react(经 deck.gl 导出)^9.0.0React 集成组件DeckGL
react/react-dom^18.0.0React 运行时
react-map-gl^8.0.0地图容器与底图桥接(此处使用其maplibre入口)
maplibre-gl^5.0.0MapLibre GL 引擎
vite^7.3.3开发服务器与打包

如果你只想用原生的 deck.gl(不使用 React),可参考 screen-grid-layer 官方文档 中基于Deck类的写法;该文档也提供了 TypeScript 与 React 两种等价写法。

数据格式:Uber 出行点数据与自定义数据接入

示例使用的数据是 deck.gl 官方示例数据集(deck.gl-data 仓库中的uber-pickup-locations.json),其原始来源为 fivethirtyeight 的 Uber TLC FOIL 响应数据集。在app.tsx中,数据通过 URL 直接加载:

const DATA_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/screen-grid/uber-pickup-locations.json';

数据集中的每条记录是一个三元组[longitude, latitude, count],对应类型定义为:

type DataPoint = [longitude: number, latitude: number, count: number];

其中第三个分量count作为该位置的**权重(weight)**参与聚合——示例中通过getWeight: d => d[2]取出,含义是该网格单元内所有点的权重总和。

换成你自己的数据时,只需替换data属性。README 指引读者参考 ScreenGridLayer 文档(其相对链接在本文中已转换为仓库根路径docs/api-reference/aggregation-layers/screen-grid-layer.md)。从app.tsx的 props 签名可以看到,data既可以是数据 URL 字符串,也可以是内存中的数组:

data?: string | DataPoint[];

也就是说,你可以传入任意[lng, lat, weight]形式的点数组,或通过getPositiongetWeight两个访问器适配任意字段结构的数据对象(例如文档示例中从d.COORDINATES取坐标、从d.SPACES取权重)。

底图配置:CARTO 免费底图与替代方案

示例的底图由CARTO 免费底图服务提供,配置在app.tsxMAP_STYLE常量中:

const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json';

这里选用的是 CARTO 的 "Dark Matter(无标注)" 样式,深色底图能突出彩色聚合网格的对比度。底图通过react-map-gl/maplibreMap组件渲染,并与DeckGL共享同一画布(通过reuseMaps复用地图实例,避免重复初始化):

<DeckGL device={device} layers={layers} initialViewState={INITIAL_VIEW_STATE} controller={true}> <Map reuseMaps mapStyle={mapStyle} /> </DeckGL>

README 指出,如需使用其他底图方案,可参考 deck.gl 官方指南 "Using other basemap services"(详见仓库 docs/developer-guide/base-maps 目录下关于地图集成的说明)。无论使用 Mapbox、MapLibre 还是 Google Maps,核心思路一致:用DeckGL的 React 子组件承载对应地图,并把mapStyle替换为目标服务的样式 URL。

视图状态与图层配置逐项拆解

初始视图状态(INITIAL_VIEW_STATE)

const INITIAL_VIEW_STATE: MapViewState = { longitude: -73.75, latitude: 40.73, zoom: 9.6, maxZoom: 16, pitch: 0, bearing: 0 };

该状态将视角定位到纽约市上空,zoom: 9.6保证网格在默认尺度下具有合理粒度,maxZoom: 16限制最大缩放级别。

ScreenGridLayer 的核心配置

app.tsx中的图层配置如下:

new ScreenGridLayer<DataPoint>({ id: 'grid', data, opacity: 0.8, getPosition: d => [d[0], d[1]], getWeight: d => d[2], cellSizePixels: cellSize, // 默认 20 colorRange, // 6 级 YlOrRd 风格色带 gpuAggregation, // 默认 true aggregation // 默认 'SUM' })

配合这些配置,我们来逐一对照官方文档与源码理解其含义:

  • cellSizePixels(默认100):每个网格单元的宽高(像素)。源码 screen-grid-layer.ts 中定义其类型为number、最小值为1。示例设为 20,表示每 20×20 像素一个聚合单元,适合展示高密度城市点数据;值越小网格越细、单元数量越多。
  • aggregation(默认'SUM'):单元值的聚合操作。官方文档列出的合法值包括'SUM'(权重求和)、'MEAN'(均值)、'MIN'(最小值)、'MAX'(最大值)、'COUNT'(落入单元的点数量)。getWeightaggregation共同决定每个单元的值。
  • gpuAggregation(默认true):是否在浏览器支持时使用 GPU 聚合。源码getAggregatorType()会先检查WebGLAggregator.isSupported(this.context.device),支持则走 GPU 路径,否则自动回退 CPU 聚合(详见下文性能小节)。
  • colorRange:色带数组,示例自定义了 6 级从浅黄到深红的 YlOrRd 风格色带(含 alpha 通道):
    const colorRange: Color[] = [ [255, 255, 178, 25], [254, 217, 118, 85], [254, 178, 76, 127], [253, 141, 60, 170], [240, 59, 32, 212], [189, 0, 38, 255] ];

    默认值为 colorbrewer 的6-class YlOrRd色带。每个颜色为[R, G, B][R, G, B, A]四元组,通道值范围 0–255,省略 Alpha 时按 255 处理。

  • getPosition(默认object => object.position):从每条数据取坐标的访问器。
  • getWeight(默认1):每条数据的权重。传数字则所有对象共用该权重;传函数则逐条求值。示例中d[2]即三元组中的 count 字段。
  • opacity(示例设为0.8):继承自基础 Layer 属性的整体不透明度。

更多渲染选项(来自官方文档)

官方文档还定义了以下可选参数,读者可在自定义场景中按需启用:

  • cellMarginPixels(默认2,范围被钳制在[0, 5]):单元之间的间隙(像素)。注意它只影响渲染时的格子外观,不改变点的分箱方式(源码注释明确说明"setting this prop does not affect how points are binned")。
  • colorScaleType(默认'linear'):数值到颜色的映射方式。'linear'colorDomain区间内对colorRange做线性插值;'quantize'则将 domain 均分为与色带数量相等的若干段,每段映射一种离散颜色。源码类型定义为'linear' | 'quantize'
  • colorDomain(默认null,自动):[min, max]数值区间。未提供时,图层在运行期以所有单元的实际最小/最大值作为 domain;显式指定则可在不同数据集间保持统一的颜色映射,便于横向对比。
  • gpuAggregationaggregation上文已述,cellSizePixels变化会触发重新聚合——源码updateState中监听cellSizePixelsaggregationdataChangedviewportChanged等变化并调用aggregator.setProps重算。

屏幕空间聚合原理:为什么平移缩放会触发重算

理解 ScreenGridLayer 的关键在于"屏幕空间"这三个字。官方文档特别提示:聚合发生在屏幕坐标系中,因此每当地图缩放或平移,图层都必须重新聚合数据。这也是它最适合中小规模数据集的原因——数据量不大时,重算开销可控,且视觉表现力极强。

从源码可以印证这一设计。CPU 聚合路径在createAggregator中把每个点的世界坐标投影到屏幕:

getValue: ({positions}: {positions: number[]}, index: number, opts: BinOptions) => { 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)]; }

可以看到:投影后落在视口之外的[lng, lat]点会被丢弃,视口内的点按floor(屏幕坐标 / cellSizePixels)归入对应网格单元——这就是"分箱(binning)"过程。GPU 路径则把同一逻辑写成 GLSL 顶点着色器(getBin中通过project_position_to_clipspace投影并计算gridCoords),由 WebGL 并行完成。updateState中还有一条关键逻辑:当viewportChanged为真时调用aggregator.setNeedsUpdate()强制重算,与文档的说明完全吻合。

另外注意,屏幕网格聚合是2 维分箱dimensions: 2),单元由col(列)、row(行)唯一标识;在 GPU 路径下,分箱范围按当前视口尺寸动态计算(binIdRange),见 screen-grid-layer.ts。

GPU 与 CPU 聚合的取舍

gpuAggregation的默认值为true,但并非所有场景都应开启。官方 Aggregation Layers 总览 的 "CPU vs GPU Aggregation" 一节给出了权威指导:

  • 兼容性:GPU 聚合所需的浏览器能力已被现代浏览器广泛支持,覆盖 95%+ 的全球市场;但个别设备/芯片的驱动差异可能影响结果。
  • 数据规模:CPU 聚合耗时与数据量大致线性相关;GPU 聚合有初始化着色器与上传缓冲的前期开销,但处理更多数据的边际成本很小。数据量大于约 10 万时 GPU 显著更快,小数据集下 GPU 反而可能更慢
  • 数据分布:CPU 聚合内存与"至少含一个点的单元数"成正比;GPU 聚合内存与"所有可能单元数"(含空单元)成正比。点越密集集中,GPU 优势越明显;点越稀疏分散,越不适合 GPU。
  • 扩展(Extensions)DataFilterExtensionMaskExtension等基于 GPU 的扩展仅支持 GPU 聚合
  • 精度:GPU 着色器仅支持 32 位浮点,虽做了缓解措施,但结果可能与 CPU 聚合存在微小差异(仓库测试会校验二者的一致性在可接受范围内)。
  • 访问单元内点:GPU 聚合不暴露单元内的具体数据点;若你需要"点选单元后列出其中的位置列表"这类功能,只能改用 CPU 聚合或手动过滤数据。这一点直接对应拾取信息中pointIndices/points字段"仅 CPU 聚合可用"的限制。

官方文档给出的参考性能数据(2016 款 15 英寸 MacBook Pro 上的随机数据):25K 点时 GPU 反而慢约 33%;100K 点时 GPU 快约 267%;1M 点时 GPU 快约 1144%。

拾取交互:hover/click 返回的单元对象

官方文档规定,ScreenGridLayer 的 hover/click 事件中PickingInfo.object代表一个聚合后的网格单元,包含以下字段:

字段类型说明
colnumber单元列索引,从视口左侧 0 开始
rownumber单元行索引,从视口顶部 0 开始
valuenumber聚合值,由getWeightaggregation共同决定
countnumber落在该单元内的数据点数量
pointIndicesnumber[]单元内数据对象的索引,仅 CPU 聚合可用
pointsobject[]单元内的数据对象,仅 CPU 聚合且 data 为数组时可用

这一行为在源码getPickingInfo中有明确实现(screen-grid-layer.ts):命中单元后组装{col, row, value, count},并在bin.pointIndices存在时补上pointIndicespoints。在示例中给DeckGL加上getTooltip即可展示提示框,例如:

getTooltip: ({object}) => object && `Count: ${object.value}`

文档中的 TypeScript 示例使用了ScreenGridLayerPickingInfo<BikeRack>类型(从@deck.gl/aggregation-layers导入,源码定义见 screen-grid-layer.ts),可让拾取对象获得完整的类型提示。

安装方式补充与测试佐证

除了通过deck.gl全家桶引入,官方文档还提供了按模块安装的方式:

npm install deck.gl # 或 npm install @deck.gl/core @deck.gl/layers @deck.gl/aggregation-layers

对应导入语句为import {ScreenGridLayer} from '@deck.gl/aggregation-layers'(类型ScreenGridLayerPropsScreenGridLayerPickingInfo亦从此模块导出);也支持通过 unpkg 的预打包脚本(dist.min.js)在纯 HTML 中直接使用全局deck.ScreenGridLayer

仓库对 ScreenGridLayer 有完善的自动化测试,可作进一步参考:单元测试位于 test/modules/aggregation-layers/screen-grid-layer.spec.ts(使用@deck.gl/test-utilsgenerateLayerTeststestLayer覆盖各 prop 组合),网格单元渲染测试见screengrid-cell-layer.spec.ts,聚合器实现位于 modules/aggregation-layers/src/common/aggregator(CPUAggregatorWebGLAggregator)。想深入了解图层渲染层,可阅读 modules/aggregation-layers/src/screen-grid-layer 目录下的screen-grid-cell-layer.ts与 GLSL/WGSL 着色器文件。

总结

examples/website/screen-grid是一个麻雀虽小五脏俱全的 deck.gl 实战模板:它演示了从依赖安装、数据接入、底图配置到图层参数调优的完整链路,并天然覆盖了 ScreenGridLayer 最核心的"屏幕空间聚合"特性与 GPU/CPU 双路径实现。掌握了这份示例之后,你可以基于 ScreenGridLayer 官方文档 继续探索cellMarginPixelscolorScaleTypecolorDomain等进阶参数,或参考 Aggregation Layers 总览 了解 GridLayer、HexagonLayer、HeatmapLayer、ContourLayer 等其他聚合图层的异同,把屏幕网格聚合能力平滑扩展到自己的业务数据之上。

【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询