⭐Mapbox GL JS學習探索系列(4) – Marker重疊解決方案
- 2019 年 11 月 22 日
- 筆記
版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/j_bleach/article/details/103145507
簡介

相比於layer,marker 有著更為靈活的呈現方式,適用於地圖上更加複雜的標註顯示,而與此同時marker是通過dom渲染,然後疊加在地圖圖層上的,因此在性能上不及layer。在實際應用場景中,當地圖需要大量渲染複雜的結構標註時,layer通常不能完全滿足需求,而此時marker就成了替代方案之一,但marker沒有layer那麼多的配置項去滿足marker之間或者marker與地圖之間的位置關係。本文利用source的cluster屬性,著重解決marker在地圖中顯示重疊的問題。
基礎用法
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增加一個彈出窗口。
marker重疊顯示解決方案
在mapbox中,想要直接達到marker具有邊界檢測的效果是比較困難的,目前的思路是通過兩兩計算marker間的距離,來控制marker的顯示隱藏,避免重疊。但這種方法,計算量太大,可行度差。因此需要一種藉助於類似於layer那種自適配地圖顯示的方案,來解決marker的重疊顯示問題。
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。
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的數據變動,及數據未載入完成的狀態,有且只在滿足更新條件時,更新地圖標註顯示。
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; }
流程圖:

變數 |
描述 |
---|---|
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的顯示隱藏。
總結
至此,通過source上的cluster配置,解決了關於地圖marker的重疊顯示問題,實現了通過地圖縮放,來自適應的顯示相關標註點,如果有更好的方法歡迎交流討論。