Vue2+OpenLayers实现车辆开始、暂停、重置行驶轨迹动画(提供Gitee源码)
前言:根据经纬度信息绘制一个完整的行驶路线,车辆根据绘制好的路线从开始点位行驶到结束点位,可以通过开始、暂停、重置按钮控制车辆状态。
目录
一、案例截图
二、安装OpenLayers库
npm install ol
三、安装Element-UI
没安装的看官方文档:Element - The world's most popular Vue UI framework
四、代码实现
4.1、初始化变量
关键代码:
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,
}
},
4.2、创建起始点位
/**
* 创建开始点位
*/
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、根据经纬度计算车辆旋转角度
关键代码:
//计算旋转角度
calculateRotation(currentPoint,nextPoint){
const dx = nextPoint[0] - currentPoint[0];
const dy = nextPoint[1] - currentPoint[1];
return Math.atan2(dy,dx);
},
4.4、创建车辆图标
先封装一个获取车辆样式的方法,后续都会用到。
关键代码:
//获取车辆的样式
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,
})
},
创建完整的车辆图标。
关键代码:
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、绘制路线
关键代码:
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,表示动画结束。
关键代码:
//车辆行驶动画
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; // 重置动画开始时间
}
// 计算下一个点的角度
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、开始事件
关键代码:
//开始动画
startAnimation() {
this.isAnimating = true;
// 获取动画开始时间
this.animationStartTime = performance.now();
this.animateVehicle();
},
4.8、暂停事件
关键代码:
// 暂停动画
pauseAnimation() {
if (this.isAnimating) {
// 设置为未动画状态
this.isAnimating = false;
}
},
4.9、重置事件
关键代码:
// 重置动画
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、完整代码
<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,
}
},
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: 17,
maxZoom: 18,
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() {
this.isAnimating = true;
// 获取动画开始时间
this.animationStartTime = performance.now();
this.animateVehicle();
},
// 暂停动画
pauseAnimation() {
if (this.isAnimating) {
// 设置为未动画状态
this.isAnimating = false;
}
},
// 重置动画
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; // 重置动画开始时间
}
// 计算下一个点的角度
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实现车辆开始.暂停.重置行驶轨迹动画
原文地址:https://blog.csdn.net/HJW_233/article/details/145163933
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!