百度地圖大量點聚合js原始碼分析
百度地圖中的點聚合主要有兩個檔案一個控制聚合即MarkerClusterer.js,一個控制顯示的樣子TextIconOverlay.js,這裡吐槽一下,官方給的文件並沒有說明!!!
http://api.map.baidu.com/library/TextIconOverlay/1.2/src/TextIconOverlay_min.js(無格式壓縮版)
http://api.map.baidu.com/library/TextIconOverlay/1.2/src/TextIconOverlay.js
http://api.map.baidu.com/library/MarkerClusterer/1.2/src/MarkerClusterer_min.js
http://api.map.baidu.com/library/MarkerClusterer/1.2/src/MarkerClusterer.js
先看MarkerClusterer,一般建立聚合時的程式碼如下.這裡的for迴圈實際上在隨機的建立一些經緯度座標,
var markers = []; for(i=0; i < 10000; i++) { pt = new BMap.Point(Math.random() * 5 + 75, Math.random() * 10 + 21); markers.push(new BMap.Marker(pt)); } var st = [ { url: "m4.png", size:new BMap.Size(92, 92) } ]; var markerClusterer = new BMapLib.MarkerClusterer(map, {markers:markers,styles:st});
對應到原始碼部分
/** *@exports MarkerClusterer as BMapLib.MarkerClusterer */ var MarkerClusterer = /** * MarkerClusterer * @class 用來解決載入大量點要素到地圖上產生覆蓋現象的問題,並提高效能 * @constructor * @param {Map} map 地圖的一個例項。 * @param {Json Object} options 可選引數,可選項包括:<br /> * markers {Array<Marker>} 要聚合的標記陣列<br /> * girdSize {Number} 聚合計算時網格的畫素大小,預設60<br /> * maxZoom {Number} 最大的聚合級別,大於該級別就不進行相應的聚合<br /> * minClusterSize {Number} 最小的聚合數量,小於該數量的不能成為一個聚合,預設為2<br /> * isAverangeCenter {Boolean} 聚合點的落腳位置是否是所有聚合在內點的平均值,預設為否,落腳在聚合內的第一個點<br /> * styles {Array<IconStyle>} 自定義聚合後的圖示風格,請參考TextIconOverlay類<br /> */ BMapLib.MarkerClusterer = function(map, options){ if (!map){ return; } this._map = map; this._markers = []; this._clusters = []; var opts = options || {}; this._gridSize = opts["gridSize"] || 60; this._maxZoom = opts["maxZoom"] || 18; this._minClusterSize = opts["minClusterSize"] || 2; this._isAverageCenter = false; if (opts['isAverageCenter'] != undefined) { this._isAverageCenter = opts['isAverageCenter']; } this._styles = opts["styles"] || []; var that = this; this._map.addEventListener("zoomend",function(){ that._redraw(); }); this._map.addEventListener("moveend",function(){ that._redraw(); }); var mkrs = opts["markers"]; isArray(mkrs) && this.addMarkers(mkrs); };
可以看到原始碼中BMapLib.MarkerClusterer = function(map, options)的引數,第一個為map,第二個為options提供了很多選項.每個選項上面說的很清楚了.程式碼裡也用opts["xxx"]來取出其中的資料.||後面意思為,不寫的屬性預設值.可以看到原始碼最後兩行
var mkrs = opts["markers"];
isArray(mkrs) && this.addMarkers(mkrs);
實際上是把建立MarkerClusterer時候傳入的marker陣列拿出來,先判斷是否是一個Array即陣列,再呼叫addMarkers方法進行具體操作.我們看到addMarkers方法的原始碼裡,可以看到,其對陣列進行了一個遍歷新增的過程.最後進行this._createClusters()的具體操作.
/**
* 新增要聚合的標記陣列。
* @param {Array<Marker>} markers 要聚合的標記陣列
*
* @return 無返回值。
*/
MarkerClusterer.prototype.addMarkers = function(markers){
for(var i = 0, len = markers.length; i <len ; i++){
this._pushMarkerTo(markers[i]);
}
this._createClusters();
};
插播一個問題:聚合物難以清除的問題
網上給的解決方法大多是
如果要清除必須用markerClusterer.clearMarkers()清除,map.clearOverlays()拖動的時候會再出現
但實際操作來看的話如果想刪除再生產是不行的.這裡給出一個解決方案.
先初始化一個MarkerClusterer
//初始化打點資料
var markers=[];
//初始化點聚合
var markerClusterer=new BMapLib.MarkerClusterer(map, {markers:markers});
可以用markerClusterer.clearMarkers()清除,後面如果需要再新增,不用再new BMapLib.MarkerClusterer 可以直接使用markerClusterer.addMarkers.其實原始碼裡也可以看出來,它也是這麼做的.原始碼在建立BMapLib.MarkerClusterer最後也是呼叫了this.addMarkers方法.
另外:這裡要注意最後一句話 styles {Array<IconStyle>} 自定義聚合後的圖示風格,請參考TextIconOverlay類.有些人疑惑我們並沒有建立TextIconOverlay,那麼更改樣式圖片等行為是怎麼實現的,實際上這個便是兩個js檔案聯絡的地方.那麼我們就在當前MarkerClusterer.js檔案找TextIconOverlay有沒有出現.發現如下
/**
* @ignore
* Cluster
* @class 表示一個聚合物件,該聚合,包含有N個標記,這N個標記組成的範圍,並有予以顯示在Map上的TextIconOverlay等。
* @constructor
* @param {MarkerClusterer} markerClusterer 一個標記聚合器示例。
*/
function Cluster(markerClusterer){
this._markerClusterer = markerClusterer;
this._map = markerClusterer.getMap();
this._minClusterSize = markerClusterer.getMinClusterSize();
this._isAverageCenter = markerClusterer.isAverageCenter();
this._center = null;//落腳位置
this._markers = [];//這個Cluster中所包含的markers
this._gridBounds = null;//以中心點為準,向四邊擴大gridSize個畫素的範圍,也即網格範圍
this._isReal = false; //真的是個聚合
this._clusterMarker = new BMapLib.TextIconOverlay(this._center, this._markers.length, {"styles":this._markerClusterer.getStyles()});
//this._map.addOverlay(this._clusterMarker);
}
可以看到最後一行,建立了TextIconOverlay.並傳入了獲取到的引數,看到沒有我們找到了styles!!!,也就是說,自動為我們建立了TextIconOverlay,那麼我們就去看看,TextIconOverlay檔案幹了什麼.
/**
*@exports TextIconOverlay as BMapLib.TextIconOverlay
*/
var TextIconOverlay =
/**
* TextIconOverlay
* @class 此類表示地圖上的一個覆蓋物,該覆蓋物由文字和圖示組成,從Overlay繼承。文字通常是數字(0-9)或字母(A-Z ),而文字與圖示之間有一定的對映關係。
*該覆蓋物適用於以下類似的場景:需要在地圖上新增一系列覆蓋物,這些覆蓋物之間用不同的圖示和文字來區分,文字可能表示了該覆蓋物的某一屬性值,根據該文字和一定的對映關係,自動匹配相應顏色和大小的圖示。
*
*@constructor
*@param {Point} position 表示一個經緯度座標位置。
*@param {String} text 表示該覆蓋物顯示的文字資訊。
*@param {Json Object} options 可選引數,可選項包括:<br />
*"<b>styles</b>":{Array<IconStyle>} 一組圖示風格。單個圖表風格包括以下幾個屬性:<br />
* url {String} 圖片的url地址。(必選)<br />
* size {Size} 圖片的大小。(必選)<br />
* anchor {Size} 圖示定位在地圖上的位置相對於圖示左上角的偏移值,預設偏移值為圖示的中心位置。(可選)<br />
* offset {Size} 圖片相對於可視區域的偏移值,此功能的作用等同於CSS中的background-position屬性。(可選)<br />
* textSize {Number} 文字的大小。(可選,預設10)<br />
* textColor {String} 文字的顏色。(可選,預設black)<br />
*/
BMapLib.TextIconOverlay = function(position, text, options){
this._position = position;
this._text = text;
this._options = options || {};
this._styles = this._options['styles'] || [];
(!this._styles.length) && this._setupDefaultStyles();
};
這裡就很清楚,剛才傳進來三個引數的意義,第一個是位置,第二個是顯示的文字,第三個包含了一些可選項上面註釋已經很清楚了.
可以看出options中最重要的是styles.若建立時候自己定義了則按照定義的樣式來,若未定義,則取預設風格即:this._setupDefaultStyles()
TextIconOverlay.prototype._setupDefaultStyles = function(){
var sizes = [53, 56, 66, 78, 90];
for(var i = 0, size; size = sizes[i]; i++){
this._styles.push({
url:_IMAGE_PATH + i + '.' + _IMAGE_EXTENSION,
size: new BMap.Size(size, size)
});
}//for迴圈的簡潔寫法
};
這裡根據聚合裡包含的點的個數,顯示不同的樣式.可以看到共有五種樣子.
url:_IMAGE_PATH + i + '.' + _IMAGE_EXTENSION, size: new BMap.Size(size, size)
也可以找到_IMAGE_PATH和_IMAGE_EXTENSION 實際上就是進行了拼接而已.
/**
* 圖片的路徑
* @private
* @type {String}
*/
var _IMAGE_PATH = 'http://api.map.baidu.com/library/TextIconOverlay/1.2/src/images/m';
/**
* 圖片的字尾名
* @private
* @type {String}
*/
var _IMAGE_EXTENSION = 'png';
事實上MarkerClusterer.js裡的
Cluster.prototype.updateClusterMarker = function ()函式在更新時也會修改樣式.
/**
* 更新該聚合的顯示樣式,也即TextIconOverlay。
* @return 無返回值。
*/
Cluster.prototype.updateClusterMarker = function () {
if (this._map.getZoom() > this._markerClusterer.getMaxZoom()) {
this._clusterMarker && this._map.removeOverlay(this._clusterMarker);
for (var i = 0, marker; marker = this._markers[i]; i++) {
this._map.addOverlay(marker);
}
return;
}
if (this._markers.length < this._minClusterSize) {
this._clusterMarker.hide();
return;
}
this._clusterMarker.setPosition(this._center);
this._clusterMarker.setText(this._markers.length);
var thatMap = this._map;
var thatBounds = this.getBounds();
this._clusterMarker.addEventListener("click", function(event){
thatMap.setViewport(thatBounds);
});
};
具體在TextIconOverlay.js檔案中還可以修改很多.讀者可以自己分析.比如更改顯示的樣式_image_path就可以看到點聚合圖片url,修改即可改變聚合點的樣式。另外還可以在MarkerClusterer.js中改寫原始碼.提升效率,這裡可以參考https://blog.csdn.net/educast/article/details/69523705.