百度地图自定义图层实现海量点聚合
2019-03-04 23:07

百度地图自定义图层

    百度地图开发中,地图的覆盖层提供我们往往根据实际项目的需求来展现符合产品的覆盖物,以此来契合整个系统的UI风格等。简单的开发,可以使用百度地图提供的marker,InfoWindow,Label等来实现基本的点位标注,信息展示的。但对于复杂的需求,我们就需要通过自定义覆盖层来实现了。

   下面我们看一下自定义覆盖的实现方式:

//1,定义一个覆盖物,原型指向BMap的Overlay对象。
function myOver(pt){
    this._pt=pt;
}
myOver.prototype=new BMap.Overlay();

//2,定义覆盖物的初始化方法,当调用map.addOverlay方法时,API会调用此方法。
myOver.prototype.initialize=function(map){
    this._map=map;
    //...
    return div;
}
//3,定义覆盖物的绘制方法,在地图变化时(如:拖拽,缩放)会调用此方法。
myOver.prototype.draw=function(){

}
//4,覆盖物集成Overlay的show,hide方法,在调用时会修改初始化方法返回的div的display属性。
//如果元素比较复杂,可以自己实现show,hide的方法。
myOver.protoptye.show=function(){

}
myOver.prototype.hide=function(){

}

实现一个自定义地图覆盖物只需要以上四步就可以完成,往往我们平时只需要实现三步即可。

下面我们通过实现一个具体的自定义覆盖物的实例来进一步学习如果使用地图的自定义覆盖

自定义覆盖物实现地图上的海量点聚合展示

首先,实现海量点我们首先想到的是通过SVG,或者Canvas来实现。他们效率更高。

可能有人要问:为什么不能使用DIV来实现呢?

我也想用div来实现啊,div多方便,多省事啊。但是DOM元素的渲染效率是很慢的,尤其上万,十万的div展示在页面上,浏览器是撑不住的,具体浏览器能展现的dom元素极限我没做过测试,不过上万点的div展示我测试过,chrome都带不动,即是能展示出来,那也是卡的无语。所以我们还是老老实实的用Canvas来实现海量点的展示吧。至于SVG,理论上也是可以的(没做过测试)。

var getExtendedBounds = function(map, bounds, gridSize){
	bounds = cutBoundsInRange(bounds);
	var pixelNE = map.pointToPixel(bounds.getNorthEast());
	var pixelSW = map.pointToPixel(bounds.getSouthWest()); 
	pixelNE.x += gridSize;
	pixelNE.y -= gridSize;
	pixelSW.x -= gridSize;
	pixelSW.y += gridSize;
	var newNE = map.pixelToPoint(pixelNE);
	var newSW = map.pixelToPoint(pixelSW);
	return new BMap.Bounds(newSW, newNE);
};

/**
* 按照百度地图支持的世界范围对bounds进行边界处理
* @param {BMap.Bounds} bounds BMap.Bounds的实例化对象
*
* @return {BMap.Bounds} 返回不越界的视图范围
*/
var cutBoundsInRange = function (bounds) {
        var maxX = getRange(bounds.getNorthEast().lng, -180, 180);
	var minX = getRange(bounds.getSouthWest().lng, -180, 180);
	var maxY = getRange(bounds.getNorthEast().lat, -74, 74);
        var minY = getRange(bounds.getSouthWest().lat, -74, 74);
	return new BMap.Bounds(new BMap.Point(minX, minY), new BMap.Point(maxX, maxY));
}; 

/**
 * 对单个值进行边界处理。
 * @param {Number} i 要处理的数值
 * @param {Number} min 下边界值
 * @param {Number} max 上边界值
 * 
 * @return {Number} 返回不越界的数值
 */
var getRange = function (i, mix, max) {
	mix && (i = Math.max(i, mix));
	max && (i = Math.min(i, max));
	return i;
};

//传入BMap.Point的数组几何
function clusterPts(pts){
	this._pts=pts;
}
clusterPts.prototype=new BMap.Overlay();
clusterPts.prototype.initialize=function(map){
	this._map=map;
	//创建画布元素并添加到图层容器中
	let cvs=document.createElement("canvas");
	map.getPanes().markerPane.appendChild(cvs);
	this._cvs=cvs;
	return cvs;
}
clusterPts.prototype.draw=function(){
	let clusters=[];
	let arr=[];
	let arr2=[];
				
	//设置svg/canvas的合适位置
	let size=this._map.getSize();
	let center=this._map.getCenter();
	//获取地图中心点相对于图层的位置
	let rel_xy=this._map.pointToOverlayPixel(center);
	//获取地图中心点的绝对位置
	let abs_xy=this._map.pointToPixel(center);
	//两个位置点的差值为画布的位置
	let left=rel_xy.x-abs_xy.x;
	let top=rel_xy.y-abs_xy.y;
	this._cvs.setAttribute("style","position:absolute;left:"+left+"px;top:"+top+"px;z-index:1");
	//设置画布的大小为地图的大小
	this._cvs.setAttribute("width",size.width);
	this._cvs.setAttribute("height",size.height);
	let ctx=this._cvs.getContext("2d");
	//清除画布内容
	ctx.clearRect(0,0,this._cvs.width,this._cvs.height);
	//聚合的距离
        let distance=200;
	let bound=this._map.getBounds();
        //获取地图向外扩展distance大小后的范围
	let _bound=getExtendedBounds(this._map,bound,distance);
	//点聚合的运算
	this._pts.forEach(it=>{
	    //判断点是否在地图展示范围内
	  if(_bound.containsPoint(it)){
	       //遍历点的集合,跟聚合数组中的元素做比较,如果在聚合范围内则聚合,
	       //如果没有在聚合范围内则在聚合数组中加入一项,并记录聚合的点数量
		let position=this._map.pointToPixel(it);	
		  if(arr.length==0){
			arr.push({count:1,pos:position});
		  }else{
			let flag=true;
			for(let i=0;i<arr.length;i++){
				let c=arr[i];
				let pos=c.pos;
				if(Math.abs(pos.x-position.x)<=distance&&Math.abs(pos.y-position.y)<=distance){
					c.count++;
					flag=false;
					break;
				}
			}
			if(flag){
				arr.push({count:1,pos:position});
			}
		}
	   }
	});
        //遍历聚合的数据并且在画布上渲染,count为1的单点与聚合的点采用不同的形式进行渲染。
	arr.forEach(item=>{
			let position=item.pos;
			if(item.count==1){
						
				ctx.beginPath();
				ctx.fillStyle="blue";
				ctx.strokeStyle="#FFF";
				ctx.lineWidth=2;
				ctx.arc(position.x,position.y,5,0,2*Math.PI);
				ctx.closePath();
				ctx.stroke();
				ctx.fill();
			}else{
				ctx.globalAlpha =0.6;
				ctx.beginPath();
				ctx.fillStyle=this._color;
						
				ctx.ellipse(position.x,position.y,40,20,0,0,2*Math.PI,true);
				ctx.closePath();
				ctx.fill();
				ctx.globalAlpha =1;
				ctx.fillStyle="red";
				ctx.font="bold 17px Arial";
				ctx.fillText(item.count,position.x-ctx.measureText(item.count).width/2,position.y+6);
			}	

});

海量点聚合的自定义覆盖物如上面实现,使用起来也很方便。

//将海量点的数组传入到自定义覆盖物对象中
let css2=new this.clusterPts(pts2);
//将覆盖物添加到地图上				
this.map.addOverlay(css2);

海量点聚合的效果如下:

image.png


原创文章,转载请注明来自:妹纸前端-www.webfront-js.com.
阅读(397)
辛苦了,打赏喝个咖啡
微信
支付宝
妹纸前端
妹纸前端工作室 | 文章不断更新中
京ICP备16005385号-1