⭐Mapbox GL JS學習探索系列(4) - Marker重疊解決方案

簡介

在這裏插入圖片描述
相比於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

marker重疊顯示解決方案

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

相關文章
相關標籤/搜索