相比于layer,marker 有着更为灵活的呈现方式,适用于地图上更加复杂的标注显示,而与此同时marker是经过dom渲染,而后叠加在地图图层上的,所以在性能上不及layer。在实际应用场景中,当地图须要大量渲染复杂的结构标注时,layer一般不能彻底知足需求,而此时marker就成了替代方案之一,但marker没有layer那么多的配置项去知足marker之间或者marker与地图之间的位置关系。本文利用source的cluster属性,着重解决marker在地图中显示重叠的问题。json
var popup = new mapboxgl.Popup({ offset: 25 }) .setText('popUpText'); var marker = new mapboxgl.Marker({ element: element, draggable: true, offset: [10, 0], }) .setLngLat([0, 0]) .setPopup(popup) .addTo(map);
marker 接收一个dom元素做为显示单位,默认是一个svg 定位图标。options 还支持配置偏移量及可拖拽配置,也能够对marker增长一个弹出窗口。dom
在mapbox中,想要直接达到marker具备边界检测的效果是比较困难的,目前的思路是经过两两计算marker间的距离,来控制marker的显示隐藏,避免重叠。但这种方法,计算量太大,可行度差。所以须要一种借助于相似于layer那种自适配地图显示的方案,来解决marker的重叠显示问题。svg
this.map.addSource("build-marker-source", { "type": "geojson", "data": { "type": "FeatureCollection", "features": [] }, "cluster": true, "clusterRadius": 35 })
经过给layer设置聚合属性的source来间接控制marker的显示隐藏。在source中设置cluster为true时,可使当前图层的marker之间获取边缘检测的效果,使得marker两两之间碰撞覆盖时,自动聚合成其中的一个(聚合目标的经纬度坐标与原始数据有必定误差),clusterRadius来设置聚合目标的半径大小。性能
this.map.addSource("build-marker-source", { "type": "geojson", "data": { "type": "FeatureCollection", "features": [] }, "cluster": true, "clusterRadius": 35 // 聚合半径 })
经过监听地图的数据更新,来实时的绘制与layer显示状态相同的marker。ui
this.map.on("data", (e) => { if (e.sourceId !== "build-marker-source" || !e.isSourceLoaded) return; this.map.on("move", updateMarkers); this.map.on("moveend", updateMarkers); updateMarkers(); });
在监听地图数据更新过程当中,过滤掉非操做marker的数据变更,及数据未加载完成的状态,有且只在知足更新条件时,更新地图标注显示。this
var markers = {}; var markersOnScreen = {}; const updateMarkers = () => { let sourceObj = this.map.getSource("build-marker-source") var newMarkers = {}; var features = this.map.querySourceFeatures("build-marker-source"); for (var i = 0; i < features.length; i++) { let coords = features[i].geometry.coordinates; let props = features[i].properties; let name = "" if (!props.cluster) continue; var id = props.cluster_id; if (id) { sourceObj.getClusterLeaves(id, 10, 0, (e, f) => { name = f[0].properties.name }) } else { id = props.id name = props.name } var marker = markers[id]; if (!marker && name) { let el = document.createElement("div"); el.className = "popUpBox"; el.innerText = name marker = markers[id] = new creeper.Marker({element: el}).setLngLat(coords); } newMarkers[id] = marker; if (!markersOnScreen[id]) marker && marker.addTo(this.map); } // for every marker we've added previously, remove those that are no longer visible for (id in markersOnScreen) { if (!newMarkers[id]) markersOnScreen[id] && markersOnScreen[id].remove(); } markersOnScreen = newMarkers; }
流程图:spa
变量 | 描述 |
---|---|
markers | 当前地图标注总集合,经过聚合id或资源自定义uid为主键 |
markersOnScreen | 上轮地图数据变动标注集合,即本轮数据变动前,地图显示标注集合 |
newMarkers | 本轮地图数据变动标注集合,当前地图需显示的标注集合 |
利用this.map.querySourceFeatures("build-marker-source")
获取当前地图可视的标注信息数据集合,经过遍历集合来查看当前可视marker是否为聚合类,若是为非聚合类的话,当前marker数据就是原始数据能够直接标记在地图当中,若是遍历目标为聚合类,则须要利用资源对象中的getClusterLeaves
方法,经过cluster_id来查找原始数据源,由于聚合以后的marker坐标,失去了原有的properties,取而代之的是聚合相关的内容属性,所以想要获取marker的name及原始经纬度,则须要二次查询。
经过自定义属性中的uid,或者cluster_id来循环查找markers里面是否已经实例化当前marker。每一轮次的可视feature遍历,都去重置newMarkers,将符合可视条件的marker以key-value的方式赋值到newMarkers,并在markersOnScreen中遍历旧的marker是否存在于newMarkers,若是不存在则在当前地图中移除。逻辑末尾,再将newMarkers赋值到markersOnScreen上,等待下一轮次的数据更新,来判断相关marker的显示隐藏。code
至此,经过source上的cluster配置,解决了关于地图marker的重叠显示问题,实现了经过地图缩放,来自适应的显示相关标注点,若是有更好的方法欢迎交流讨论。对象