Vue2+OpenLayers实现车辆开始、暂停、重置行驶轨迹动画(提供Gitee源码)

server/2025/1/17 3:31:06/
htmledit_views">

前言:根据经纬度信息绘制一个完整的行驶路线,车辆根据绘制好的路线从开始点位行驶到结束点位,可以通过开始、暂停、重置按钮控制车辆状态。 

目录

一、案例截图

二、安装OpenLayers库

三、​安装Element-UI ​

四、代码实现

4.1、初始化变量

4.2、创建起始点位

4.3、根据经纬度计算车辆旋转角度

4.4、创建车辆图标

4.5、绘制路线

4.6、车辆行驶动画

4.7、开始事件

4.8、暂停事件

4.9、重置事件

4.10、完整代码 

五、Gitee源码


一、案例截图

二、安装OpenLayers库

npm install ol

三、​安装Element-UI ​

没安装的看官方文档:Element - The world's most popular Vue UI framework

四、代码实现

4.1、初始化变量

关键代码:

javascript">data() {return {map:null,//点位信息pointList: [[120.430070,31.938140],[120.428570,31.939100],[120.429530,31.941680],[120.431240,31.943580],[120.432410,31.944820],[120.433600,31.943970],],//用于存放车辆、起始点位和折线的图层vectorLayer: new VectorLayer({source: new VectorSource(),zIndex: 2,}),//车辆carFeature:null,//动画开始标记isAnimating: false,//动画开始时间/动画暂停时间animationStartTime:0,//索引currentIndex:0,// 每个点之间的移动时间(毫秒)speed: 1000,//记录上一个动画的运行时间lastAnimationTime: 0,}
},

4.2、创建起始点位

javascript">/*** 创建开始点位*/
createStartPoint(){// 创建feature要素,一个feature就是一个点坐标信息let feature = new Feature({geometry: new Point(this.pointList[0]),});// 设置要素的图标feature.setStyle(new Style({// 设置图片效果image: new Icon({src: 'http://lbs.tianditu.gov.cn/images/bus/start.png',// anchor: [0.5, 0.5],scale: 1,}),zIndex: 10,}),);this.vectorLayer.getSource().addFeature(feature);
},//创建结束点位
createEndPoint(){// 创建feature要素,一个feature就是一个点坐标信息let feature = new Feature({geometry: new Point(this.pointList[this.pointList.length-1]),});// 设置要素的图标feature.setStyle(new Style({// 设置图片效果image: new Icon({src: 'http://lbs.tianditu.gov.cn/images/bus/end.png',// anchor: [0.5, 0.5],scale: 1,}),zIndex: 10}),);this.vectorLayer.getSource().addFeature(feature);
},

4.3、根据经纬度计算车辆旋转角度

关键代码:

javascript">//计算旋转角度
calculateRotation(currentPoint,nextPoint){const dx = nextPoint[0] - currentPoint[0];const dy = nextPoint[1] - currentPoint[1];return Math.atan2(dy,dx);
},

4.4、创建车辆图标

先封装一个获取车辆样式的方法,后续都会用到。

关键代码:

javascript">//获取车辆的样式
getVehicleStyle(rotation) {return new Style({// 设置图片效果image: new Icon({src: 'http://lbs.tianditu.gov.cn/images/openlibrary/car.png',// anchor: [0.5, 0.5],scale: 1,rotation: -rotation,}),zIndex: 5,})
},

创建完整的车辆图标。

关键代码:

javascript">createCar(){// 创建feature要素,一个feature就是一个点坐标信息this.carFeature = new Feature({geometry: new Point(this.pointList[0]),});const currentPoint = fromLonLat(this.pointList[0]);const nextPoint = fromLonLat(this.pointList[1]);let rotation = this.calculateRotation(currentPoint,nextPoint);this.carFeature.setStyle(this.getVehicleStyle(rotation));this.vectorLayer.getSource().addFeature(this.carFeature);
},

4.5、绘制路线

关键代码:

javascript">drawLine(){// 创建线特征const lineFeature = new Feature({geometry: new LineString(this.pointList),});// 设置线样式const lineStyle = new Style({stroke: new Stroke({color: '#25C2F2',width: 4,lineDash: [10, 8], // 使用点划线 数组的值来控制虚线的长度和间距}),});lineFeature.setStyle(lineStyle);// 创建矢量层并添加特征const vectorSource = new VectorSource({features: [lineFeature],});const vectorLayer = new VectorLayer({source: vectorSource,zIndex: 1});// 将矢量层添加到地图this.map.addLayer(vectorLayer);// 设置地图视图以适应路径this.map.getView().fit(lineFeature.getGeometry().getExtent(), {padding: [20, 20, 20, 20],maxZoom: 18,});
},

4.6、车辆行驶动画

实现思路:

1.动画启动条件

检查动画状态:首先,代码检查this.isAnimating是否为true,以及this.currentIndex是否已经到达pointList的最后一个点。如果满足任一条件,则函数返回,停止动画。

2.获取当前和下一个点

获取当前点和下一个点:通过this.currentIndex获取当前点currentPoint和下一个点nextPoint。

3.计算经过的时间

计算经过的时间:使用timestamp参数(通常由requestAnimationFrame传递)减去this.animationStartTime来计算动画已经经过的时间。

4.计算当前位置

插值计算:根据经过的时间和速度(this.speed),计算进度progress,并使用这个进度来插值计算当前车辆的位置coordinates。这通过线性插值实现,即根据当前点和下一个点的坐标计算出车辆的当前位置。

5.更新车辆位置

更新车辆坐标:通过this.carFeature.getGeometry().setCoordinates(coordinates)更新车辆的实际位置。

6.更新动画状态

检查进度是否完成:如果progress等于1,表示车辆已经到达下一个点。此时,更新currentIndex以指向下一个点,并重置animationStartTime为当前时间。

7.计算并更新车辆朝向

计算角度:调用this.calculateRotation(currentPoint, nextPoint)计算车辆应朝向的角度。 更新样式:通过this.carFeature.setStyle(this.getVehicleStyle(angle))更新车辆的样式,以反映新的朝向。

8.继续动画

请求下一帧:如果currentIndex仍然小于pointList的长度,调用requestAnimationFrame(this.animateVehicle)继续动画;否则,将this.isAnimating设为false,表示动画结束。

关键代码:

javascript">//车辆行驶动画
animateVehicle(timestamp) {if (!this.isAnimating || this.currentIndex >= this.pointList.length - 1) return;const currentPoint = this.pointList[this.currentIndex];const nextPoint = this.pointList[this.currentIndex + 1];// 计算经过的时间const elapsed = timestamp - this.animationStartTime;const progress = Math.min(elapsed / this.speed, 1);// 计算当前位置的坐标const coordinates = [currentPoint[0] + (nextPoint[0] - currentPoint[0]) * progress,currentPoint[1] + (nextPoint[1] - currentPoint[1]) * progress,];// 更新车辆位置this.carFeature.getGeometry().setCoordinates(coordinates);if (progress === 1) {this.currentIndex++;this.animationStartTime = timestamp; // 重置动画开始时间this.lastAnimationTime = 0; // 移动到下一个点时重置}// 计算下一个点的角度const angle = this.calculateRotation(currentPoint, nextPoint);this.carFeature.setStyle(this.getVehicleStyle(angle)); // 更新样式以反映新的朝向// 继续动画if (this.currentIndex < this.pointList.length - 1) {requestAnimationFrame(this.animateVehicle);} else {this.isAnimating = false; // 动画结束}
},

4.7、开始事件

关键代码:

javascript">startAnimation() {// 如果没有开始动画,则开始动画if (!this.isAnimating) {this.isAnimating = true;//这里存放的是暂停动画的时间,下面启动动画后,会将当前时间减去暂停动画的时间就是动画已运行的时间,这样就不会从头开始了//当前时间比如为900ms,用900ms减去500ms,可以计算出当前暂停了400msthis.animationStartTime = performance.now() - (this.lastAnimationTime || 0);// 继续或者启动动画// 计算经过时间:elapsed = timestamp - this.animationStartTime; 900ms减去400ms 可以算出已经运行了500ms 也就是上次动画所运行的时间了this.animateVehicle();}
},

4.8、暂停事件

关键代码:

javascript">// 暂停动画
pauseAnimation() {if (this.isAnimating) {this.isAnimating = false;// 保存当前动画运行的时间 比如已经运行了500msthis.lastAnimationTime = performance.now() - this.animationStartTime;}
},

4.9、重置事件

关键代码:

javascript">// 重置动画
resetAnimation() {// 停止动画this.isAnimating = false;// 重置索引this.currentIndex = 0;// 将车辆位置重置到起始点this.carFeature.getGeometry().setCoordinates(this.pointList[0]);const currentPoint = fromLonLat(this.pointList[0]);const nextPoint = fromLonLat(this.pointList[1]);let rotation = this.calculateRotation(currentPoint,nextPoint);// 重置车辆样式this.carFeature.setStyle(this.getVehicleStyle(rotation));
},

4.10、完整代码 

javascript"><template><div><el-button type="primary" @click="startAnimation">开始</el-button><el-button type="warning" @click="pauseAnimation">暂停</el-button><el-button type="info" @click="resetAnimation">重置</el-button><div id="map-container"></div></div>
</template>
<script>
import { Map, View } from 'ol'
import { Tile as TileLayer } from 'ol/layer'
import {fromLonLat, get} from 'ol/proj';
import { getWidth, getTopLeft } from 'ol/extent'
import { WMTS } from 'ol/source'
import WMTSTileGrid from 'ol/tilegrid/WMTS'
import { defaults as defaultControls} from 'ol/control';
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import Feature from "ol/Feature";
import {LineString, Point} from "ol/geom";
import {Icon, Stroke, Style} from "ol/style";export const projection = get("EPSG:4326");
const projectionExtent = projection.getExtent();
const size = getWidth(projectionExtent) / 256;
const resolutions = [];
for (let z = 0; z < 19; ++z) {resolutions[z] = size / Math.pow(2, z);
}export default {data() {return {map:null,//点位信息pointList: [[120.430070,31.938140],[120.428570,31.939100],[120.429530,31.941680],[120.431240,31.943580],[120.432410,31.944820],[120.433600,31.943970],],//用于存放车辆、起始点位和折线的图层vectorLayer: new VectorLayer({source: new VectorSource(),zIndex: 2,}),//车辆carFeature:null,//动画开始标记isAnimating: false,//动画开始事件animationStartTime:0,//索引currentIndex:0,// 每个点之间的移动时间(毫秒)speed: 1000,//记录上一个动画的运行时间lastAnimationTime: 0,}},mounted(){this.initMap() // 加载矢量底图},methods:{initMap() {const KEY = '你申请的KEY'this.map = new Map({target: 'map-container',layers: [// 底图new TileLayer({source: new WMTS({url: `http://t{0-6}.tianditu.com/vec_c/wmts?tk=${KEY}`,layer: 'vec', // 矢量底图matrixSet: 'c', // c: 经纬度投影 w: 球面墨卡托投影style: "default",crossOrigin: 'anonymous', // 解决跨域问题 如无该需求可不添加format: "tiles", //请求的图层格式,这里指定为瓦片格式wrapX: true, // 允许地图在 X 方向重复(环绕)tileGrid: new WMTSTileGrid({origin: getTopLeft(projectionExtent),resolutions: resolutions,matrixIds: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15','16','17','18']})})}),// 标注new TileLayer({source: new WMTS({url: `http://t{0-6}.tianditu.com/cva_c/wmts?tk=${KEY}`,layer: 'cva', //矢量注记matrixSet: 'c',style: "default",crossOrigin: 'anonymous',format: "tiles",wrapX: true,tileGrid: new WMTSTileGrid({origin: getTopLeft(projectionExtent),resolutions: resolutions,matrixIds: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15','16','17','18']})})})],view: new View({center: [120.430070,31.938140],projection: projection,zoom: 16,maxZoom: 17,minZoom: 1}),//加载控件到地图容器中controls: defaultControls({zoom: false,rotate: false,attribution: false})});// 将矢量层添加到地图this.map.addLayer(this.vectorLayer);this.createStartPoint();this.createEndPoint();this.createCar();this.drawLine();},/*** 创建开始点位*/createStartPoint(){// 创建feature要素,一个feature就是一个点坐标信息let feature = new Feature({geometry: new Point(this.pointList[0]),});// 设置要素的图标feature.setStyle(new Style({// 设置图片效果image: new Icon({src: 'http://lbs.tianditu.gov.cn/images/bus/start.png',// anchor: [0.5, 0.5],scale: 1,}),zIndex: 10,}),);this.vectorLayer.getSource().addFeature(feature);},//创建结束点位createEndPoint(){// 创建feature要素,一个feature就是一个点坐标信息let feature = new Feature({geometry: new Point(this.pointList[this.pointList.length-1]),});// 设置要素的图标feature.setStyle(new Style({// 设置图片效果image: new Icon({src: 'http://lbs.tianditu.gov.cn/images/bus/end.png',// anchor: [0.5, 0.5],scale: 1,}),zIndex: 10}),);this.vectorLayer.getSource().addFeature(feature);},createCar(){// 创建feature要素,一个feature就是一个点坐标信息this.carFeature = new Feature({geometry: new Point(this.pointList[0]),});const currentPoint = fromLonLat(this.pointList[0]);const nextPoint = fromLonLat(this.pointList[1]);let rotation = this.calculateRotation(currentPoint,nextPoint);this.carFeature.setStyle(this.getVehicleStyle(rotation));this.vectorLayer.getSource().addFeature(this.carFeature);},//计算旋转角度calculateRotation(currentPoint,nextPoint){const dx = nextPoint[0] - currentPoint[0];const dy = nextPoint[1] - currentPoint[1];return Math.atan2(dy,dx);},//获取车辆的样式getVehicleStyle(rotation) {return new Style({// 设置图片效果image: new Icon({src: 'http://lbs.tianditu.gov.cn/images/openlibrary/car.png',// anchor: [0.5, 0.5],scale: 1,rotation: -rotation,}),zIndex: 5,})},drawLine(){// 创建线特征const lineFeature = new Feature({geometry: new LineString(this.pointList),});// 设置线样式const lineStyle = new Style({stroke: new Stroke({color: '#25C2F2',width: 4,lineDash: [10, 8], // 使用点划线 数组的值来控制虚线的长度和间距}),});lineFeature.setStyle(lineStyle);// 创建矢量层并添加特征const vectorSource = new VectorSource({features: [lineFeature],});const vectorLayer = new VectorLayer({source: vectorSource,zIndex: 1});// 将矢量层添加到地图this.map.addLayer(vectorLayer);// 设置地图视图以适应路径this.map.getView().fit(lineFeature.getGeometry().getExtent(), {padding: [20, 20, 20, 20],maxZoom: 18,});},startAnimation() {// 如果没有开始动画,则开始动画if (!this.isAnimating) {this.isAnimating = true;//这里存放的是暂停动画的时间,下面启动动画后,会将当前时间减去暂停动画的时间就是动画已运行的时间,这样就不会从头开始了//当前时间比如为900ms,用900ms减去500ms,可以计算出当前暂停了400msthis.animationStartTime = performance.now() - (this.lastAnimationTime || 0);// 继续或者启动动画// 计算经过时间:elapsed = timestamp - this.animationStartTime; 900ms减去400ms 可以算出已经运行了500ms 也就是上次动画所运行的时间了this.animateVehicle();}},// 暂停动画pauseAnimation() {if (this.isAnimating) {this.isAnimating = false;// 保存当前动画运行的时间 比如已经运行了500msthis.lastAnimationTime = performance.now() - this.animationStartTime;}},// 重置动画resetAnimation() {// 停止动画this.isAnimating = false;// 重置索引this.currentIndex = 0;// 将车辆位置重置到起始点this.carFeature.getGeometry().setCoordinates(this.pointList[0]);const currentPoint = fromLonLat(this.pointList[0]);const nextPoint = fromLonLat(this.pointList[1]);let rotation = this.calculateRotation(currentPoint,nextPoint);// 重置车辆样式this.carFeature.setStyle(this.getVehicleStyle(rotation));},//车辆行驶动画animateVehicle(timestamp) {if (!this.isAnimating || this.currentIndex >= this.pointList.length - 1) return;const currentPoint = this.pointList[this.currentIndex];const nextPoint = this.pointList[this.currentIndex + 1];// 计算经过的时间const elapsed = timestamp - this.animationStartTime;const progress = Math.min(elapsed / this.speed, 1);// 计算当前位置的坐标const coordinates = [currentPoint[0] + (nextPoint[0] - currentPoint[0]) * progress,currentPoint[1] + (nextPoint[1] - currentPoint[1]) * progress,];// 更新车辆位置this.carFeature.getGeometry().setCoordinates(coordinates);if (progress === 1) {this.currentIndex++;this.animationStartTime = timestamp; // 重置动画开始时间this.lastAnimationTime = 0; // 移动到下一个点时重置}// 计算下一个点的角度const angle = this.calculateRotation(currentPoint, nextPoint);this.carFeature.setStyle(this.getVehicleStyle(angle)); // 更新样式以反映新的朝向// 继续动画if (this.currentIndex < this.pointList.length - 1) {requestAnimationFrame(this.animateVehicle);} else {this.isAnimating = false; // 动画结束}},},
}
</script>
<style scoped>
#map-container {width: 100%;height: 100vh;
}
</style>

五、Gitee源码

地址:Vue2+OpenLayers实现车辆开始.暂停.重置行驶轨迹动画 


http://www.ppmy.cn/server/158986.html

相关文章

Qt 5.14.2 学习记录 —— 십이 QLineEdit、QTextEdit

文章目录 1、QLineEdit1、写程序2、正则表达式检查电话号码3、验证两次输入的密码是否一致4、切换显示密码状态 2、TextEdit1、多行编写2、信号 1、QLineEdit text在代码上改变或者界面上直接改动都会修改这个属性。 clearButtonEnabled&#xff0c;输入框为空&#xff0c;没有…

【跟着官网学技术系列之MySQL】第7天之创建和使用数据库1

前言 在当今信息爆炸的时代&#xff0c;拥有信息检索的能力很重要。 作为一名软件工程师&#xff0c;遇到问题&#xff0c;你会怎么办&#xff1f;带着问题去搜索引擎寻找答案&#xff1f;亦或是去技术官网&#xff0c;技术社区去寻找&#xff1f; 根据个人经验&#xff0c;一…

图解Git——分支管理《Pro Git》

分支管理 1. 常用分支管理命令 列出所有分支&#xff1a;git branch 当前检出的分支前会标记一个 *。 查看分支最后一次提交&#xff1a;git branch -v查看已合并到当前分支的分支&#xff1a;git branch merge 可以用来确认哪些分支已经合并&#xff0c;可以安全删除。 查…

【Go】Go Gorm 详解

1. 概念 Gorm 官网&#xff1a;https://gorm.io/zh_CN/docs/ Gorm&#xff1a;The fantastic ORM library for Golang aims to be developer friendly&#xff0c;这是官网的介绍&#xff0c;简单来说 Gorm 就是一款高性能的 Golang ORM 库&#xff0c;便于开发人员提高效率 那…

JAVA实战开源项目:课程智能组卷系统(Vue+SpringBoot) 附源码

本文项目编号 T 009 &#xff0c;文末自助获取源码 \color{red}{T009&#xff0c;文末自助获取源码} T009&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 老…

精通Python (10)

一&#xff0c;基于tkinter模块的GUI GUI是图形用户界面的缩写&#xff0c;图形化的用户界面对使用过计算机的人来说应该都不陌生&#xff0c;在此也无需进行赘述。Python默认的GUI开发模块是tkinter&#xff08;在Python 3以前的版本中名为Tkinter&#xff09;&#xff0c;从这…

win10电脑 定时关机

win10电脑 定时关机 https://weibo.com/ttarticle/p/show?id2309405110707766296723 二、使用任务计划程序设置定时关机打开任务计划程序&#xff1a; 按下“Win S”组合键&#xff0c;打开搜索框。 在搜索框中输入“任务计划程序”&#xff0c;然后点击搜索结果中的“任务…

LeetCode1909 删除一个元素使数组严格递增

判断删除一个元素后数组是否可变为严格递增 一、问题描述 在编程中&#xff0c;我们有时会遇到这样一个有趣的问题&#xff1a;给定一个下标从 0 开始的整数数组 nums&#xff0c;我们需要判断是否恰好删除一个元素后&#xff0c;该数组可以变成严格递增的&#xff0c;或者如…