ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

deck.gl 多视图 Radio 示例深度解析:MVTLayer 叠加 H3HexagonLayer 与联动小地图的实现

deck.gl 多视图 Radio 示例深度解析:MVTLayer 叠加 H3HexagonLayer 与联动小地图的实现 deck.gl 多视图 Radio 示例深度解析MVTLayer 叠加 H3HexagonLayer 与联动小地图的实现【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl本文基于 deck.gl 仓库中的独立示例项目 examples/website/radio完整拆解一个「主地图 联动小地图minimap」的多视图地理可视化应用它以美国广播电台Radio Station及其服务覆盖区为数据题材同时使用了MVTLayer加载矢量瓦片服务等值线、H3HexagonLayer渲染 H3 蜂窝覆盖统计、ScatterplotLayer绘制电台点位并通过layerFilter与viewState同步机制实现主视图与小地图的差异化渲染。读完本文你将掌握 deck.gl 多视图布局、瓦片图层与 H3 聚合图层组合、按视图过滤图层以及飞行过渡动画的完整实战方案。示例概览这个 Radio 示例在做什么examples/website/radio是 deck.gl 官方网站中 Multi-View多视图示例的最小独立版本README 原文为 a minimal standalone version of the Multi-View example。它的业务场景是展示美国本土广播电台的分布AM/FM以及每个电台的广播服务覆盖范围service contour。其核心亮点并非数据本身而是用两个MapView实例在同一画布上叠加渲染主视图id: main全屏展示电台点位、服务等值线、H3 覆盖统计小地图id: minimap画布左上角 20%×20% 的缩小视图仅渲染 H3 覆盖层和主视图可视范围框起到「鹰眼导航」作用。整个应用由两个源文件构成app.tsx核心逻辑与 search-bar.tsx电台搜索控件入口为 index.html依赖声明在 package.json。运行方式与项目结构README 给出的运行方式是把整个目录复制到你的项目后执行# 安装依赖 npm install # 或 yarn # 使用 Vite 打包并启动本地服务 npm start从 package.json 可以看到完整的脚本与依赖{ scripts: { start: vite --open, start-local: vite --config ../../vite.config.local.mjs, build: vite build }, dependencies: { loaders.gl/csv: ^4.4.3, material-ui/core: ^4.11.3, material-ui/lab: ^4.0.0-alpha.57, d3-scale: ^4.0.0, deck.gl: ^9.0.0, maplibre-gl: ^5.0.0, react: ^18.0.0, react-dom: ^18.0.0, react-map-gl: ^8.0.0 } }关键依赖说明deck.gl9.x框架主体包含deck.gl/core、deck.gl/layers、deck.gl/geo-layers、deck.gl/react等子模块代码中从deck.gl/core引入MapView、WebMercatorViewport、FlyToInterpolator从deck.gl/layers引入ScatterplotLayer、PathLayer从deck.gl/geo-layers引入MVTLayer、H3HexagonLayer从deck.gl/react引入DeckGLreact-map-glmaplibre-gl在主视图内嵌 MapLibre 底图实现底图与 deck.gl 图层的同步叠加loaders.gl/csv用于把远程 CSV/TSV 数据解析为图层数据d3-scale提供scaleSqrt、scaleLinear做颜色映射material-ui/core/material-ui/lab提供Autocomplete搜索框组件。入口 index.html 引用app.tsx暴露的renderToDOM函数完成挂载script typemodule import {renderToDOM} from ./app.tsx; renderToDOM(document.getElementById(app)); /scriptrenderToDOM先渲染一个无数据占位再异步加载电台 TSV 数据后二次渲染export async function renderToDOM(container: HTMLDivElement) { const root createRoot(container); root.render(App /); const stations ( await load(DATA_URL.STATIONS, CSVLoader, { csv: {delimitersToGuess: \t, skipEmptyLines: true} }) ).data; root.render(App data{stations} /); }数据格式与自定义数据README 说明示例数据存放于 deck.gl Example Data 仓库的examples/radio目录展示美国本土的广播电台及其服务等值线数据源为 FCC 联邦通信委员会。app.tsx 中定义了三个数据端点const DATA_URL_BASE https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/radio; const DATA_URL { STATIONS: ${DATA_URL_BASE}/stations.tsv, COVERAGE: ${DATA_URL_BASE}/coverage.csv, CONTOURS: ${DATA_URL_BASE}/service-contour/{z}/{x}-{y}.mvt };三类数据形态与对应图层如下数据格式消费图层说明stations.tsvTSV制表符分隔ScatterplotLayer电台元数据含呼号、频率、AM/FM 类型、城市、经纬度coverage.csvCSVH3HexagonLayer每个 H3 六边形格内的电台数量统计service-contour/{z}/{x}-{y}.mvtMapbox Vector TileMVTMVTLayer按瓦片切分的服务等值线矢量面Station类型在 app.tsx 中定义export type Station { callSign: string; // 电台呼号如 KQED frequency: number; // 频率数值 type: AM | FM; city: string; state: string; name: string; longitude: number; latitude: number; };更换自有数据时README 指向两份核心文档MVTLayer 文档MVTLayer是TileLayer的派生类支持通过 URL 模板如{z}/{x}/{y}.mvt或 TileJSON 加载矢量瓦片只加载、解码并渲染当前视口内可见瓦片中的要素并自动处理跨瓦片要素裁剪。若瓦片要素没有 feature id可设置uniqueIdProperty本例为callSign来跨瓦片标识同一要素highlightedFeatureId配合highlightColor实现指定要素高亮binary: true默认使用二进制格式提升大数据集性能。需要以 TileJSON 方式接入时还可用onDataLoad回调。H3HexagonLayer 文档该层从 H3 地理索引系统渲染六边形是CompositeLayer。核心访问器getHexagon默认取object.hexagon本例指定为d.hex渲染选项highPrecision默认auto在数据包含五边形、粗分辨率res 0–5或混合分辨率时自动切换高精度渲染coverage默认1可在 0–1 之间缩放六边形半径。多视图架构主地图与小地图的布局与联动这是本示例的技术核心也呼应了 README 中引用的 开发者指南《Views and Projections》。deck.gl 的视图体系由View相机硬配置、viewState相机实时状态与Viewport解析后的相机负责投影/反投影数学运算三层组成同一个Deck实例可以通过views属性承载多个View每个视图拥有独立的画布区域与交互控制器。1. 定义两个 MapViewapp.tsx 中定义了两个MapViewconst mainView new MapView({id: main, controller: true}); const minimapView new MapView({ id: minimap, x: 20, y: 20, width: 20%, height: 20%, clear: true });要点x/y/width/height支持数字或 CSS 百分比字符串20%运行时随画布尺寸解析主视图开启controller: true小地图未开启控制器因此只有主视图接受用户平移/缩放clear: true让迷你视图区域拥有独立透明清屏避免背景叠加。2. 独立 viewState 与同步策略两个视图各自拥有独立的 view state按视图id作为键app.tsx 中const INITIAL_VIEW_STATE: {main: MapViewState; minimap: MapViewState} { main: { longitude: -100, latitude: 40, zoom: 3, minZoom: 3 }, minimap: { longitude: -100, latitude: 40, zoom: 1 } };联动逻辑在onViewStateChange中实现当用户操作主视图时小地图保持自身缩放级别但跟随主视图的中心经纬度const onViewStateChange useCallback( ({viewState: newViewState}: ViewStateChangeParametersMapViewState) { setViewState(() ({ main: newViewState, minimap: { ...INITIAL_VIEW_STATE.minimap, longitude: newViewState.longitude, latitude: newViewState.latitude } })); }, [] );这与开发者指南中「minimap 跟随主视角」的经典模式完全一致主视图把完整的新viewState写入小地图只更新经纬度而保留自己的zoom。同时本例通过受控方式把viewState与onViewStateChange传给DeckGL属于「无状态组件」用法适合需要从外部搜索框、按钮操作相机状态的场景。3. 主视图内嵌 MapLibre 底图DeckGL的 JSX 子树里嵌入了react-map-gl的Map组件与搜索栏DeckGL device{device} layers{layers} views{showMinimap ? [mainView, minimapView] : mainView} viewState{viewState} parameters{{depthCompare: always}} onViewStateChange{onViewStateChange} layerFilter{layerFilter} getTooltip{getTooltip} MapView idmain Map reuseMaps mapStyle{mapStyle} keymap / SearchBar data{data} onChange{onSelectStation} / /MapView {showMinimap ( MapView idminimap div style{minimapBackgroundStyle} / /MapView )} /DeckGL小地图内部没有底图图层仅用一个带阴影的白色div作为背景突出「纯数据叠加」的鹰眼视图主视图则通过reuseMaps复用 MapLibre 实例mapStyle默认指向 CARTO 的 Positron 浅色风格见下文「底图配置」一节。图层设计四层数据叠加的完整拆解app.tsx 中的layers数组依次构建了四个图层const layers [ data new MVTLayerServiceContour({ id: service-contours, data: DATA_URL.CONTOURS, maxZoom: 8, onTileError: () {}, pickable: true, autoHighlight: true, highlightColor: [255, 200, 0, 100], uniqueIdProperty: callSign, highlightedFeatureId: highlight, opacity: 0.2, lineWidthMinPixels: 2, getLineColor: f { const {type, frequency} stationMap[f.properties.callSign]; return type AM ? amColorScale(frequency) : fmColorScale(frequency); }, getFillColor: [255, 255, 255, 0] }), new ScatterplotLayerStation({ id: stations, data, getPosition: d [d.longitude, d.latitude], getFillColor: [40, 40, 40, 128], getRadius: 20, radiusMinPixels: 2 }), new H3HexagonLayer{hex: string; count: number}({ id: coverage, data: DATA_URL.COVERAGE, getHexagon: d d.hex, stroked: false, extruded: false, getFillColor: d coverageColorScale(d.count), loaders: [CSVLoader] }), new PathLayer({ id: viewport-bounds, data: viewportBounds, getPath: d d, getColor: [255, 0, 0], getWidth: 2, widthUnits: pixels }) ];MVTLayer服务等值线通过{z}/{x}/{y}.mvtURL 模板按需加载矢量瓦片maxZoom: 8限制瓦片最高层级uniqueIdProperty: callSign让跨瓦片被切割的同一电台轮廓在拾取/高亮时被识别为同一要素highlightedFeatureId: highlight与autoHighlight、highlightColor配合实现点击/搜索电台后该电台服务区轮廓高亮getLineColor依据stationMap中的电台类型与频率着色FM 用fmColorScale频率域 87–108从蓝渐变到绿AM 用amColorScale频率域 530–1700从红渐变到黄opacity: 0.2、透明填充让轮廓线叠加在 H3 覆盖层之上时保持可读。ScatterplotLayer电台点位以getPosition: d [d.longitude, d.latitude]把每座电台画成半透明深灰圆点getRadius: 20结合radiusMinPixels: 2保证缩小到全美视图时点仍可见。H3HexagonLayerH3 覆盖统计把coverage.csv中每个 H3 格子的电台数量count映射为颜色scaleSqrt的平方根比例尺让低密度区域的差异更易分辨const coverageColorScale scaleSqrtColor() .domain([1, 10, 100]) .range([ [237, 248, 177], [127, 205, 187], [44, 127, 184] ]);stroked: false、extruded: false表示纯平面填充六边形loaders: [CSVLoader]告诉该层如何解析 CSV 数据H3HexagonLayer本身是CompositeLayer内部按highPrecision设置选用ColumnLayer或SolidPolygonLayer渲染。PathLayer主视图可视范围框viewportBounds通过WebMercatorViewport对主视图的四个角做反投影得到世界坐标多边形再画成红色矩形让用户在小地图上一眼看到主视图当前覆盖的地理范围const viewportBounds useMemo(() { const {width, height} viewState.main; if (!width) return null; const viewport new WebMercatorViewport(viewState.main); const topLeft viewport.unproject([0, 0]); const topRight viewport.unproject([width, 0]); const bottomLeft viewport.unproject([0, height]); const bottomRight viewport.unproject([width, height]); return [[topLeft, topRight, bottomRight, bottomLeft, topLeft]]; }, [viewState]);这展示了开发者指南中「Viewport 提供 project/unproject 数学能力」的典型应用即便日常不需要直接操作Viewport在需要坐标换算的场景如计算屏幕对应地理范围中可以直接实例化使用。layerFilter按视图差异化渲染默认情况下所有可见图层都会渲染进所有视图。本例需要「H3 覆盖层 可视范围框」只出现在小地图而其余图层只出现在主视图因此通过layerFilter实现app.tsxfunction layerFilter({layer, viewport}: FilterContext) { const shouldDrawInMinimap layer.id.startsWith(coverage) || layer.id.startsWith(viewport-bounds); if (viewport.id minimap) return shouldDrawInMinimap; return !shouldDrawInMinimap; }其底层由Deck的layerFilterprop 承载在渲染器 deck-renderer.ts 中注册并在每帧渲染与拾取picking流程中分别调用见 deck.ts 与 deck-picker.ts因此它同时约束「画什么」和「拾取什么」。开发者指南针对瓦片类图层给出了重要性能提醒TileLayer、MVTLayer、HeatmapLayer、ScreenGridLayer等会在视口变化时执行数据拉取或聚合这类昂贵操作不建议把同一个瓦片图层实例渲染进多个视图若多个视图都需要瓦片底图应当为每个视图创建独立图层实例并配合layerFilter各自限定渲染范围指南中给出了layer.id tiles-for-${viewport.id}的命名约定示例。交互搜索、飞行定位与 Tooltip电台搜索与飞行过渡SearchBarsearch-bar.tsx基于 Material-UI 的Autocomplete实现支持按呼号callSign或台名name前缀匹配最多返回 30 条候选MAX_OPTIONS并过滤掉非字母数字字符以增强健壮性。选中电台后触发onSelectStationconst onSelectStation useCallback((station: Station) { if (station) { setViewState(currentViewState ({ minimap: currentViewState.minimap, main: { ...currentViewState.main, longitude: station.longitude, latitude: station.latitude, zoom: 8, transitionInterpolator: new FlyToInterpolator(), transitionDuration: auto } })); } setHighlight(station station.callSign); }, []);这段代码同时演示了两个能力受控 viewState 的编程式相机操作直接把目标位置写入main视图状态FlyToInterpolator过渡动画transitionInterpolatortransitionDuration: auto让相机从当前位置平滑飞行到电台所在位置并放大到zoom: 8同时更新highlight状态驱动MVTLayer的highlightedFeatureId高亮该电台的服务区轮廓。基于 stationMap 的 Tooltip应用预先用useMemo把电台数组转成callSign → Station的映射表stationMapgetTooltip回调据此把拾取到的要素可能是 MVT 要素或 Station 对象翻译成可读文案function getTooltipText(stationMap, object) { if (!object) return null; let callSign; if (properties in object) { callSign object.properties.callSign; // MVT 要素 } else { callSign object.callSign; // Station 对象 } if (!(callSign in stationMap)) return null; const {name, frequency, type, city, state} stationMap[callSign]; return ${callSign} ${name} ${type} ${frequency} ${type AM ? kHz : MHz} ${city}, ${state}; }uniqueIdProperty: callSign在此再次发挥作用MVT 中跨瓦片被切碎的等值线在拾取时能通过properties.callSign反查完整电台信息实现「点哪条线都显示该台详情」的交互。底图配置与替代方案README 明确说明本例底图由CARTO 免费底图服务提供代码中默认的mapStyle为mapStyle https://basemaps.cartocdn.com/gl/positron-gl-style/style.jsonCARTO 的 Positron 浅色风格适合作为数据可视化底图——低饱和底色让 MVTLayer 等高亮色图层更突出。如需切换其他底图服务商Mapbox、MapTiler、自有瓦片服务等README 指引参考官方「using-with-map」指南中的Using other basemap services一节在仓库中可进一步阅读 get-started/using-with-map.md 了解 deck.gl 与其他底图库Mapbox GL、MapLibre、Google Maps 等的叠加方式。另外示例组件还通过showMinimap属性控制是否显示小地图通过mapStyle、device、data等 props 支持外部注入方便在 playground 中直接换数据、换底图进行实验。小结从示例到自有应用的可复用要点examples/website/radio虽然只有两个 TSX 文件却集中展示了 deck.gl 高价值的多视图能力组合可归纳为以下可直接复用的模式多MapView叠加用x/y/width/height定义子视图区域用带id的独立viewState让各视图保持不同相机参数主从视图同步在onViewStateChange中只把经纬度同步给小地图保留其固定缩放形成「鹰眼」效果layerFilter差异化渲染按viewport.id与layer.id前缀决定图层归属同时注意瓦片类图层不宜跨视图复用实例编程式相机受控viewStateFlyToInterpolator实现搜索定位的飞行过渡配合highlightedFeatureId完成数据联动高亮数据层组合MVTLayer矢量瓦片等值线H3HexagonLayerH3 聚合面ScatterplotLayer点位PathLayer范围框在同一Deck中按业务需要分层组织。如需继续深入可对照阅读本仓库中的 MVTLayer API 文档、H3HexagonLayer API 文档 以及 Views 开发者指南并结合 app.tsx 与 search-bar.tsx 的完整源码逐一验证上述机制。【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表