高德地图轨迹回放/轨迹播放

前言

本篇文章主要介绍高德地图的轨迹回放或播放的实现过程,是基于vue2实现的功能,同时做一些改动也是能够适配vue3的。其中播放条是用的是element UI中的el-slider组件,包括使用到的图标也是element UI自带的。可以实现轨迹的播放、暂停、停止、播放倍数,以及播放拖拽,涉及到的高德地图的相关权限申请,这里就不再赘述,好了,废话不多说,效果图附上。

效果图 


一、地图初始化

首先,需要在组件dom加载完毕后初始化地图,这里小谭直接用的new AMap.Map方法进行初始化,需要在index.html引入高德的服务。

<script src="https://webapi.amap.com/maps?v=2.0&key=你的key"></script>

其次,在引入高德服务之后,需要在单独引入高德AMapUI 组件库,因为轨迹播放是基于该组件库实现的,引入示例:

<!--引入UI组件库(1.1版本) -->
<script src="//webapi.amap.com/ui/1.1/main.js"></script>

最后,就可以进行初始化地图了,注意需要在组件dom加载完毕才能进行初始化!其中this.map是地图实例,附上代码:

 this.map = new AMap.Map('myMap', {
            zoom: 10, //级别
            center:[120.209758, 30.246809], //中心点坐标 默认在杭州
 });

二、轨迹插件初始化

在地图初始化完成之后,可以引入一些需要的插件,这里就不再过多赘述,我们直接引入AMapUI,我们这里用到的是PathSimplifier模块,故只需要引入该模块即可,附上代码:

//加载PathSimplifier,loadUI的路径参数为模块名中 'ui/' 之后的部分
new AMapUI.load(['ui/misc/PathSimplifier'], PathSimplifier => {
    if (!PathSimplifier.supportCanvas) {
        alert('当前环境不支持 Canvas!');
        return;
    }
    if (this.pathList?.length) {
        //启动页面
        this.initPage(PathSimplifier);
    }
});

其中,涉及到的this.pathList是我这边后端返回坐标点信息,this.pathList结构如下:

this.pathList = [
    [
        120.79580028, // 经度
        30.03570354 // 纬度
    ],
    ...
    
];

this.initPage方法如下,需要注意的是,方法内声明的content是轨迹播放时展示的车辆图标,如果不需要可以删掉,PathSimplifier中的配置请参照高德地图轨迹展示的开发文档,还有方法最后调用的this.cruiseInit方法已经放到下一部分了。

initPage(PathSimplifier) {
    let content = PathSimplifier.Render.Canvas.getImageContent(
        '/img/car1.png',
        () => {
            //图片加载成功,重新绘制一次
            this.pathSimplifierIns.renderLater();
        },
        function onerror(e) {
            this.$message({ type: 'error', message: '图片加载失败!' });
        }
    );
    this.pathSimplifierIns = new PathSimplifier({
        zIndex: 100,
        map: this.map,
        getPath: function (pathData, pathIndex) {
            return pathData.path;
        },
        renderOptions: {
            //轨迹线的样式
            getPathStyle: (pathItem, zoom) => {
                return {
                    pathLineStyle: {
                        strokeStyle: "red",
                        lineWidth: 6,
                        dirArrowStyle: true,
                    },
                };
            },
            pathNavigatorStyle: {
                initRotateDegree: 180,
                width: 20,
                height: 35,
                autoRotate: true,
                content,
            },
        },
    });
    this.cruiseInit(); //巡航器初始化
}

三、巡航器初始化

巡航器初始化方法this.cruiseInit代码如下:

cruiseInit() {
    let pathSimplifierIns = [{ path: this.pathList, color: '#28F' }];
    this.pathSimplifierIns.setData(pathSimplifierIns);
    this.pointSum = 0;
    pathSimplifierIns.forEach((item, index) => {
        this.pointSum += item.path.length;
    });

    this.marksIndex = marksIndex;
    this.cruiseStop();//如果已经存在巡航器,则停止播放
}

其中this.pointSum是为了记录巡航器的最终点数,方便对应到播放条的最大值。


四、巡航器的播放暂停等功能

巡航器的播放、暂停以及倍数的方法如下:

// 创建一个巡航器
createdCruise(index) {
    // 判断是否传入index
    let cruiseIndex;
    if (index != undefined) {
        cruiseIndex = index;
        this.cruiseIndex = index;
    } else {
        cruiseIndex = this.cruiseIndex;
    }
    let cruise = this.pathSimplifierIns.createPathNavigator(
        cruiseIndex, //关联第index条轨迹
        {
            loop: false, //循环播放
            speed: this.speedList[this.speedValue].speed, //速度
        }
    );

    if (this.cruise) {
        // 清空走过的路线
        this.cruise.destroy();
        this.cruise = null;
    }

    return cruise;
},
// 开始播放
cruiseStart() {
    this.isPlay = true;

    if (this.cruise && !this.cruise.isCursorAtPathEnd() && !this.cruise.isCursorAtPathStart() && !this.isComplete) {
        // 路段未开始并且没有结束的时候 暂停恢复动画 并且动画没有完成的时候
        this.cruise.resume();
        return;
    }
    this.isComplete = false;
    if (this.cruiseIndex == 0) {
        this.cruiseStop();
        return;
    }

    this.cruise = this.createdCruise();

    // 判断是否传入初始坐标
    if (this.startPoint) {
        this.cruise.start(this.startPoint);
        this.startPoint = 0;
    } else {
        this.cruise.start();
    }
    this.cruise.on('move', e => {
        let idx = this.cruise.cursor.idx;
        let { address, gpsTime, speed } = this.pathList[idx];
        let trackAddress = {
            address,
            gpsTime,
            speed,
        };
        this.$emit('changeData', 'trackAddress', trackAddress);
        let [min, max] = this.marksIndex[this.cruiseIndex];
        this.sliderValue = idx + min;
    });
    // 巡航完成事触发
    this.cruise.on('pause', () => {
        if (this.cruise && this.cruise.isCursorAtPathEnd()) {
            this.cruiseStart();
        }
    });
},

// 暂停播放
cruisePause() {
    this.cruise.pause();
    this.isPlay = false;
},
// 停止播放
cruiseStop() {
    if (this.cruise) {
        // 清空走过的路线
        this.cruise.destroy();
    }
    // 停止播放
    this.isPlay = false;
    this.isComplete = true;
    this.cruiseIndex = -1;
    // 为重新播放准备
    this.cruise = this.createdCruise();
    this.cruiseIndex = -1;
    this.sliderValue = 0;
},
// 速度改变
speedChange() {
    if (this.speedValue == this.speedList.length - 1) {
        this.speedValue = 0;
    } else {
        this.speedValue++;
    }
    this.cruise.setSpeed(this.speedList[this.speedValue].speed);
},

到这里巡航器的基础功能已经实现,还有一部分关于播放器调整对应轨迹改变,这里我们要用的监听器,即vue的watch属性:

watch: {
    sliderValue(val) {
        // 正在播放禁止拖拽播放器
        if (!this.cruise || this.isPlay) return;
        this.cruise.moveToPoint(val);
        this.startPoint = val;
        this.pathSimplifierIns.render();
    },
},

五、变量声明以及HTML结构

其中使用到的变量有这些:

data() {
    return {    
        // 地图实例
        map: null,
        cruise: null, //巡航器实例
        cruiseIndex: -1, // 当前播放轨迹下标
        pathSimplifierIns: null, //轨迹实例
        isPlay: false, //是否播放
        isComplete: true, //是否完成
        pointSum: 0, //播放器总数
        sliderValue: 0, //播放器当前数
        startPoint: 0, //下次播放轨迹从当前值开始
        marksIndex: {}, //每段路的起止坐标
        pathList: [],// 轨迹坐标
        speedValue: 3,// 当前播放速度下标
        // 速度列表,可自定义配置
        speedList: [
            { value: 0.5, speed: 100 },
            { value: 1, speed: 200 },
            { value: 2, speed: 400 },
            { value: 4, speed: 1600 },
            { value: 8, speed: 12800 },
            { value: 16, speed: 25600 },
        ],
    };
},

HTML结构:

<template>
    <div class="workTrack">
        <div id="myMap"></div>
        <div class="sliderBar" v-show="pathList.length">
            <span @click="cruiseStart()" v-if="!isPlay">
                <i class="el-icon-video-play"></i>
            </span>
            <span @click="cruisePause" v-else>
                <i class="el-icon-video-pause"></i>
            </span>
            <span @click="cruiseStop">
                <i class="el-icon-error"></i>
            </span>
            <el-slider :disabled="isPlay" v-model="sliderValue" :max="pointSum" :show-tooltip="false"></el-slider>
            <b @click="speedChange">
                <i class="el-icon-d-arrow-right"></i>
                <span>×{{ speedList[speedValue].value }}</span>
            </b>
        </div>
    </div>
</template>

css:

.workTrack {
    width: 100%;
    position: relative;
    height: 100%;
    #myMap {
        width: 100%;
        height: 100%;
    }
    .sliderBar {
        position: absolute;
        bottom: 30px;
        user-select: none;
        width: 100%;
        padding: 10px 2%;
        background-color: #00000064;
        border-radius: 400px;
        backdrop-filter: blur(5px);
        z-index: 99;
        width: 80%;
        right: 0;
        left: 0;
        margin: auto;
        display: flex;
        justify-content: center;
        align-items: center;
        .el-slider {
            flex: 1;
            transform: translateY(1px);
            margin: 0 15px;
        }
        ::v-deep .el-slider__runway {
            pointer-events: none;
            background-color: #00000021;
            margin: 0;
            .el-slider__bar {
                background-color: #1682e6;
            }
            .el-slider__stop {
                background-color: #1682e6;
                border-radius: 0;
                width: 2px;
            }
            .el-slider__button-wrapper {
                pointer-events: auto;
            }
            .el-slider__marks-text {
                white-space: nowrap;
                color: #fff;
                font-size: 0;
            }
        }
        > span {
            flex-shrink: 0;
            transform: translateY(1px);
            color: #eee;
            cursor: pointer;
            margin: 0 5px;
            transition: 0.3s;
            font-size: 20px;
            &:hover {
                opacity: 0.5;
            }
        }
        > b {
            flex-shrink: 0;
            color: #eee;
            font-weight: normal;
            margin: 0 5px;
            cursor: pointer;
            border-radius: 3px;
            border: 1px solid #eee;
            padding: 0px 10px;
            transition: 0.3s;
            user-select: none;
            > span {
                vertical-align: middle;
                font-size: 14px;
                display: inline-block;
                transform: translateY(-2px);
            }
            i {
                vertical-align: middle;
                font-size: 16px;
                display: inline-block;
                transform: translateY(-1px);
            }
            &:hover {
                opacity: 0.5;
            }
        }
    }
  
}

六:完整代码

完整代码如下:

<!-- 
 * @description 轨迹回放
 * @fileName: track.vue 
 * @author: tan 
 * @date: 2024-06-17 10:02:28
!-->
<template>
    <div class="workTrack">
        <div id="myMap"></div>
        <div class="sliderBar" v-show="pathList.length">
            <span @click="cruiseStart()" v-if="!isPlay">
                <i class="el-icon-video-play"></i>
            </span>
            <span @click="cruisePause" v-else>
                <i class="el-icon-video-pause"></i>
            </span>
            <span @click="cruiseStop">
                <i class="el-icon-error"></i>
            </span>
            <el-slider :disabled="isPlay" v-model="sliderValue" :max="pointSum" :show-tooltip="false"></el-slider>
            <b @click="speedChange">
                <i class="el-icon-d-arrow-right"></i>
                <span>×{{ speedList[speedValue].value }}</span>
            </b>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            // 地图实例
            map: null,
            cruise: null, //巡航器实例
            cruiseIndex: -1, // 当前播放轨迹下标
            pathSimplifierIns: null, //轨迹实例
            isPlay: false, //是否播放
            isComplete: true, //是否完成
            pointSum: 0, //播放器总数
            sliderValue: 0, //播放器当前数
            startPoint: 0, //下次播放轨迹从当前值开始
            marksIndex: {}, //每段路的起止坐标
            // 轨迹坐标
            pathList: [
                // [经度,纬度] 可再次放置测试数据
                [120.79573938, 30.03576463],
            ],
            speedValue: 3, // 当前播放速度下标
            // 速度列表,可自定义配置
            speedList: [
                { value: 0.5, speed: 100 },
                { value: 1, speed: 200 },
                { value: 2, speed: 400 },
                { value: 4, speed: 1600 },
                { value: 8, speed: 12800 },
                { value: 16, speed: 25600 },
            ],
        };
    },
    mounted() {
        this.map = new AMap.Map('myMap', {
            zoom: 10, //级别
            center: [120.209758, 30.246809], //中心点坐标 默认在杭州
        });
        this.$nextTick(() => {
            this.loadMap();
        });
    },
    methods: {
        // 加载地图
        loadMap() {
            return new Promise((reslove, reject) => {
                //加载PathSimplifier,loadUI的路径参数为模块名中 'ui/' 之后的部分
                new AMapUI.load(['ui/misc/PathSimplifier'], PathSimplifier => {
                    if (!PathSimplifier.supportCanvas) {
                        alert('当前环境不支持 Canvas!');
                        return;
                    }
                    if (this.pathList?.length) {
                        //启动页面
                        this.initPage(PathSimplifier);
                    }
                });

                reslove();
            });
        },
        initPage(PathSimplifier) {
            let content = PathSimplifier.Render.Canvas.getImageContent(
                '/img/car1.png',
                () => {
                    //图片加载成功,重新绘制一次
                    this.pathSimplifierIns.renderLater();
                },
                function onerror(e) {
                    this.$message({ type: 'error', message: '图片加载失败!' });
                }
            );
            this.pathSimplifierIns = new PathSimplifier({
                zIndex: 100,
                map: this.map,
                getPath: function (pathData, pathIndex) {
                    return pathData.path;
                },

                renderOptions: {
                    //轨迹线的样式
                    getPathStyle: (pathItem, zoom) => {
                        return {
                            pathLineStyle: {
                                strokeStyle: 'red',
                                lineWidth: 6,
                                dirArrowStyle: true,
                            },
                        };
                    },
                    pathNavigatorStyle: {
                        initRotateDegree: 180,
                        width: 20,
                        height: 35,
                        autoRotate: true,
                        content,
                    },
                },
            });
            this.cruiseInit();
        },
        // 巡航器初始化
        cruiseInit() {
            let pathSimplifierIns = [{ path: this.pathList, color: '#28F' }];
            this.pathSimplifierIns.setData(pathSimplifierIns);
            this.pointSum = 0;
            let marksIndex = {};
            pathSimplifierIns.forEach((item, index) => {
                this.pointSum += item.path.length;
                marksIndex[index] = [0, this.pointSum];
            });

            this.marksIndex = marksIndex;
            this.cruiseStop();
        },
        // 创建一个巡航器
        createdCruise(index) {
            this.cruiseIndex++;
            // 判断是否传入index
            let cruiseIndex;
            if (index != undefined) {
                cruiseIndex = index;
                this.cruiseIndex = index;
            } else {
                cruiseIndex = this.cruiseIndex;
            }
            let cruise = this.pathSimplifierIns.createPathNavigator(
                cruiseIndex, //关联第index条轨迹
                {
                    loop: false, //循环播放
                    speed: this.speedList[this.speedValue].speed, //速度
                }
            );

            if (this.cruise) {
                // 清空走过的路线
                this.cruise.destroy();
                this.cruise = null;
            }

            return cruise;
        },
        // 开始播放
        cruiseStart() {
            this.isPlay = true;

            if (this.cruise && !this.cruise.isCursorAtPathEnd() && !this.cruise.isCursorAtPathStart() && !this.isComplete) {
                // 路段未开始并且没有结束的时候 暂停恢复动画 并且动画没有完成的时候
                this.cruise.resume();
                return;
            }
            this.isComplete = false;
            if (this.cruiseIndex == 0) {
                this.cruiseStop();
                return;
            }

            this.cruise = this.createdCruise();

            // 判断是否传入初始坐标
            if (this.startPoint) {
                this.cruise.start(this.startPoint);
                this.startPoint = 0;
            } else {
                this.cruise.start();
            }
            this.cruise.on('move', e => {
                let idx = this.cruise.cursor.idx;
                let { address, gpsTime, speed } = this.pathList[idx];
                let trackAddress = {
                    address,
                    gpsTime,
                    speed,
                };
                this.$emit('changeData', 'trackAddress', trackAddress);
                let [min, max] = this.marksIndex[this.cruiseIndex];
                this.sliderValue = idx + min;
            });
            // 巡航完成事触发
            this.cruise.on('pause', () => {
                if (this.cruise && this.cruise.isCursorAtPathEnd()) {
                    this.cruiseStart();
                }
            });
        },

        // 暂停播放
        cruisePause() {
            this.cruise.pause();
            this.isPlay = false;
        },
        // 停止
        cruiseStop() {
            if (this.cruise) {
                // 清空走过的路线
                this.cruise.destroy();
            }
            // 停止播放
            this.isPlay = false;
            this.isComplete = true;
            this.cruiseIndex = -1;
            // 为重新播放准备
            this.cruise = this.createdCruise();
            this.cruiseIndex = -1;
            this.sliderValue = 0;
        },

        speedChange() {
            if (this.speedValue == this.speedList.length - 1) {
                this.speedValue = 0;
            } else {
                this.speedValue++;
            }
            this.cruise.setSpeed(this.speedList[this.speedValue].speed);
        },
    },
    watch: {
        sliderValue(val) {
            // 正在播放禁止拖拽播放器
            if (!this.cruise || this.isPlay) return;
            this.cruise.moveToPoint(val);
            this.startPoint = val;
            this.pathSimplifierIns.render();
        },
    },

    beforeDestroy() {
        if (this.pathSimplifierIns) this.pathSimplifierIns.clearPathNavigators();
        if (this.pathSimplifierIns) this.pathSimplifierIns.setData([]);
        if (this.cruise) this.cruise.destroy();
        if (this.map) this.map.destroy();
    },
};
</script>

<style lang="scss" scoped>
.workTrack {
    width: 100%;
    position: relative;
    height: 100%;
    #myMap {
        width: 100%;
        height: 100%;
    }
    .sliderBar {
        position: absolute;
        bottom: 30px;
        user-select: none;
        width: 100%;
        padding: 10px 2%;
        background-color: #00000064;
        border-radius: 400px;
        backdrop-filter: blur(5px);
        z-index: 99;
        width: 80%;
        right: 0;
        left: 0;
        margin: auto;
        display: flex;
        justify-content: center;
        align-items: center;
        .el-slider {
            flex: 1;
            transform: translateY(1px);
            margin: 0 15px;
        }
        ::v-deep .el-slider__runway {
            pointer-events: none;
            background-color: #00000021;
            margin: 0;
            .el-slider__bar {
                background-color: #1682e6;
            }
            .el-slider__stop {
                background-color: #1682e6;
                border-radius: 0;
                width: 2px;
            }
            .el-slider__button-wrapper {
                pointer-events: auto;
            }
            .el-slider__marks-text {
                white-space: nowrap;
                color: #fff;
                font-size: 0;
            }
        }
        > span {
            flex-shrink: 0;
            transform: translateY(1px);
            color: #eee;
            cursor: pointer;
            margin: 0 5px;
            transition: 0.3s;
            font-size: 20px;
            &:hover {
                opacity: 0.5;
            }
        }
        > b {
            flex-shrink: 0;
            color: #eee;
            font-weight: normal;
            margin: 0 5px;
            cursor: pointer;
            border-radius: 3px;
            border: 1px solid #eee;
            padding: 0px 10px;
            transition: 0.3s;
            user-select: none;
            > span {
                vertical-align: middle;
                font-size: 14px;
                display: inline-block;
                transform: translateY(-2px);
            }
            i {
                vertical-align: middle;
                font-size: 16px;
                display: inline-block;
                transform: translateY(-1px);
            }
            &:hover {
                opacity: 0.5;
            }
        }
    }
}
</style>
  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
对于Vue 3和高德地图轨迹回放,你可以按照以下步骤进行操作: 1. 首先,确保你已经安装了Vue 3和高德地图的相关依赖。你可以使用npm或yarn来安装这些依赖。例如,在你的Vue项目中,可以运行以下命令来安装高德地图的依赖: ``` npm install vue-amap ``` 2. 在你的Vue组件中,引入Vue AMap库并注册该组件: ```javascript import VueAMap from 'vue-amap'; export default { ... mounted() { Vue.use(VueAMap); VueAMap.initAMapApiLoader({ key: 'your_amap_key', plugin: ['AMap.Polyline'] }); }, ... } ``` 在上述代码中,你需要将`your_amap_key`替换成你自己的高德地图API密钥。 3. 在模板中添加地图容器和控件: ```html <template> <div> <amap :zoom="13" :center="[lng, lat]"> <amap-polyline :path="path" :visible="true" :style="{ strokeColor: 'red', strokeWeight: 6 }"></amap-polyline> </amap> </div> </template> ``` 在上述代码中,`amap`是地图容器组件,`amap-polyline`是轨迹回放的折线组件。你可以根据需要调整地图的缩放级别和中心点位置,以及折线的样式。 4. 在组件的`data`属性中定义轨迹回放的经纬度数据: ```javascript data() { return { lng: 116.397428, lat: 39.90923, path: [ [116.405289, 39.904987], [116.406089, 39.904987], [116.406289, 39.905087], // 更多经纬度数据... ] }; } ``` 你需要根据实际情况提供正确的经纬度数据。 5. 最后,你可以根据需求实现轨迹回放的逻辑。例如,你可以使用定时器来逐步显示折线上的点,实现轨迹的动态回放效果。 这样,你就可以在Vue 3中使用高德地图实现轨迹回放了。记得根据你的实际需求进行相应的调整和扩展。希望这能帮到你!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值