如何在Mapbox GL JS中实现自定义HTML标记的聚类功能
问题描述
之前的非聚类Symbol标记实现
我之前通过map.addLayer添加单个'symbol'类型标记,代码如下:
// Unclustered; Single Listing Styling // map.addLayer({ // id: 'unclustered-point', // type: 'symbol', // source: 'earthquakes', // filter: ['!', ['has', 'point_count']], // paint: { // 'text-color': 'white', // 'text-halo-width': 5, // Adjust the halo width as needed // 'text-halo-color': [ // 'case', // ['boolean', ['feature-state', 'hover'], false], // '#07364B', // Replace with the desired hover halo color // '#0097A7' // Replace with the default halo color // ], // }, // layout: { // 'text-field': ['get', 'price'], // // 'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'], // 'text-size': 25, // 'text-offset': [0, 0.6], // Adjust the offset as needed // 'text-anchor': 'top', // 'text-allow-overlap': true // }, // });
当前HTML标记聚类尝试方案
由于'symbol'图层不支持HTML元素作为标记,我参考相关示例实现了通过监听渲染事件更新屏幕标记的逻辑:
map.on('render', () => { if (!map.isSourceLoaded('earthquakes')) return; updateMarkers(); });
对应的updateMarkers()函数代码:
// objects for caching and keeping track of HTML marker objects (for performance) const markers = {}; let markersOnScreen = {}; function updateMarkers() { const newMarkers = {}; const features = map.querySourceFeatures('earthquakes'); // for every cluster on the screen, create an HTML marker for it (if we didn't yet), // and add it to the map if it's not there already for (const feature of features) { const coords = feature.geometry.coordinates; const props = feature.properties; if (!props.cluster) continue; const id = props.cluster_id; let marker = markers[id]; if (!marker) { const el = document.createElement('div'); el.classList.add('mapCluster'); el.innerText = props.price; marker = markers[id] = new mapboxgl.Marker({ element: el }).setLngLat(coords); } newMarkers[id] = marker; if (!markersOnScreen[id]) marker.addTo(map); } // for every marker we've added previously, remove those that are no longer visible for (const id in markersOnScreen) { if (!newMarkers[id]) markersOnScreen[id].remove(); } markersOnScreen = newMarkers; }
当前效果:
问题与需求
- 聚类功能无法正常工作,标记值显示为
undefined - 遍历
feature时同时返回了聚类和非聚类要素,需要同时处理这两种情况 - 要求实现正确的聚类逻辑,并能正常显示
feature.price相关值
解决方法
1. 确保数据源开启聚类配置
聚类生效的前提是数据源正确配置了聚类参数,检查并调整你的earthquakes数据源:
map.addSource('earthquakes', { type: 'geojson', data: '你的GeoJSON数据地址或对象', cluster: true, clusterMaxZoom: 14, // 聚类失效的最大缩放级别 clusterRadius: 50 // 聚类半径(单位:像素) });
2. 修复price显示undefined的问题
聚类要素本身没有price属性,需要通过聚合计算得到聚类内要素的价格相关值,在数据源配置中添加clusterProperties:
map.addSource('earthquakes', { type: 'geojson', data: '你的GeoJSON数据地址或对象', cluster: true, clusterMaxZoom: 14, clusterRadius: 50, clusterProperties: { sumPrice: ['+', ['get', 'price']], // 计算聚类内price总和 avgPrice: ['/', ['+', ['get', 'price']], ['get', 'point_count']] // 计算聚类内price平均值 } });
之后在updateMarkers中使用聚合后的价格值:
// 替换原代码中的el.innerText = props.price; el.innerText = props.sumPrice; // 或props.avgPrice,根据需求选择
3. 同时处理聚类与非聚类要素
当前代码只处理了聚类要素,添加逻辑处理单个非聚类要素:
function updateMarkers() { const newMarkers = {}; const features = map.querySourceFeatures('earthquakes'); for (const feature of features) { const coords = feature.geometry.coordinates; const props = feature.properties; let id, marker, el; // 处理聚类要素 if (props.cluster) { id = `cluster-${props.cluster_id}`; marker = markers[id]; if (!marker) { el = document.createElement('div'); el.classList.add('mapCluster'); el.innerText = props.sumPrice; // 使用聚合后的价格 marker = markers[id] = new mapboxgl.Marker({element: el}).setLngLat(coords); } } else { // 处理单个非聚类要素 id = `point-${feature.id || coords.join(',')}`; // 用要素id或坐标拼接唯一标识 marker = markers[id]; if (!marker) { el = document.createElement('div'); el.classList.add('mapSinglePoint'); el.innerText = props.price; // 单个要素的原始price marker = markers[id] = new mapboxgl.Marker({element: el}).setLngLat(coords); } } newMarkers[id] = marker; if (!markersOnScreen[id]) marker.addTo(map); } // 清理不在当前视图的标记 for (const id in markersOnScreen) { if (!newMarkers[id]) markersOnScreen[id].remove(); } markersOnScreen = newMarkers; }
4. 优化性能(可选)
避免每帧render都执行更新,替换为监听地图移动和缩放结束事件:
// 替换原有的map.on('render', ...) map.on('moveend', updateMarkers); map.on('zoomend', updateMarkers); // 地图加载完成后初始化一次 map.on('load', () => { if (map.isSourceLoaded('earthquakes')) updateMarkers(); });
内容的提问来源于stack exchange,提问作者Ibra
相关产品推荐
相关产品推荐

