問題說明
在地圖開發中,當地圖中繪製大量的標記點後,無論是拖動或者縮放,都會感覺到明顯的卡頓現象。(一般超過800個點後就比較明顯了).在平時的工作業務中,由於公司的實時監控頁面需要展現5000-20000車輛的實時定位跟蹤,特別是切換到車輛密集的港口碼頭卡頓現象非常嚴重(如下圖),看起來非常難看,使用者體驗也非常差。在此寫下一些開發優化中的心得體會(本文中使用的是高德地圖為參考)。
解決思路
-
首先我們應該將地圖上的所有覆蓋物(包括Marker,Icon,Text,Polygon)分組新增到多個OverlayGroup中對每一類統一管理。部分覆蓋物預設隱藏,需要時才展現。
-
不要將大量的覆蓋物資訊都直接掛載在地圖物件上,可以按需載入,減少地圖在移動後重新渲染的工作量
-
合理的使用覆蓋物聚合(聚合雖然會減少卡頓,但是覆蓋物數量太多依舊會比較卡)
解決方法
首先,新增一個地圖設定勾選皮膚,將除車輛點標記以外的所有覆蓋物按型別分類,新增到一個個單獨的OverlayGroup中預設不掛載在地圖上,在勾選時才呼叫OverlayGroup.setMap(map)方法新增到地圖上。去掉了這些不必要覆蓋物,首次進入時立馬清爽了許多。
儘管去掉了各類網點,文字,等覆蓋物,但是我們需要渲染的車輛標記點依舊是非常多的。在此基礎上,我們採用按螢幕載入,篩選出螢幕內的點,螢幕外的標記點不渲染,監控每次地圖的moveend,zoomend,resize,每次的改變後重新計算螢幕內的標記點,當螢幕內標記點較少,比如200以下(這個隨個人需要定),直接繪製在地圖上。當螢幕內標記點比較多,大於200,呼叫聚合方法,將標記點聚合後繪製在地圖上。
參考程式碼
/**監聽地圖拖拽,放大事件,根據螢幕範圍動態渲染點位 */
reloadMarks(){
AMap.event.addListener(this.map,`moveend`,()=> {
this.computeMarkers();
})
AMap.event.addListener(this.map,`zoomend`,()=> {
this.computeMarkers();
})
AMap.event.addListener(this.map,`resize`,()=> {
this.computeMarkers();
})
},
/** 根據當前螢幕範圍帥選marker */
computeMarkers(){
this.newViewData={};
//獲取當前檢視範圍
let now_bounds = this.map.getBounds();
//遍歷車輛資料,為了減少後臺傳入的重複資料,我的車輛資訊列表一直使用物件儲存
for(let sel_deviceNo in this.carDataObj0){
let item=this.carDataObj0[sel_deviceNo];
//判斷當前點的座標是否存在於檢視內
if(now_bounds.contains(item.gcj02_coords.split(`,`))){
//將當前螢幕內的檢視點儲存
this.newViewData[sel_deviceNo] = item;
}
}
this.renderMarker();
now_bounds=null;
},
/** 建立聚合**/
createCluser(markerArr) {
AMap.plugin([`AMap.MarkerClusterer`], () => {
this.cluster = new AMap.MarkerClusterer(this.map, markerArr, {
gridSize: 80,
renderCluserMarker,
minClusterSize: 1,
maxZoom: 18
});
})
},
/** 將markers渲染到map上 */
renderMarker(){
//判斷目前是否有當前覆蓋物組
!this.overLayGroup && this.overLayGroup = new AMap.OverlayGroup([]);
let MarkerAddArr = [];
for(let sel_deviceNo in this.newViewData){
/**我的所有建立的Marker點物件都是長期存在於this.markerObj中,並不銷燬,每次資料更
新後呼叫marker物件的setPosition(),setAngle(),setIcon()等方法改變狀態,再篩選出
需要掛載上的一起加到地圖上 **/
if(this.markerObj[sel_deviceNo]){
MarkerAddArr.push(this.markerObj[sel_deviceNo]);
}
}
this.overLayGroup.clearOverlays();
//超過200個點則開啟聚合模式 且在聚合模式下不渲染皮膚展示點
if(MarkerAddArr.length>=200){
if(this.cluster){
this.cluster.clearMarkers();
this.cluster.setMarkers(MarkerAddArr);
}else{
this.createCluser(MarkerAddArr);
}
}else{
if(this.cluster){
this.cluster.clearMarkers();
}
this.overLayGroup.addOverlays(MarkerAddArr);
this.overLayGroup.setMap(this.map);
}
MarkerAddArr=null;
},
複製程式碼
程式碼直接從業務中複製的,耦合度比較高,還請見諒。
做完了標記點按螢幕分組載入之後,我們在新增一個簡單的防抖方法,防止連續的拖動或者縮放導致computeMarkers方法被觸發,減少方法呼叫次數
const debounce = (fn, wait=500) =>{
return function() {
clearTimeout(fn.timer)
fn.timer = setTimeout(fn.bind(this, ...arguments), wait)
}
}
複製程式碼
完成後效果如下:減少了地圖的計算與渲染,每次只計算視野內的點數,因此總數的大小不會影響地圖效能,當視野內超過200個點都會聚合,200以下時顯示。
平時寫文章比較少,文筆很差,還請多多見諒,謝謝。