1. 程式人生 > >百度地圖大量點聚合js原始碼分析

百度地圖大量點聚合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.