在我们去实现车辆的实时轨迹展示时,websocket是非常方便的,一旦建立连接便可以不间断的将车辆的数据信息传给我们。但当面临多辆汽车的数据时处理起来就会有点手忙脚乱。首先下面简单的websocket的实现。
//获取websocket地址,接口会有后端提供
async getCarInfoWs() {
try {
let res = await managePlatform.getCarInfoWs();
if (res.success) {
this.webSocketUrl = res.data.carinfoWs;
this.initWebSocket();
}
} catch (e) {
console.log(e);
}
},
//初始化websocket,写法一般固定
initWebSocket() {
if (window.WebSocket && this.webSocketUrl !== "") {
this.ws = new WebSocket(this.webSocketUrl);
this.ws.onopen = () => {
console.log("push_ws:onopen");
this.ws.send("all");
};
this.ws.onclose = () => {
this.ws.onmessage = null;
this.ws = null;
console.log("push_ws:onclose");
};
this.ws.onerror = () => {
this.ws = null;
console.log("push_ws:onerror");
};
this.ws.onmessage = (e) => {
let res = JSON.parse(e.data);
this.monitorCarMsg = res;
};
}
},
这里我们的monitorCarMsg就是拿到的值,这个对象会随着websocket的推送一直改变,这个对象的结构大概是这样:
{
obuEsn:123456,
position_3d:[lon:123,lat:123]
...
}
小白只写了俩个键值对,因为后面只用到这两个,obuEsn大家可以看成车辆的唯一标识,postion_3d就是我们要的经纬度了。
这里因为获取websocket的值是在另一个组件,所以我们需要把值传给地图组件。我这里是兄弟组件,我就直接通过中央事件总线将车辆信息传给地图组件,不熟悉的可以自行百度一下。我简单说一下,
通过Bus.$emit("事件名‘’,车辆数据),这个Bus就是我们自己在外部写一个js文件:
import Vue from 'vue'
export default new Vue()
接着在你需要用这个Bus的组件用中import Bus from “路径” 引入就可以了。
在地图组件也是一样的操作,不过他是接收消息:
Bus.$on("事件名“,(target)=>{
这里的target就是传来的数据,这里我们可以直接调用一个处理数据的方法去展示
实时的轨迹,也可以将值赋给我们定义好的变量,通过watch监听然后处理数据(感觉第二种有点画蛇添足了)
}
这里就是核心部分了,废话不说,上代码:
carMsg: {
handler(newVal) {
if (!newVal) {
return;
}
//过滤场景消息
if (newVal.applicationScenarioPriority) {
return;
}
var currentP = coordtransform.wgs84togcj02(
newVal.position_3d.lon,
newVal.position_3d.lat
);
if (this.homeMap) {
if (this.carMarkerList.length == 0) {
var marker = this.initVehicleMark(newVal);
this.carMarkerIdList.push(newVal.obuEsn);
this.carMarkerList.push({
marker: marker, obuEsn: newVal.obuEsn });
} else {
if (!this.carMarkerIdList.includes(newVal.obuEsn)) {
var marker1 = this.initVehicleMark(newVal);
this.carMarkerIdList.push(newVal.obuEsn);
this.carMarkerList.push({
marker: marker1,
obuEsn: newVal.obuEsn,
});
}
}
//展示轨迹
this.carMarkerList.forEach((item) => {
if (item.obuEsn == newVal.obuEsn) {
if (newVal.motion.speed > 1) {
item.marker.setAngle(newVal.motion.heading);
}
item.marker.setPosition(currentP);
item.marker.setIcon(this.initVehicleIcon(newVal));
}
});
}
},
首先websocket传来的值是一个一个对象,这个大家都清楚。每一个对象都表示一个车辆的信息,可能第一次传来的是兰博基尼A666666,第二次传来的保驰捷B888888,又有可能第二次传来的还是兰博基尼A666666.简单来说他传来的数据是没有任何规律的。那我们该如何在地图上确切的展示多个车辆并确保他们走自己的路而不是走别人的路让别人无路可走呢?
//第一步
if (!newVal) {
return;
}
//第二步
//过滤场景消息
if (newVal.applicationScenarioPriority) {
return;
}
//第三步
var currentP = coordtransform.wgs84togcj02(
newVal.position_3d.lon,
newVal.position_3d.lat
);
第一步通俗易懂就是没有数据的时候不会执行直接返回,第二步是过滤场景信息,因为后端给我的数据一半是场景数据,一半是车辆数据。所以需要过滤一下(大家可以当做没看到,没啥用)。第三步还是很重要的,专业名词叫轨迹纠偏,不管我们要展示历史轨迹和实时轨迹,纠偏都是必不可少的环节。感兴趣可以自行百度。
首先我们要准备两个空数组,第一个是carMarkerIdList用来存储obuEsn的也就是前面说到的唯一标识,第二个就是carMarkerList存储我们已经画好的marker也就是小车车。省的大家往上翻,我再copy一遍。
if (this.homeMap) {
if (this.carMarkerList.length == 0) {
var marker = this.initVehicleMark(newVal);
this.carMarkerIdList.push(newVal.obuEsn);
this.carMarkerList.push({
marker: marker, obuEsn: newVal.obuEsn });
} else {
if (!this.carMarkerIdList.includes(newVal.obuEsn)) {
var marker1 = this.initVehicleMark(newVal);
this.carMarkerIdList.push(newVal.obuEsn);
this.carMarkerList.push({
marker: marker1,
obuEsn: newVal.obuEsn,
});
}
}
//展示轨迹
this.carMarkerList.forEach((item) => {
if (item.obuEsn == newVal.obuEsn) {
if (newVal.motion.speed > 1) {
item.marker.setAngle(newVal.motion.heading);
}
item.marker.setPosition(currentP);
item.marker.setIcon(this.initVehicleIcon(newVal));
}
});
}
首先我们需要判断有没有map这个实例,不然连地图都没得怎么调其中的方法呢?这里的我起名叫homeMap。
然后我们在刚开始的时候我们的数组都是空的,这个时候第一个小车信息会进来,不管是什么情况我们都要把第一个小车存起来对吧,因为只有传第二个小车信息的时候我们才需要进行判断。而且你不存起来后续也无法比较了。这时候我们手上的底牌就是一个marker实例,一个存了这个marker的obu(简化说了)数组,一个存了marker的信息对象。
接下来是第二个小车信息进来了,我们需要判断:
this.carMarkerIdList.includes(newVal.obuEsn)
如果我们之前的数组存在这个obu了也就表示是相同车辆的信息,这个时候就不进入判断,直接往下走去设置position,如果不存在这个obu,我们就再次初始化一个marker然后同样的把这个新来的分别存到两个数组中。
现在我们已经成功的把所有不同的车辆存到数组中,接下来就是让他跑起来了,(注意不是所有的存完才开始跑,再强调一下,当是相同车辆时就会直接往下走去让当前这个车移动一下,至于多少跟后端传给你的经纬度有关)。
this.carMarkerList.forEach((item) => {
if (item.obuEsn == newVal.obuEsn) {
if (newVal.motion.speed > 1) {
item.marker.setAngle(newVal.motion.heading);
}
item.marker.setPosition(currentP);
item.marker.setIcon(this.initVehicleIcon(newVal));
}
});
这里就是让小车跑起来了,我们要用到我们之前存的小车信息的数据去遍历他,然后判断他中的某一个item和当前传入的小车是同一辆,然后再去踩他的油门。这样的对应关系是不是就很清晰了。至于setPosition就是让他移动,想要更丝滑一点也可以用高德的moveTo方法,不过不是今天分享的内容就不赘述了。至于前面没讲得initVehicleMark这个方法就是创建一个marker,这个高德地图api也有,我还是把代码搞下来吧:
initVehicleMark(json) {
if (json.applicationScenarioPriority) {
return;
}
var marker = new AMap.Marker({
map: this.homeMap,
angle: json.motion.heading,
offset: new AMap.Pixel(-45, -45),
position: new AMap.LngLat(json.position_3d.lon, json.position_3d.lat),
icon: this.initVehicleIcon(json),
});
return marker;
},
至于为什么还要setIcon是应为小车有很多灯要展示,比如左转的时候要展示左转向灯亮的图片,这里也不多说了。
希望小白的总结能对写这个功能的道友有那么一点帮助,小白也是摸着石头过河,代码写的肯定有很多不足之处,也希望大神指正。