⭐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的重疊顯示問題,實現了通過地圖縮放,來自適應的顯示相關標註點,如果有更好的方法歡迎交流討論。