前言:根据经纬度信息绘制一个完整的行驶路线,车辆根据绘制好的路线从开始点位行驶到结束点位,可以通过开始、暂停、重置按钮控制车辆状态。
目录
一、案例截图
二、安装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实现车辆开始.暂停.重置行驶轨迹动画