Vue-amap高德地图+ElementUI的slider组件实现车辆轨迹拖动和回放
交通行业,轨迹回放是过不去的坎,算是基本功能。本文讲述通过查询车辆某一时间段的Gps数据,将不同状态的路段绘制在高德地图上,并通过slider滑块拖动轨迹,并且marker能沿轨迹行走,本文可算是个人开发过程的记录笔记,并非基础教程,不负责解答。由于本人也刚接触Vue没多久,本来有同事在其他项目实现类似的功能,但封装太多功能复杂,看起来费劲,于是自己花了两三天时间研究了下,好在vue-amap兼容
交通行业,轨迹回放是过不去的坎,算是基本功能。本文讲述通过查询车辆某一时间段的Gps数据,将不同状态的路段绘制在高德地图上,并通过slider滑块拖动轨迹,并且marker能沿轨迹行走,本文可算是个人开发过程的记录笔记,并非基础教程,不负责解答。
由于本人也刚接触Vue没多久,本来有同事在其他项目实现类似的功能,但封装太多功能复杂,看起来费劲,于是自己花了两三天时间研究了下,好在vue-amap兼容高德地图原生sdk,所以我感觉基本上是用JavaScript的方式实现轨迹回放。
先放效果图吧
1、用echarts画的时间速度图,不同颜色表示不同车辆状态,空车重车离线等,这个不是本篇重点,不讲了。
2、slider的轴为时间轴,长度为gps数量,拖到哪就画到哪的轨迹,为了更好的拖动效果,采用预先画好polyline,实时加载。
3、车辆会同时间轴一起动,有位移时,车辆按一定速度行驶,gps经纬度一样时,车不动,slider动,达到滑块和轨迹同步的效果。
总体思路大致是这样:
1、通过接口获取Gps数组,长度为N;
2、遍历Gps数据,根据点位的状态,两点为一段,预先绘制各个线段,位移不变则以0代替,总共有N-1段;
3、
直接上代码,
1、定义一个地图和marker,因为要区分线路颜色,polyline在获取到gps后动态生成。
<div class="amap-page-container">
<el-amap
vid="amapDemo"
:center="mapData.center"
:zoom="mapData.zoom"
:plugin="plugin"
class="amap-demo"
:events="mapData.events"
pitch-enable="false">
<el-amap-marker vid="carmarker"
auto-rotation="true"
angle="0"
:position="carMarker.position"
:icon="carMarker.icon"
:offset="carMarker.offset"
:events="carMarker.events" />
</el-amap>
</div>
2、一个echarts图表,element的Slide组件,还有各个button及绑定的相应事件。
<div>
<el-row>
<pie id="line1"
width="100%"
height="100px"
:chart-options="speedChartOptions" />
</el-row>
<el-row style="margin-top:-15px;margin-left:30px;margin-right:5px; ">
<el-slider ref="timeSlider"
v-model="currentIndex"
:format-tooltip="formatTooltip"
:max="maxIndex"
@input="moveSlider"
@change="afterMoveSlider" />
</el-row>
<el-row style="margin-left:30px;margin-top:-5px;">
<el-button v-if="!isMoving"
type="primary"
size="mini"
:disabled="!list||list.length===0||isMoving"
@click="carStartMove()">开始</el-button>
<el-button v-else
type="primary"
size="mini"
:disabled="!isMoving"
@click="carPauseMove()">暂停</el-button>
<el-button type="primary"
size="mini"
:disabled="currentGpsIndex===0"
@click="carResetMove()">重置</el-button>
<el-button type="primary"
size="mini"
:disabled="currentGpsIndex===0"
@click="carBackMove()">向后</el-button>
<el-button type="primary"
size="mini"
:disabled="currentGpsIndex===maxIndex"
@click="carforeMove()">向前</el-button>
<el-button type="primary"
size="mini"
:disabled="!isMoving"
@click="carSlowMove()">减速</el-button>
<el-button type="primary"
size="mini"
:disabled="!isMoving"
@click="carFastMove()">加速</el-button>
</el-row>
</div>
3、绑定的数据
data: function () {
const self = this;
return {
filterQuery: {
plateNo: '',
beginTime: this.$moment().add(-1, 'h').format('YYYY-MM-DD HH:mm:ss'),
endTime: this.$moment().format('YYYY-MM-DD HH:mm:ss')
},
listLoading: false,
searchVisible: false,
speedChartOptions: {},
list: null,
gpsCount: 0,
maxIndex: 0,
currentIndex: 0,
currentGpsIndex: 0,
isMoving: false,
moveSpeed: 2000,
traceLine: [],
movingTraceLine: [],
passedPathLine: [],
markers: [],
carMarker: {
position: [116.478935, 39.997761],
icon: carIcon,
offset: [-26, -13],
events: {
init(o) {
self.moveMarker = o;
},
moving(e) {
//为了保持行驶过程中的线路样式与silder拖动的样式一致,或许有其他更好的方式
const passingPolyline = self.movingTraceLine[self.currentGpsIndex - 1];
passingPolyline.setPath(e.passedPath);
self.mapObj.add(passingPolyline);
self.passedPathLine.push(passingPolyline);
},
moveend(e) {
self.moveCar();
}
}
},
moveMarker: null,
mapObj: null,
mapData: {
zoom: 16,
center: [113.32845, 23.134527],
events: {
init(o) {
self.mapObj = o;
},
click(e) {
// const { lng, lat } = e.lnglat;
// self.mapData.center = [lng, lat];
}
}
},
/* 一些工具插件*/
plugin: [
{
// 地图类型
pName: 'MapType',
defaultType: 0,
events: {
init(instance) {
// console.log(instance);
}
}
}
]
};
},
4、具体方法,onSubmit,查询坐标点,提前画好不同样式的线段,位移为0则线段为0,即可根据点位序号快速加载轨迹,通过地图原生map.add([])或map.remove([])方法来显示滑块拖动的实时效果,这里有一个问题就是当线段多的时候,或许会卡,可以试验下,但目前我还找不出更好的办法。
onSubmit() {
this.listLoading = true;
if (this.isMoving) {
this.moveMarker.stopMove();
this.isMoving = false;
}
//清除前一次查询轨迹
this.currentIndex = 0;
this.currentGpsIndex = 0;
this.maxIndex = 0;
if (this.mapObj && this.passedPathLine.length > 0) {
this.mapObj.remove(this.passedPathLine);
}
//定义两个数组存储轨迹,在滑块拖动时可以快速响应
this.passedPathLine = [];
this.movingTraceLine = [];
const arrDate = [];
const arrLoadSpeed = [];
const arrEmptySpeed = [];
const arrOffSpeed = [];
fetchTracePlayBack(this.filterQuery).then(response => {
this.listLoading = false;
var resultList = JSON.parse(response);
if (resultList.length === 0) {
this.$message.error(`【${this.filterQuery.plateNo}】gps为空`);
if (this.markers.length > 0) {
this.mapData.center = this.markers[0].position;
}
return;
}
// const gpsList = [];
const traceLineArr = [];
resultList.forEach((item, index) => {
if (index < resultList.length - 1) {
if (resultList[index].Longitude !== resultList[index + 1].Longitude || resultList[index].Latitude !== resultList[index + 1].Latitude) {
//根据两点距离,提前画好轨迹,N个点则有N-1个线段,主要是不同线段样式
const computeline = this.getPolyLine(resultList[index], resultList[index + 1]);
this.movingTraceLine.push(computeline);
traceLineArr.push(computeline);
} else {
//两点间无位移,则线段为0
this.movingTraceLine.push(0);
traceLineArr.push(0);
}
}
item.index = index;
item.position = this.$wgs84togcj02(item.Longitude, item.Latitude);
item.GpsTime = this.$moment(item.GpsTime).format('YYYY-MM-DD HH:mm:ss');
if (parseInt(item.RunStatus) === 0) {
item.RunState = '熄火';
} else if (parseInt(item.RunStatus) === 2) {
item.RunState = '离线';
} else if (parseInt(item.RunStatus) === 1) {
if (parseInt(item.CarryStatus) === 1) {
item.RunState = '空车';
} else if (parseInt(item.CarryStatus) === 2) {
item.RunState = '重车';
} else {
item.RunState = '运行';
}
} else {
item.RunState = '未知';
}
this.traceLine = traceLineArr;
this.passedPathLine = traceLineArr;
this.list = resultList;
this.gpsCount = resultList.length;
this.maxIndex = resultList.length - 1;
this.moveMarker.setPosition(resultList[0].position);
this.moveMarker.setIcon(this.getCarIcon(resultList[0].RunStatus, resultList[0].CarryStatus));
this.mapData.center = resultList[0].position;
this.mapObj.add(this.passedPathLine);
this.$nextTick(() => {
this.mapObj.setFitView();
});
}).catch(err => {
this.$message.error(err);
this.listLoading = false;
});
},
getpolyline方法,根据两gps点的属性返回不同样式的线路
getPolyLine(dataCur, dataNext) {
if (dataCur == null || dataNext == null) { return null; }
const lineArr = [];// 创建线覆盖物节点坐标数组
lineArr.push(this.$wgs84togcj02(dataCur.Longitude, dataCur.Latitude));
lineArr.push(this.$wgs84togcj02(dataNext.Longitude, dataNext.Latitude));
let polyline = null;
const curCarryStatus = parseInt(dataCur.CarryStatus);
const nextCarryStatus = parseInt(dataNext.CarryStatus);
if ((curCarryStatus === 1 && nextCarryStatus === 1) || (curCarryStatus === 2 && nextCarryStatus === 1)) {
// 空车
polyline = new AMap.Polyline({
path: lineArr, // 设置线覆盖物路径
strokeColor: '#0000ff', // 线颜色
strokeOpacity: 0.6, // 线透明度
strokeWeight: 3, // 线宽
strokeStyle: 'solid', // 线样式
strokeDasharray: [10, 5] // 补充线样式
});
} else if ((curCarryStatus === 1 && nextCarryStatus === 2) || (curCarryStatus === 2 && nextCarryStatus === 2)) {
// 重车
polyline = new AMap.Polyline({
path: lineArr, // 设置线覆盖物路径
strokeColor: 'red', // 线颜色
strokeOpacity: 0.8, // 线透明度
strokeWeight: 3, // 线宽
strokeStyle: 'solid', // 线样式
strokeDasharray: [10, 5] // 补充线样式
});
} else {
polyline = new AMap.Polyline({
path: lineArr, // 设置线覆盖物路径
strokeColor: 'black', // 线颜色
strokeOpacity: 0.8, // 线透明度
strokeWeight: 3, // 线宽
strokeStyle: 'solid', // 线样式
strokeDasharray: [10, 5] // 补充线样式
});
}
return polyline;
},
elementui滑块拖动时显示当前点的时间
formatTooltip(val) {
if (this.list !== null) {
return this.list[val].GpsTime;
}
},
button绑定的方法
carStartMove() {
if (this.currentGpsIndex === this.maxIndex) {
this.currentGpsIndex = 0;
this.currentIndex = 0;
this.moveMarker.setPosition(this.list[0].position);
this.mapObj.remove(this.passedPathLine);
}
if (this.currentGpsIndex === 0 && this.passedPathLine.length > 0) {
this.mapObj.remove(this.passedPathLine);
this.passedPathLine = [];
}
this.isMoving = true;
this.moveCar();
},
carPauseMove() {
if (this.isMoving) {
this.moveMarker.stopMove();
this.isMoving = false;
}
},
carResetMove() {
if (this.isMoving) {
this.moveMarker.stopMove();
this.isMoving = false;
}
this.currentGpsIndex = 0;
this.currentIndex = 0;
this.moveMarker.setPosition(this.list[0].position);
this.moveMarker.setIcon(this.getCarIcon(this.list[0].RunStatus, this.list[0].CarryStatus));
},
carBackMove() {
if (this.currentGpsIndex > 0) {
this.currentGpsIndex--;
this.currentIndex--;
}
},
carforeMove() {
if (this.currentIndex < this.maxIndex) {
this.currentGpsIndex++;
this.currentIndex++;
}
},
carSlowMove() {
if (this.moveSpeed > 200) {
this.moveSpeed = this.moveSpeed - 200;
}
},
carFastMove() {
this.moveSpeed += 200;
},
图标移动的主要方法movecar,通过marker.moveto来移动图标,moveend之后再调用movecar来完成全部轨迹的行走,直至最后,movealong我试过并不满足我的思路,没深入。moveSlider和afterMoveSlider则是slider绑定的事件,用户配合图标的移动,
moveCar() {
const self = this;
if (this.currentIndex === this.maxIndex) {
this.moveMarker.stopMove();
this.isMoving = false;
return;
}
const bound = this.mapObj.getBounds();
if (!bound.contains(this.moveMarker.getPosition())) {
this.mapObj.panTo(this.moveMarker.getPosition());
}
this.currentGpsIndex++;
this.currentIndex++;
this.moveMarker.setIcon(this.getCarIcon(this.list[this.currentGpsIndex].RunStatus, this.list[this.currentGpsIndex].CarryStatus));
if (this.traceLine[this.currentGpsIndex - 1] === 0 || this.traceLine[this.currentGpsIndex - 1].getLength() < 0.5) {
if (this.isMoving) {
setTimeout((e) => {
if (self.isMoving) {
self.moveCar();
}
}, 100);
}
} else {
this.moveMarker.moveTo(this.list[this.currentGpsIndex].position, self.moveSpeed);
}
},
moveSlider(v) {
if (this.isMoving) {
if (v > this.currentGpsIndex || v < this.currentGpsIndex) {
this.moveMarker.stopMove();
this.isMoving = false;
this.mapObj.remove(this.passedPathLine);
this.passedPathLine = this.traceLine.slice(0, v);
this.mapObj.add(this.passedPathLine);
this.moveMarker.setPosition(this.list[v].position);
this.moveMarker.setIcon(this.getCarIcon(this.list[v].RunStatus, this.list[v].CarryStatus));
const bound = this.mapObj.getBounds();
if (!bound.contains(this.moveMarker.getPosition())) {
this.mapObj.panTo(this.moveMarker.getPosition());
}
}
} else {
if (this.mapObj != null) {
if (this.passedPathLine.length > 0) {
this.mapObj.remove(this.passedPathLine);
}
this.passedPathLine = this.traceLine.slice(0, v);
this.mapObj.add(this.passedPathLine);
this.moveMarker.setPosition(this.list[v].position);
this.moveMarker.setIcon(this.getCarIcon(this.list[v].RunStatus, this.list[v].CarryStatus));
const bound = this.mapObj.getBounds();
if (!bound.contains(this.moveMarker.getPosition())) {
this.mapObj.panTo(this.moveMarker.getPosition());
}
}
}
// 当重置为0时触发,可重新绘制轨迹
if (v === 0 && this.mapObj !== null) {
this.passedPathLine = this.traceLine.slice(0);
this.mapObj.add(this.passedPathLine);
}
},
afterMoveSlider(v) {
this.currentGpsIndex = v;
},
这大概就是所有的思路了,时间有限,浅尝辄止,或许并不完善,但这样的结合和代码,我想肯定可以帮到你,写文章比代码要纠结些,希望能够给需求的人提供一种思路启发。
要是有用的话,点个赞转发下啥的,技术在于分享,代码在于复制,拿去拿去都拿去
更多推荐
所有评论(0)