baidu地图动态轨迹与轨迹箭头效果研究
一、问题引入
使用baidu地图 jsapi,生成轨迹,由于轨迹连线效果一般,需要增加动态效果或轨迹箭头连线
轨迹连线代码如下:
let linesArr = [] arrPoint.forEach((v, i) => { if (v.x && v.y) { linesArr.push(new BMap.Point(Number(v.x), Number(v.y))); } }) // 普通连线 this.lineLayer = new BMap.Polyline(linesArr, { strokeWeight: 2, strokeColor: '#06a5e0', strokeOpacity: 1 }); this.map.addOverlay(this.lineLayer);
二、动态轨迹方案
方案一、mapv
查阅文档,使用mapv插件,模拟实现动态迁徙图
官方demo地址:mapv (baidu.com)
demo效果如图:
结合示例代码如下,实现动态效果,可设置拖尾数量,和间距
具体参数配置可参考:baidu map mapv相关参数说明 - 盼星星盼太阳 - 博客园 (cnblogs.com)
// 迁徙动态图 const qianxiData = [] linesArr.forEach((v, i) => { if (i < linesArr.length - 1) { qianxiData.push({ from: i, to: i + 1, fromCenter: { x: v.lng, y: v.lat }, toCenter: { x: linesArr[i + 1].lng, y: linesArr[i + 1].lat } }) } }) const lineData = qianxiData.map(v => { return ({ geometry: { type: 'LineString', coordinates: [[v.fromCenter.x, v.fromCenter.y], [v.toCenter.x, v.toCenter.y]] }, }); }) console.log(lineData); var lineDataSet = new mapv.DataSet(lineData); var lineOptions = { strokeStyle: '#06a5e0', // shadowColor: 'rgba(255, 250, 50, 1)', // shadowBlur: 20, lineWidth: 6, zIndex: 100, draw: 'simple' } var lineLayer = new mapv.baiduMapLayer(this.map, lineDataSet, lineOptions); const timeData = [] qianxiData.forEach(v => { var delX = (v.toCenter.x - v.fromCenter.x) / 50; var delY = (v.toCenter.y - v.fromCenter.y) / 50; for (var i = 0; i < 50; i++) { var pointNLng = v.fromCenter.x + delX * i; var pointNLat = v.fromCenter.y + delY * i; timeData.push({ geometry: { type: 'Point', coordinates: [pointNLng, pointNLat] }, time: i, icon: sy }); } }) console.log('timeData', timeData); var timeDataSet = new mapv.DataSet(timeData); var timeOptions = { fillStyle: 'rgba(255, 250, 250, 0.5)', zIndex: 200, size: 2.5, animation: { type: 'time', stepsRange: { start: 0, end: 50 }, trails: 3, duration: 2, }, draw: 'simple' } var timeMapvLayer = new mapv.baiduMapLayer(this.map, timeDataSet, timeOptions);
实际使用中,有如下问题:
1. 此效果一般用于类似航线图,动画时长一致,往中心点聚集效果较好,若是轨迹连线,每段长度不一致,看上去速度快慢不一致,杂乱;
2.上述效果的原理是生成固定间距的点,依次按时间闪烁,形成迁徙动画效果。地图存在放大缩小情形,放大后,间距不合理,效果不好;
鉴于上述效果场景有限,查阅文档,官方类 BMapLib.LuShu
方案二、BMapLib.LuShu
官方文档地址:BMapLibrary - BMapLib.LuShu (baidu.com)
实例化该类后,可调用,start,end,pause等方法控制覆盖物的运动。
参考百度地图(20)-路书 - googlegis - 博客园 (cnblogs.com)实现效果如下:
代码如下:
setLushu(arrPoints) { var sy = new BMap.Symbol(BMap_Symbol_SHAPE_BACKWARD_OPEN_ARROW, { scale: 0.5,//图标缩放大小 strokeColor: '#fff',//设置矢量图标的线填充颜色 strokeWeight: 2,//设置线宽 }); const icon = require('@/assets/image/xxx.png')
const markerIcon = new BMap.Icon(icon, new BMap.Size(40, 35)) const arrPois = arrPoints.map(v => { return (new BMap.Point(Number(v.x), Number(v.y))); }) this.lushu = new BMapLib.LuShu(this.map, arrPois, { defaultContent: "", autoViewport: true, icon: markerIcon, speed: 1000, enableRotation: true, landmarkPois: [] }); this.lushu.start(this.endCallback) }, /* end_callback */ endCallback() { console.log('123', this.lushu); setTimeout(() => { this.lushu.start(this.endCallback) }, 500) },
在获取所有轨迹点位arrPoint处调用,
this.setLushu(arrPoint)
注意:在start方法处传入回调 endCallback 。用于在播放结束时,需要做一些操作,如清除覆盖物/重新播放等
需要对lushu.js源码修改,由于源码太长并且版本不一致也会有所不同(代码类似),只贴出修改部分
(c.prototype.start = function(end_callback) { var f = this, e = f._path.length; /* end_callback */ //自定义添加 当结束的时候判断回调函数 if(this._end_callback) { //当前路书未结束,新的直接返回 console.log("当前路书没有跑完,等跑完后在start"); return ; } if(end_callback) { //路书跑完结束回调 this._end_callback = end_callback; } /* end_callback */ ......
*********
在运动结束处调用回调方法
_moveNext: function(e) { var f = this; console.log('e',e); if (e < this._path.length - 1) { f._move(f._path[e], f._path[e + 1], f._tween.linear); } else { /* end_callback */ if(this._end_callback){ this._end_callback(); this._end_callback = null; } /* end_callback */ } },
此效果能实现轨迹从头到尾播放的效果
三、轨迹箭头效果
方案一、IconSequence
代码如下:
var sy = new BMap.Symbol(BMap_Symbol_SHAPE_BACKWARD_OPEN_ARROW, { scale: 0.5,//图标缩放大小 strokeColor: '#fff',//设置矢量图标的线填充颜色 strokeWeight: 2,//设置线宽 }); var icons = new BMap.IconSequence(sy, '100%', '5%', false);//设置为true,可以对轨迹进行编辑 //绘制折线以及样式 this.lineLayer = new BMap.Polyline(linesArr, { strokeColor: "#06a5e0",//设置颜色 strokeWeight: 6,//宽度 strokeOpacity: 1,//折线的透明度,取值范围0 - 1 enableEditing: false,//是否启用线编辑,默认为false enableClicking: false,//是否响应点击事件,默认为true icons: [icons], }); // this.lineLayer.setTop(true) this.map.addOverlay(this.lineLayer);
效果如图
方案二、BMapGL.Polyline
由于项目中使用地图版本不一致,new BMap.Polyline参数没有strokeTexture
在 GL 版本中移除了 IconSequence,可使用strokeTexture
代码如下:
var polyline = new BMapGL.Polyline( require("@/assets/json/mapRoute.json").map(p=>new BMapGL.Point(p.lng, p.lat)), { sequence: true, strokeTexture: { // width/height 需要是2的n次方 url: require('@/assets/image/marker-road-green.png'), width: 16, // 图片的宽度 height: 64 // 图片的高度 }, strokeWeight: 8, strokeOpacity: 1 }); map.addOverlay(polyline);
url图片如下:
可实现效果:
方案三、Polyline
思路:此方案使用生成线段基础api Polyline,原理还是将箭头作为线段,分别画出左右部分。通过线段起始点,计算出箭头角度。
在轨迹连线上图时调用:
// 普通连线 this.lineLayer = new BMap.Polyline(linesArr, { strokeWeight: 2, strokeColor: '#06a5e0', strokeOpacity: 1 }); this.map.addOverlay(this.lineLayer); this.addArrow(this.lineLayer, 8, Math.PI / 6);
具体代码如下:
clearArrow() { if (this.arrowArr && this.arrowArr.length) { this.arrowArr.forEach(v => { this.map.removeOverlay(v) }) this.arrowArr = [] } }, addArrow(polyline, length, angleValue) { //绘制箭头的函数 this.clearArrow() var linePoint = polyline.getPath();//线的坐标串 var arrowCount = linePoint.length;
for (var i = 1; i < arrowCount; i++) { //在中间点处绘制箭头 var pixelStart = this.map.pointToPixel(linePoint[i - 1]); var pixelEnd = this.map.pointToPixel(linePoint[i]); // 箭头位置偏移起点自定义百分比/有问题 /* if(pixelEnd.x - pixelStart.x > 50 || pixelEnd.y - pixelStart.y > 50){ const scale = (pixelEnd.x - pixelStart.x) / (pixelEnd.y - pixelStart.y); const pow = Math.pow(scale, 2) const newY = Math.sqrt(50 / (pow + 1)) const newX = scale * newY pixelEnd.x = pixelEnd.x - newX pixelEnd.y = pixelEnd.y - newY } */ // 箭头位置偏移线段中间 pixelEnd.x = (pixelEnd.x - pixelStart.x) * 0.5 + pixelStart.x; pixelEnd.y = (pixelEnd.y - pixelStart.y) * 0.5 + pixelStart.y; var angle = angleValue;//箭头和主线的夹角 var r = length; // r/Math.sin(angle)代表箭头长度 var delta = 0; //主线斜率,垂直时无斜率 var param = 0; //代码简洁考虑 var pixelTemX, pixelTemY;//临时点坐标 var pixelX, pixelY, pixelX1, pixelY1;//箭头两个点 if (pixelEnd.x - pixelStart.x == 0) { //斜率不存在是时 pixelTemX = pixelEnd.x; if (pixelEnd.y > pixelStart.y) { pixelTemY = pixelEnd.y - r; } else { pixelTemY = pixelEnd.y + r; } //已知直角三角形两个点坐标及其中一个角,求另外一个点坐标算法 pixelX = pixelTemX - r * Math.tan(angle); pixelX1 = pixelTemX + r * Math.tan(angle); pixelY = pixelY1 = pixelTemY; } else { //斜率存在时 delta = (pixelEnd.y - pixelStart.y) / (pixelEnd.x - pixelStart.x); param = Math.sqrt(delta * delta + 1); if ((pixelEnd.x - pixelStart.x) < 0) { //第二、三象限 pixelTemX = pixelEnd.x + r / param; pixelTemY = pixelEnd.y + delta * r / param; } else { //第一、四象限 pixelTemX = pixelEnd.x - r / param; pixelTemY = pixelEnd.y - delta * r / param; } //已知直角三角形两个点坐标及其中一个角,求另外一个点坐标算法 pixelX = pixelTemX + Math.tan(angle) * r * delta / param; pixelY = pixelTemY - Math.tan(angle) * r / param; pixelX1 = pixelTemX - Math.tan(angle) * r * delta / param; pixelY1 = pixelTemY + Math.tan(angle) * r / param; } var pointArrow = this.map.pixelToPoint(new BMap.Pixel(pixelX, pixelY)); var pointArrow1 = this.map.pixelToPoint(new BMap.Pixel(pixelX1, pixelY1)); // mapv方式箭头
var Arrow = new BMap.Polyline([ pointArrow, // linePoint[i], this.map.pixelToPoint(new BMap.Pixel(pixelEnd.x, pixelEnd.y)), pointArrow1 ], { strokeColor: "#06a5e0", strokeWeight: 2, strokeOpacity: 1 }); this.arrowArr.push(Arrow) // 覆盖物方式箭头上图 this.map.addOverlay(Arrow); }
},
需要在初始化地图时监听地图缩放,重新生成箭头,保证箭头大小不变,连线也可用此方式,点位多了(1W条)以后性能较好
// 初始化地图 loadMap() { this.map = initBMap(this.$refs.map); this.map.addEventListener("zoomend", this.mapChange) }, mapChange() { if (this.lineLayer) { this.addArrow(this.lineLayer, 8, Math.PI / 6); } },
效果如图: