插件git地址为 github.com/spiritJun/T…
demo的git地址为 github.com/spiritJun/t…
场景:
(上一篇文章已经讲解了高德地图的初始化过程再次提示上诉所说TrackFactory类后边会有交代敬请期待)画线这个需求其实是在高德地图实例加载成功后的操作,如果说实例化地图是在打地基那么这个画线以及纠偏的实例就是搭建整个功能的架子了尤为重要!此方法将会初始化高德地图AMap.Polyline画线实例以及AMap.Marker demo图中小车的实例以及将轨迹进行500个点的批次递归纠偏(为了性能考虑没有使用两点之间的纠偏)以及动态添加轨迹点并且画线功能(说的有些啰嗦其实大概就是为了封装的更完善吧毕竟有可能后端同学可能会进行分页或者socket推送数据过来这样就需要前端优化一下性能了,大神不喜勿喷撒);
需要思考的问题:
1.因为此次使用的是高德地图500个轨迹点的纠偏方法,所以后端传来的数据需要进行500个轨迹点的分页处理,但是后端并不会一次性传给前端所有数据 或推流或者分页这样的话需要前端保证500个点的分页的顺序
2.轨迹点连接的问题,有这么一个场景,前500个点都是同一个点换言之小车并未移动,但是如果将这500个点丢给高德的话高德就会抛出异常并不会进行纠偏,这个时候就会出现一个问题就是画线的过程中出现中断链接不上或者出现直线的问题
3.当每次添加轨迹点的时候 需要等待所有轨迹纠偏、小车画线以及画线完毕以后进行下一次画线不然的话就会出现循环中的索引错乱问题这也就是我们常说的异步循环变同步循环
4.对数据的处理轨迹纠偏前后进行数据保存并且一一对应因为需要后边的单元测试以及对数据进行debugger处理以及清楚的看到到底是哪一次的推送或者数据出现了问题
5.因为此次轨迹纠偏是以500个点的纠偏,但是其实并不准确有许多直线的情况,所以还是两点间纠偏更为靠谱一点但这样设计又会将整个的纠偏思路以及后续的画线小车移动全部打乱等同于重构后期的话我会将其设置为可配置方案并解耦
整个轨迹纠偏以及画线的设计思路:
1.前端将以500个点进行总页码的计算,对后端的未纠偏的数据进行前端以500个点的递归处理,处理成高德地图能纠偏的数据也就是如下数据 * {
* 1:[{
* "x": item.lon *1,
"y": item.lat *1,
"ag": item.agl *1,
"sp": item.spd *1,
"tm": tm
* }],
2:[...]
* }
2.将处理完的数据进行高德地图500个点的纠偏处理此时需要判断当前后端推送的数据以及当前页码的校验因为不需要纠偏多次重复的数据,然后进行循环纠偏并且把数据以同样的页码当key值存入已纠偏的数组中这样未纠偏的500个点的页码与纠偏完的数据就能一一对应上了 这时就可以看到是哪500个点出现了问题
3.在循环体中趁热进行在拿到纠偏成功的数据之后进行画线处理(这里我在纠偏处做个了小操作即使高德纠偏未成功我也会返回一个空数组出来不会抛出异常阻塞下面的循环进行)
4.根据业务需求封装画线的样式以及初始化画线的类以及小车等并且控制展示
需要的props以及向外抛出的方法 __proto__如图所示
代码如下所示,因为太长我就压缩了
export default class WriteHistoryLine { constructor(props) { this.props = { ...props }; this.historyBeforeRedress = []; //纠偏前的数据处理 this.historyAfterRedress = [];//纠偏后的数据处理 难过 this.deepNum = 0;//轮询的次数 this.renderPageNo = 1;//画线的第几页 相当于500一页 不用find这种方法 因为不能因为循环影响了性能 this.currentMarkerBox = null;//小车的实例 this._init(); } //纠偏前的数据处理 需要额外解析deepNum _renderDeepNum (data = this.props.data, count_ = 0) { if (data.length > 500) { if (data.length % 500 == 0) { this.deepNum = data.length / 500 + count_; } else { this.deepNum = Math.ceil(data.length / 500) + count_; } } else { this.deepNum = count_ + 1 || 1; } } _init () { if (!this.props.AMap || !this.props.data) { this.props.writeError && this.props.writeError(); } else { //进行轨迹纠偏 if (this.props.isReDress) { this._renderDeepNum(); this._reduceHistoryData({ data: this.props.data }); } else { //不需要纠偏 直接跑车 // this._drawLine(graspRoadArr).then(res => { // let beginPosition = this.historyBeforeRedress[0][1];//历史轨迹是初始点 // beginPosition = beginPosition[0]; // this._drawCar([beginPosition.x, beginPosition.y], beginPosition.ag); // // this.props.map.setFitView(); // this.props.map.setZoomAndCenter(14, [beginPosition.x, beginPosition.y]); // }).catch(err => { // console.error(err); // }); } } } /** * 将数据进行隔断渲染 最终渲染结果为 * { * 1:[{ * "x": item.lon *1, "y": item.lat *1, "ag": item.agl *1, "sp": item.spd *1, "tm": tm * }], 2:[...] * } */ _reduceHistoryData ({ data = [], count_ = 0, isAddLine = false }) {//记录一下当前循环的次数与递归是否匹配 let count = 0; let currentCount = 0; let preTime, curTime, tm; let arr = []; //historyBeforeRedress const callBack = () => { for (let i = currentCount; i < currentCount + 500; i++) { if (i > data.length - 1) { break; } else { let item = data[i]; curTime = new Date(item.utc); if (i == 0) { tm = 1478031031; //初试时间 1970- } else { preTime = !preTime ? new Date(data[i - 1].utc) : preTime; tm = Math.round(curTime.getTime() / 1000) - Math.round(preTime.getTime() / 1000); } arr.push({ x: Number(item.lon), y: Number(item.lat), ag: Number(item.agl), sp: Number(item.spd), tm }); preTime = curTime; } } count += 1; if (arr.length > data.length) { return; } else { if (count <= this.deepNum) { currentCount += 500; callBack(); } else { if (!isAddLine) { for (let k = 0; k < this.deepNum; k++) { this.historyBeforeRedress.push({ [k + 1]: arr.slice(k * 500, k * 500 + 500), }); } } else { for (let k = 0; k < (this.deepNum - count_); k++) { this.historyBeforeRedress.push({ [k + count_ + 1]: arr.slice(k * 500, k * 500 + 500), }); } } this._reDressLine(count_);//纠偏 } } } callBack(); } //纠偏 500个点的高德纠偏 async _reDressLine (filterNumber) { console.log(this.historyBeforeRedress); //画全部的轨迹 先注释掉 // let historyBeforeRedress = this.historyBeforeRedress; // this.renderPageNo = 1; //画全部的轨迹 先注释掉 let historyBeforeRedress = this.historyBeforeRedress.filter((item, index) => { return index + 1 > filterNumber; //画线优化 每次都把上一次最后一个500条目的轨迹进行纠偏 这样可以连上了 就大于等于 目前应该不用 不想多画线 }); // this.renderPageNo = filterNumber || this.renderPageNo; //默认数据是CRM的数据源 所以解析的话 还是递归 处理 Promise for (let i = 0; i < historyBeforeRedress.length; i++) { let item = historyBeforeRedress[i]; if (this.deepNum >= this.renderPageNo) { //大于的时候才会画图 这块纠偏一次就加一次能对得上 //可能会有几百个点都是同一个点的情况 这种情况需要下一版优化 let graspRoadArr = await this._reDressGraspRoad(item[this.renderPageNo]).catch(errItem => { console.error(errItem); }); this.historyAfterRedress.push({ [this.renderPageNo]: graspRoadArr }); this.renderPageNo += 1; //画线方法复用 if (!this.props.isRealTime) { await this._drawLine(graspRoadArr).catch(err => { console.error(err); }); //业务争议 // this.props.map.setFitView(); } } } //可以优化一下 因为不能有空数组的 就可以重新纠偏一下 然后把数组赋值 链接一下 !filterNumber && this.props.isShowCar && this._renderCarLine(); } //整除的问题 续上的问题 边画边渲染的问题 //(画小车)处理纠偏完之后的数据 有可能前几页都没有数据的情况 都得考虑一下小车的感受 _renderCarLine () { //数据格式为 [ {1:[500个点]},{2:[500个点]} let beginPosition = null; for (let i = 0; i < this.historyAfterRedress.length; i++) { let item = this.historyAfterRedress[i]; if (item[i + 1].length > 0) { beginPosition = item[i + 1][0]; break; } } if (beginPosition) { this._drawCar([beginPosition.x, beginPosition.y]); // this.props.map.setFitView(); this.props.map.setZoomAndCenter(20, [beginPosition.x, beginPosition.y]); console.log(this.historyAfterRedress); } } //处理纠偏的方法 _reDressGraspRoad (redressArr) { return new Promise((resolve, reject) => { this.props.AMap.plugin("AMap.GraspRoad", () => { const grasp = new this.props.AMap.GraspRoad(); grasp.driving(redressArr, (error, result) => { if (error) { resolve([]); // reject(redressArr[0]); 后期优化 } else { // console.log(result.data.points); resolve(result.data.points); } }); }); }) } //画线~~ 纠完偏就画线 _drawLine (polyPath) { let path = []; polyPath.forEach(item => { path.push([item.x, item.y]) }); let strokeObj = this.props.strokeObj || { strokeColor: "#28F", //线颜色 strokeOpacity: 0.8, //线透明度 strokeWeight: 5, //线宽 strokeStyle: "solid", //线样式 lineJoin: "round", zIndex: 60, strokeDasharray: [10, 5] //补充线样式 } return new Promise((res, rej) => { let polyline = new this.props.AMap.Polyline({ path, //设置线覆盖物路径 ...strokeObj, }); polyline.setMap(this.props.map); // console.log('完成') res(); }) } //画小车 开小车 _drawCar (position, agl) { let icon = new this.props.AMap.Icon({ image: this.props.carPoto, size: [60, 32], imageSize: new this.props.AMap.Size(60, 32) }); if (this.currentMarkerBox) { this.currentMarkerBox.setMap(null); this.currentMarkerBox = null; } let angle = agl ? Number(agl) - 90 : 0; this.currentMarkerBox = new this.props.AMap.Marker({ icon, autoRotation: true, angle, offset: new this.props.AMap.Pixel(-10, -20), position, }); this.currentMarkerBox.setMap(this.props.map); } //添加轨迹点并且进行轨迹纠偏 addPoint (data, count_) { return new Promise((resolve, reject) => { this._searchLoopRenderNum(() => { this._renderDeepNum(data, count_); this._reduceHistoryData({ data, count_, isAddLine: true }); // setTimeout(() => { // console.log(this.historyAfterRedress); // }, 500); resolve(this.historyAfterRedress); }) }) } //轮询一下吧还是 _searchLoopRenderNum (callback) { if (this.deepNum == this.renderPageNo) { callback(); } else { setTimeout(() => { this._searchLoopRenderNum(callback); }, 300) } } //轮询一下吧还是 查询当前是否有Marker类 小车的实例 哎 searchLoopMarker (callback) { if (this.currentMarkerBox) { callback(); } else { setTimeout(() => { this.searchLoopMarker(callback); }, 300) } }}