重要:博客内使用地图全替换为WMap,想使用的请参考我这篇博客WMap
最近都没发博客,因为一直在写关于小车历史轨迹这部分的逻辑,目前已经可以了,分别实现了历史双轨迹,历史单轨迹,实时轨迹
先说说我的小车轨迹,主要分为小车 和 轨迹 两部分,小车由Marker类实例化(Marker类是我开发的地图中的类,至于这个类,在我博客中可以看到, 我的地图文档(WMap))上面是链接,Marker类继承于openlayers的Feature类。轨迹由我的Polyline实例化,继承于openlayers的LineString类。
轨迹线类具体讲解在上面我的链接里的第12个类
历史双轨迹:
我们可以看到,双轨迹意思就是:有两条轨迹线,下面蓝色的是历史总轨迹线,上面的是小车在历史轨迹中的实时线,这很好理解吧,两条线最终都会重合,并且这两条线的颜色都是可控的。
const polyline = new WMap.Polyline({
map,
path: lineArr,
showDir: true, // 是否显示箭头
strokeColor: '#28F', // 线颜色 历史双轨迹线必须
activeStrokeColor: 'green', // 活跃状态时的线条颜色 历史双轨迹线必须
strokeWeight: 6, // 线宽
segmentLength: 2, // 最小子路径长度,不传默认为2
units: 'meters' // 最小子路径单位,不传默认为meters
})
可以看出,activeStrokeColor是上面实时线的颜色,strokeColor是下面总线的颜色,其他参数都好理解,segmentLength这个参数是我特意加进去的,为啥呢,因为我们传递路径数组时候,很多时候两个坐标点距离过远,因为小车移动,我是监听移动时候的实时路径数组的,所以当两点间距过大,就会导致小车突然位移很远,从而达不到一个较平滑的动画效果,所以我将所有传进来的路径数组做了处理:
将传进来的路径数组先进行去重,再进行分段路径处理,以segmentLength的距离来进行等距离分割,再将此数据传入我们的path参数中,这样我们就好在小车移动的时候拿到实时路径:
marker.on('moving', function (e) {
polyline.setPath(e.passedPath)
})
上面代码中的passPath属性是我当前所取到的已经跑过的路径集合,setPath方法,就是我写在Polyline类中的方法,目的是 对实时线进行 setCoordinates 操作。
历史单轨迹:
单轨迹和双轨迹相比,就是单纯的小车在总线上跑,没有那条实时线,那么在参数化实例时少了 activeStrokeColor 参数
// 历史单轨迹线和双轨迹线相比,就少了一个 activeStrokeColor 参数,其他都相同,就不重复写了
const polyline = new WMap.Polyline({
map,
path: lineArr,
showDir: true, // 是否显示箭头
strokeColor: '#28F', // 线颜色 历史单轨迹线必须
strokeWeight: 6, // 线宽
segmentLength: 2 // 分段距离 默认为2m
})
以及在监听小车移动时,不需要做setPath操作,因为我上面说过,此方法是动态绘制实时线的,历史单轨迹不需要绘制实时线,所以就不需要setPath。
另外我地图 轨迹类 完全支持小车的操作: 小车的开始暂停继续停止
// 开始动画
startButton.addEventListener('click', function () {
startAnimation()
})
// 暂停动画
pauseButton.addEventListener('click', function () {
pauseAnimation()
})
// 继续动画
resumeButton.addEventListener('click', function () {
resumeAnimation()
})
// 停止动画
stopButton.addEventListener('click', function () {
stopAnimation()
})
实时轨迹:
实时轨迹就和历史轨迹不一样了,因为没法提前知道所有路径数组的点,都是接口返回一次才知道当前的所在位置,所以全是实时绘制的,自然而然,参数也就很少,只有下面这几个。
// 实时轨迹线,就是在定时接收数据时候,在形成当前轨迹,所以参数较少,下面定时器就是模拟接收数据
const polyline = new WMap.Polyline({
map,
activeStrokeColor: 'green', // 活跃状态时的线条颜色 实时轨迹线必须
strokeWeight: 6, // 线宽
})
lineArr.map((item,i) => {
setTimeout(() => {
marker.move(polyline, item)
}, i * 1000)
})
上面我们使用定时器来模拟接口定时返回坐标点,这里我们就只需要调用move方法就行了,也不需要监听小车移动了,自然而然,实时轨迹是没有啥开始暂停等动画的(这个好理解吧,实时的咋可能有这些,手动狗头...)(不过想实现也不是不可能,不过我目前可能不太想写这些没啥必要的优化)。
疑难迷惑点:
1、大家看到的箭头,是如何生成的呢?
答:其实不难发现,只有在历史轨迹中才有箭头,此箭头是通过先拿到轨迹点完整数组,然后计算屏幕像素点位置,等距离分割,拿到每个点的位置,然后在Polyline类所继承的Feature类调用setStyle(feature => { const style = (生成箭头的方法) return style }) 方法来将每个箭头都添加到轨迹线上。
2、历史轨迹线中的setPath方法,究竟是如何实现的添加动态轨迹点?
答:其实在历史双轨迹实例中,setPath作用就是重复的 Feature.getGeometry().setCoordinates(path),在实时轨迹中(ps:不是说setPath只在历史轨迹中调用吗?那是因为我将此方法集成在实时轨迹中的move方法中了,就没暴露出来),Feature.getGeometry().appendCoordinate(path)。
3、双轨迹线是如何实现的呢?
答:其实很奇妙,最开始我们写的时候,是实例化两个Polyline类,每个Polyline有自己的Feature,每个Feature在有一个LineString,但是这会导致我们在高速度跑小车时,两条线的坐标点会有偏移,导致上面的实时线不能完全遮盖下面的总线,所以后面我优化后,发现只需要实例化一个Polyline类,然后在这个类里面实例化两个Feature类,再加上我们使用封装后的统一路径数组,所以能实现完美重合。
4、关于分段距离方法优化?
答:其实我们在拿到接口数据时,很多原因会导致拿到的坐标路径数组里有重复的坐标点,或者两个相邻坐标点距离位置过远,这都是很经常发生的,但是我们地图上要是不对这些数据进行处理,就会使小车运行效果很差,所以我想到,调用 const line = turf.lineString(path),来将我们获取到的数组传进来,生成一条线,再通过const lineChunk = turf.lineChunk(line, segmentLength, {units: 'meters'})将这条线划分成一个个等距离线段,那我们肯定要对这些线段进行去重处理,那么使用 arrayDup(lineChunk.features.filter(item => item.type === 'Feature').map(item => item.geometry.coordinates).flat()) 来最终返回处理过后的路径数组(arrayDup方法是二维数组去重方法)(通用方法链接)
5. 历史轨迹大数据量处理导致页面卡顿?
此问题感谢@ZiLanSeDeHuanXiang 这位同学提出来,因为作为个人开发者,以前可能没尝试百万虚拟轨迹段大量处理,忽略了,所以当处理后数据量太大时,会严重卡顿页面,所以我采取了web worker方式来对虚拟轨迹计算,所以一进来页面是没有任何影响的,web worker单开线程进行计算,当计算好的时候,使用Polyline.getVirtuaPath(),返回有值,则代表轨迹点计算好了,则可以开始进行小车运动啦~(ps:虚拟轨迹路径不是你传入进去的路径喔,切记,你传入的是原始路径,虚拟轨迹路径是我根据相关算法得出来的)
大家有不懂的可以随时在下面评论,我都会不定时看的,知无不言言无不尽。另外目前我的地图库是纯js写的,不存在兼容性问题,想了解源码性问题的,也可以和我交流哦,林大大哟是我唯一官方账号,并且只在csdn上发布,其他站点发布的请勿相信