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;
        // 定义mapv连线/箭头数组
            /* let data_poly1 = []
            let data_poly2 = []
            let data_polyline = []
            const options_lines = {
                // fillStyle: '#15f6fe',
                strokeStyle: '#15f6fe',
                lineWidth: 2,
            } */
            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方式箭头
          // mapv箭头/连线
                /* data_poly1.push({
                    geometry: {
                        type: 'LineString',
                        coordinates: [
                            [pointArrow.lng, pointArrow.lat],
                            [this.map.pixelToPoint(new BMap.Pixel(pixelEnd.x, pixelEnd.y)).lng, this.map.pixelToPoint(new BMap.Pixel(pixelEnd.x, pixelEnd.y)).lat]
                        ]
                    }
                })
                data_poly2.push({
                    geometry: {
                        type: 'LineString',
                        coordinates: [
                            [pointArrow1.lng, pointArrow1.lat],
                            [this.map.pixelToPoint(new BMap.Pixel(pixelEnd.x, pixelEnd.y)).lng, this.map.pixelToPoint(new BMap.Pixel(pixelEnd.x, pixelEnd.y)).lat]
                        ]
                    }
                })
                data_polyline.push({
                    geometry: {
                        type: 'LineString',
                        coordinates: [
                            [linePoint[i-1].lng, linePoint[i-1].lat],
                            [linePoint[i].lng, linePoint[i].lat]
                        ]
                    }
                }) */
                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);
            }
         
       /* const dataSet_poly1 = new mapv.DataSet(data_poly1)
            const dataSet_poly2 = new mapv.DataSet(data_poly2)
            const dataSet_polyline = new mapv.DataSet(data_polyline)
            // mapv方式箭头/连线上图
            this.arrowLeftMapvLayer = new mapv.baiduMapLayer(this.map, dataSet_poly1, options_lines);
            this.arrowRightMapvLayer = new mapv.baiduMapLayer(this.map, dataSet_poly2, options_lines);
            this.polylineMapvLayer = new mapv.baiduMapLayer(this.map, dataSet_polyline, options_lines); */
        },

需要在初始化地图时监听地图缩放,重新生成箭头,保证箭头大小不变,连线也可用此方式,点位多了(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);
            }
        },

效果如图:

 

posted @ 2023-10-09 15:32  盼星星盼太阳  阅读(709)  评论(0编辑  收藏  举报