You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 05:44:51