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