使用Super Cluster在DeckGL中实现地图聚类功能失败
DeckGL + Super Cluster 聚类功能报错排查与解决
我基于DeckGL与Super Cluster实现了地图聚类功能,用到了IconLayer、GeoJsonLayer、ScatterplotLayer及TextLayer四种图层。addAssetMonitoringGeoJsonLayer()的数据已传递给addAssetMonitoringIconsLayer(),但renderLayers()无法获取到该数据,控制台抛出以下错误:
deck: update of CompositeLayer({id: 'icon-cluster'}): Cannot read properties of undefined (reading 'getClusters') deck: update of CompositeLayer({id: 'icon-cluster'}): undefined is not iterable (cannot read property Symbol(Symbol.iterator))
相关实现代码如下:
addAssetMonitoringIconsLayer () { const getIconSize = (size) => { return Math.min(100, size) / 100 + 1; } class IconClusterLayer extends CompositeLayer { shouldUpdateState({changeFlags}) { return changeFlags.somethingChanged; } updateState({props, oldProps, changeFlags}) { const rebuildIndex = changeFlags.dataChanged || props.sizeScale !== oldProps.sizeScale; if (rebuildIndex) { const index = new Supercluster({maxZoom: 16, radius: props.sizeScale}); index.load( props.data.map(d => ({ geometry: {coordinates: props.getPosition(d)}, properties: d, }) )); this.setState({index}); } const z = Math.floor(this.context.viewport.zoom); if (rebuildIndex || z !== this.state.z) { this.setState({ data: this.state.index.getClusters([-180, -85, 180, 85], z), z, }); } } renderLayers() { const {data} = this.state; return [ // No clustering -> render icons from the API -> onHover: show the asset name -> on Click: show the asset details new IconLayer( this.getSubLayerProps({ id: 'icon', data, getPosition: d => d.geometry.coordinates, getIcon: d => d.properties.cluster ? '' : {url: (BASE_URL).substring(0, BASE_URL.length) + d.properties.icon_url, width: 38, height: 60, anchorY: 60}, transitions: {getPosition: { duration: 100 }}, onHover: (info, d) => { if (!d.properties.cluster && (info.coordinate && info.object)){ const marker = new window.google.maps.Marker({ position: { lat: info.coordinate[1], lng: info.coordinate[0] }, map: this.map.current.context[MAP] }); marker.setOptions({ 'opacity': 0.0 }); marker.setOptions({ 'title': `${info.object.asset_model__name}` }); marker.addListener("click", () => { this.showMarkerInfo(info.object); }); } }, onClick: (info, d) => { if (!d.properties.cluster){ this.showMarkerInfo(info.object); } }, }) ), // when there is clustering show this and change it's size on zoom change -> onHover -> show total number of assets in the cluster new ScatterplotLayer( this.getSubLayerProps({ id: 'scatter-plot-cluster-layer', data, getPosition: d => d.geometry.coordinates, getRadius: d => getIconSize(d.properties.cluster ? (d.properties.point_count * 200) : 0), getFillColor: [255, 0, 0], opacity: 0.5, radiusScale: 6, radiusMinPixels: 20, radiusMaxPixels: 100, transitions: { getRadious: { duration: 1000, enter: t => t * 1.1, } }, onHover: (info, d) => { if (!d.properties.cluster && (info.coordinate && info.object)){ const marker = new window.google.maps.Marker({ position: { lat: info.coordinate[1], lng: info.coordinate[0] }, map: this.map.current.context[MAP] }); marker.setOptions({ 'opacity': 0.0 }); marker.setOptions({ 'title': `${d.properties.point_count}` }); } } }) ), // when clustered, show to the total number of assets in the cluster on top of ScatterplotLayer new TextLayer ( this.getSubLayerProps({ id: 'scatter-plot-cluster-layer', data, getText: d => d.properties.cluster ? d.properties.point_count : '', getSize: 12, getColor: [255, 255, 255], characterSet: 'auto', }) ) ]; } } // All the data that are necessary to create clustering and plotting point assets const layer = { id: "icon-cluster", data: this.props.assetMonitoringIconsData, pickable: true, getPosition: (d) => { if(d.geom && d.geom.coordinates) { return d.geom.coordinates; } return d.geom; }, sizeScale: 60, dataTransform: (data) => { return data.results.map(res => { if(res.geom && res.geom_type === 'POINT' && res.geom) { res.geom = { "type": "Point", "coordinates": [ res.geom.lng, res.geom.lat ] }; } return res; }); } }; const layers = this.overlay.current.props.layers ? [new IconClusterLayer(layer)].concat(this.overlay.current.props.layers.filter((ele) => ele.id !== layer.id)) : [new IconClusterLayer(layer)]; if (this.overlay.current) { this.overlay.current.setMap(this.map.current.context[MAP]); this.overlay.current.setProps({ layers }); } } addAssetMonitoringGeoJsonLayer () { const layer = { id: "custom-GeoJson-layer", data: this.props.assetMonitoringIconsData, pickable: true, stroked: true, filled: false, lineWidthScale: 20, getLineColor: [160, 160, 180, 200], getLineWidth: 1, wrapLongitude: true, dataTransform: (data) => { return data.results.map(res => { if(res.geom && res.geom_type === 'LINESTRING') { if (res.geom) { res.geom = { "type": "MultiLineString", "coordinates": res.geom, }; } } return res; }); } }; const layers = this.overlay.current.props.layers ? [new GeoJsonLayer(layer)].concat(this.overlay.current.props.layers.filter((ele) => ele.id !== layer.id)) : [new GeoJsonLayer(layer)]; if (this.overlay.current) { this.overlay.current.setMap(this.map.current.context[MAP]); this.overlay.current.setProps({ layers }); } }
问题原因分析
this.state.index未初始化:updateState中仅在rebuildIndex为true时创建Supercluster实例,组件首次渲染或rebuildIndex为false时,this.state.index为undefined,调用getClusters直接报错。- 数据有效性未校验:
props.data可能为空或未正确加载,导致props.data.map触发“undefined is not iterable”错误。 - 图层ID重复:ScatterplotLayer和TextLayer使用同一ID
scatter-plot-cluster-layer,引发DeckGL图层管理冲突。 - 属性名拼写错误:ScatterplotLayer的transitions中
getRadious应为getRadius,导致过渡逻辑失效并干扰状态更新。
解决方案
1. 初始化Supercluster实例
在IconClusterLayer中添加initializeState方法,提前初始化index和数据:
class IconClusterLayer extends CompositeLayer { initializeState() { this.setState({ index: new Supercluster({maxZoom: 16, radius: this.props.sizeScale}), data: [], z: 0 }); } // 其余代码保持不变 }
2. 校验数据有效性
在updateState中先校验数据,避免空值迭代:
updateState({props, oldProps, changeFlags}) { const rebuildIndex = changeFlags.dataChanged || props.sizeScale !== oldProps.sizeScale; if (rebuildIndex) { const index = new Supercluster({maxZoom: 16, radius: props.sizeScale}); // 确保数据有效后再处理 const validData = props.data?.results || []; index.load( validData.map(d => ({ geometry: {coordinates: props.getPosition(d)}, properties: d, })) ); this.setState({index}); } const z = Math.floor(this.context.viewport?.zoom || 0); // 确认index存在后再调用getClusters if ((rebuildIndex || z !== this.state.z) && this.state.index) { this.setState({ data: this.state.index.getClusters([-180, -85, 180, 85], z), z, }); } }
3. 修复重复图层ID
给TextLayer设置唯一ID:
new TextLayer ( this.getSubLayerProps({ id: 'cluster-text-layer', data, getText: d => d.properties.cluster ? d.properties.point_count : '', getSize: 12, getColor: [255, 255, 255], characterSet: 'auto', }) )
4. 修正属性名拼写错误
将getRadious改为正确的getRadius:
transitions: { getRadius: { duration: 1000, enter: t => t * 1.1, } }
5. 确保数据时序正确
在组件更新时监听数据变化,再触发图层添加:
// 类组件示例 componentDidUpdate(prevProps) { if (prevProps.assetMonitoringIconsData !== this.props.assetMonitoringIconsData) { this.addAssetMonitoringGeoJsonLayer(); this.addAssetMonitoringIconsLayer(); } }
内容的提问来源于stack exchange,提问作者wgetDJ
相关产品推荐
相关产品推荐

