高德地图路径回放
创建出高德地图
- 安装我们的vue-amap依赖
- 官网https://github.com/ElemeFE/vue-amap里面有详细教学
- 可视化面板安装(推荐 本人超级喜欢的一种方式)
npm install -S vue-amap
- 在main.js上进行配置
// 导入AMap import AMap from 'vue-amap' // 注册 Vue.use(AMap) // 加载 AMap.initAMapApiLoader({ // 高德的key key: '你可爱的key', // 插件集合 plugin: ['AMapManager', 'AMap.Autocomplete', 'AMap.PlaceSearch', 'AMap.Scale', 'AMap.OverView', 'AMap.ToolBar', 'AMap.MapType', 'AMap.PolyEditor', 'AMap.CircleEditor', 'Geocoder', 'Geolocation', 'AMap.MarkerClusterer', 'AMap.PolyEditor', 'AMap.CircleEditor', 'AMap.MouseTool', 'AMap.Driving', 'AMap.CitySearch', 'AMap.InfoWindow', 'AMap.LngLat', 'AMap.DistrictSearch', 'AMap.TileLayer.Traffic', 'AMap.Heatmap', 'AMap.Autocomplete', 'AMap.PlaceSearch'], // 高德 sdk 版本,默认为 1.4.4 v: '1.4.4' })
- 如果出现AMap’ is not defined尝试以下方法解决
- 在.eslintrc.js进行配置
globals: { AMap: true, AMapUI: true, },
- 在index.html里进行配置
<script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=你可爱的key"> </script> <script src="//webapi.amap.com/ui/1.1/main.js?v=1.1.1"></script>
- 在.eslintrc.js进行配置
- 设置存放地图的容器(没出地图的设置div大小)
<!-- 地图 --> <div id="container"></div>
- 设置地图
mounted() { // 设置地图基本上配置 const param = { // 是否监控地图容器尺寸变化 resizeEnable: true, // 初始地图级别 zoom: 15 } this.map = new AMap.Map('container', param) },
做到这一步地图就可以显示出来了
创建路线
路线的样式:https://lbs.amap.com/api/amap-ui/reference-amap-ui/mass-data/pathsimplifier/
-
后台传递来的数据(丢在data或者js导入进来都可以)
// 后台传递来的路线 linePath: [ { latitude: 39.997761, longitude: 116.478935, time: '2020-08-21 16:21:18' }, { latitude: 39.997825, longitude: 116.478939, time: '2020-08-21 16:21:21' }, { latitude: 39.998549, longitude: 116.478912, time: '2020-08-21 16:21:24' }, { latitude: 39.998555, longitude: 116.478998, time: '2020-08-21 16:21:27' }, { latitude: 39.99856, longitude: 116.479282, time: '2020-08-21 16:21:30' }, { latitude: 39.998528, longitude: 116.479658, time: '2020-08-21 16:21:33' }, { latitude: 39.998453, longitude: 116.480151, time: '2020-08-21 16:21:36' }, { latitude: 39.998302, longitude: 116.480784, time: '2020-08-21 16:21:39' }, { latitude: 39.998184, longitude: 116.481149, time: '2020-08-21 16:21:42' }, { latitude: 39.997997, longitude: 116.481573, time: '2020-08-21 16:21:45' }, { latitude: 39.997846, longitude: 116.481863, time: '2020-08-21 16:21:48' }, { latitude: 39.997718, longitude: 116.482072, time: '2020-08-21 16:21:51' }, { latitude: 39.997718, longitude: 116.482362, time: '2020-08-21 16:21:54' }, { latitude: 39.998935, longitude: 116.483633, time: '2020-08-21 16:21:57' }, { latitude: 39.998968, longitude: 116.48367, time: '2020-08-21 16:22:00' }, { latitude: 39.999861, longitude: 116.484648, time: '2020-08-21 16:22:03' } ]
-
后台传递的json,我们需要给他转换为数组
// 初始化 init() { // 后台传递来的数据是json的,所以我们改成数组 // 轨迹 // 线条路线 let linePath = this.linePath linePath.forEach(item => { // 把json转为数组 this.pathList.push([item.longitude, item.latitude]) }) // 设置路线 this.setPath() },
-
设置路线
// 设置路线 setPath() { let that = this AMapUI.load(['ui/misc/PathSimplifier', 'lib/$'], function( PathSimplifier ) { if (!PathSimplifier.supportCanvas) { console.log('当前环境不支持 Canvas!') return } function onload() { that.pathSimplifierIns.renderLater() } function onerror() { console.log('图片加载失败!') } // 历史轨迹巡航器 that.pathSimplifierIns = new PathSimplifier({ zIndex: 100, map: that.map, // 所属的地图实例 getPath: function(pathData) { // 这里的pathData保存的是路线 return pathData.path }, // 自动设置视图 autoSetFitView: true, // 巡航器样式 renderOptions: { // 路径导航样式 pathNavigatorStyle: { // 一开始小车的旋转角度 initRotateDegree: 0, // 小车的宽 width: 20, // 小车的高 height: 32, // 自动旋转 autoRotate: true, // 折线拐点连接处样式 lineJoin: 'round', // PathSimplifier提供了一个快捷方法用于创建图片内容的content(function): // 图片地址 // 图片加载成功,重新绘制一次 onload方法 // 图片加载失败 onerror方法 content: PathSimplifier.Render.Canvas.getImageContent( 'https://webapi.amap.com/images/car.png', onload, onerror ), // 这个位置提示一下 因为我们用的是图片所以看不见效果 // 想看见效果删除上面的content // 他是一个三角形 这里就是设置边框颜色和内部颜色的 // 填充色 fillStyle: null, // 描边色 strokeStyle: null, // 边的宽度 lineWidth: 1, // 巡航器经过的路径的样式 pathLinePassedStyle: { lineWidth: 6, strokeStyle: 'skyblue' } }, // 线条样式 pathLineStyle: { lineWidth: 6, strokeStyle: 'pink' }, // 鼠标移入之后线条的样式 pathLineHoverStyle: { lineWidth: 0, borderWidth: 0 }, // 鼠标单击之后线条的样式 pathLineSelectedStyle: { lineWidth: 6, borderWidth: 0, strokeStyle: 'blue' }, pathTolerance: 0, keyPointTolerance: -1, renderAllPointsIfNumberBelow: 0 // 绘制路线节点,如不需要可设置为-1 } }) // 历史轨迹巡航器设置数据 这里设置的就是上面pathData的数据 that.pathSimplifierIns.setData([ { name: '轨迹', path: that.pathList } ]) }) }
做到这一步路线就可以显示出来了
创建小图标
-
后台传递来的数据(丢在data或者js导入进来都可以)
// 后台传递来的地图上的icon inco: [ { latitude: 39.997761, longitude: 116.478935 }, { latitude: 39.99856, longitude: 116.479282 }, { latitude: 39.999861, longitude: 116.484648 } ]
-
遍历创建icon
// 初始 init() { // 创建起始和经过的icon this.icon.forEach(item => { this.addIcon(item) }) }, addIcon(item) { // 设置每个icon的内容 const marker = new AMap.Marker({ // 图片 icon: '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png', // 位置 position: [item.longitude, item.latitude], //设置基点偏移 offset: new AMap.Pixel(-13, -30) }) // 把图标放在地图上 marker.setMap(this.map) }
-
修改样式(如果图标太大可以修改一下图片 f12去查,设置失败需要考虑作用域更深一层)
// 设置图标大小 /deep/.amap-icon img { width: 25px; height: 34px; }
做到这一步icon就可以显示出来了
创建巡航器
我们先创建巡航器然后再去研究然后动起来
- data里设置一个巡航器
// 巡航器 navgtr: null
- setPath
// 开启中心自适应 that.pathSimplifierIns.setFitView(-1) // 对第一条线路(即索引 0)创建一个巡航器 that.navgtr = that.pathSimplifierIns.createPathNavigator(0, { loop: false // 循环播放 })
做到这一步小车就可以显示出来了
添导入加进度条
-
使用什么框架都可以这里使用iview
- 安装依赖
- 导入(main.js)
// 导入 vue-amap import AMap from 'vue-amap' // 导入iview import iView from 'iview' // 注册iview Vue.use(iView)
-
代码部分
<!-- 进度条 --> <div class="map-control"> <!-- 开始按钮 --> <Icon v-if="!start" class="play-icon" type="ios-play" @click="navgControl"/> <!-- 暂停按钮 --> <Icon v-else class="play-icon" type="ios-pause" @click="navgControl"/> <!-- 开始时间 --> <span class="passed-time">00:00:00</span> <Slider class="map-slider"></Slider> <!-- 倍数 --> <div class="map-times" @mouseenter="isTimesChoose=true" @mouseleave="isTimesChoose=false"> <div class="times-show">倍速{{times}}</div> <div class="choose-box"> <!-- <ul v-show="isTimesChoose"> --> <ul v-show="isTimesChoose"> <li v-for="item in speedList" :key="item" :class="{active:times==item}" @click="changeSpeed(1)">倍速 {{item}}</li> </ul> </div> </div>> <!-- 结束 --> <span class="passed-time">00:00:00</span> </div>
// 设置图标大小 /deep/.amap-icon img { width: 25px; height: 34px; } // 设置循环器样式 .map-control { // 绝对定位 position: absolute; bottom: 0; left: 0; width: 1200px; height: 80px; line-height: 80px; color: #fff; background-image: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); padding: 0 40px; z-index: 1000; // 设置按钮大小 .play-icon { font-size: 36px; } // 设置时间样式 .passed-time { // 相对定位 position: relative; // 转为行内元素 display: inline-block; top: 1px; margin-left: 15px; font-size: 14px; } // 进度条样式 .map-slider { // 转为行内元素 display: inline-block; width: 75%; margin-left: 15px; position: relative; top: 14px; } // 修改默认的进度条样式 .choose-box { // 相对定位 position: absolute; top: -135px; left: -6px; height: 162px; // 过滤 transition: all 0.5s linear; } // 倍数 .map-times { display: inline-block; position: relative; margin-left: 15px; // 倍速样式 .times-show { padding: 0 10px; line-height: 24px; font-size: 13px; border: 1px solid #fff; border-radius: 4px; // 鼠标设置为默认样式 cursor: default; } // 倍数 ul { background: rgba(0, 0, 0, 0.7); padding: 10px; width: 70px; text-align: center; border-radius: 3px; li { height: 26px; line-height: 26px; cursor: pointer; } li.active { color: #ff8533; } li:hover { font-size: 13px; } } } }
// 暂停和播放按钮 navgControl() { this.start = !this.start }, // 进度条 changeSpeed(item) { this.times = item }
计算速度和播放时间
-
我们需要把后台传递给我们的时间转换为时间戳
-
2020-08-21 16:21:18 — 1597998078000
-
在init里继续进行编写代码
linePath.forEach(item => { // 把json转为数组 this.pathList.push([item.longitude, item.latitude]) // 当前时间戳 item.stampTime = new Date(item.time).getTime() })
-
-
我们需要计算出每个路程的速度,这里假设数组的长度为x,里面分别是a、b、c……
-
我们需要计算出a-b的速度 时间 路程 以此类推
-
时间和路程已经给我们了就差速度了这个小学问题就不解释了上代码
-
计算出2点直接的距离,放在for里不方便 提取出来
// 计算两个坐标点之间的距离 distanceFun(point1, point2) { // 那数组转化为经纬度 let p1 = new AMap.LngLat(point1[0], point1[1]) let p2 = new AMap.LngLat(point2[0], point2[1]) // 计算2点直接的距离 distance这个函数有继续可以了解一下 let distance = Math.round(p1.distance(p2)) return distance }
-
计算2点直接的速度
- 计算下一段路程经历了多少s,
- intervalTime间隔时间 秒
- nextDistance:下一段路程:m,
- nextDistance:下一段路速度:km/h
linePath.forEach((item, i) => { // 获得到下一个位置 let next = linePath[i + 1] // 判断是否还有下一个 if (next) { // 计算出间隔时间 每秒 item.intervalTime = (next.stampTime - item.stampTime) / 1000 // 计算出下一站 item.nextDistance = this.distanceFun( [item.longitude, item.latitude], [next.longitude, next.latitude] ) // 求出具体的速度 item.nextSpeed = item.nextDistance / 1000 / (item.intervalTime / 60 / 60) } })
-
计算出总时间(因为太简单了,所以就放在这里直接说了)
// 计算总时间,hh:mm:ss 因为计算出来是时间搓 所以要进行格式化 this.totalTime = this.getTime( (endPoint.stampTime - startPoint.stampTime) / 1000 ) // 格式化时间(不解释) getTime(sTime) { let ss let mm = '00' let hh = '00' if (sTime > 60) { let s = sTime % 60 ss = s > 9 ? s : '0' + s let mTime = parseInt(sTime / 60) if (mTime > 60) { let m = mTime % 60 mm = m > 9 ? m : '0' + m hh = parseInt(mTime / 60) } else { mm = mTime > 9 ? mTime : '0' + mTime } } else { ss = sTime > 9 ? sTime : '0' + sTime } return hh + ':' + mm + ':' + ss }
- 在date里添加
// 总时间 totalTime: '00:00:00',
- 修改页面的总时间
<span class="passed-time">{{totalTime}}</span>
- 在date里添加
-
滑动条改变事件
给滑动条绑定滑动事件
<Slider class="map-slider" @on-input="sliderChange"></Slider>
// 滑动条改变事件
sliderChange(val) {
// 计算开始距离
let num = parseInt((val / 100) * this.pathList.length)
// 计算结束的距离
let decimal =
String((val / 100) * this.pathList.length).split('.')[1] || 0
// 移动小车
this.navgtr.moveToPoint(num, Number('0.' + decimal))
// 重新绘制
this.pathSimplifierIns.renderLater()
},
完成之后移动滑块就可以改变小车位置了
设置移动中的状态
-
我们需要获取到一开的速度,2个办法
- 计算一下一开始的速度
- 直接获取数组0的速度
-
我们采取方案2 首先先把之前遍历的数据设置到data里
// 在init方法加上这行代码 // 修改全局的路线数据 this.linePath = linePath
-
在setPath里编写,获取开始的速度以及给巡航器设置最开始的速度
// 获取初始速度 // 设置默认的为 0.1 let startSpeed = 0.1 if (that.linePath.length !== 0) { // 获取一开始的速度 并且判断是否为0 如果不为0直接返回 为0给一个初始的速度 startSpeed = that.linePath[0].nextSpeed === 0 ? 0.1 : that.linePath[0].nextSpeed } // 对第一条线路(即索引 0)创建一个巡航器 that.navgtr = that.pathSimplifierIns.createPathNavigator(0, { loop: false, // 循环播放 // 速度×倍速 speed: startSpeed * that.times })
-
计算出开始时间和结束时间,原来展现的 在setPath里编写
let linePath = that.linePath let len = linePath.length let startPoint = linePath[0] let endPoint = linePath[len - 1]
-
编写移动状态
-
修改滑动条和开始时间的样式
<!-- 开始时间 将00 设置为经过的时间 不能再固定写死了--> <span class="passed-time">{{passedTime}}</span> <!-- 滑动条修改2个地方 step步长 tip-forma提示框 --> <Slider class="map-slider" @on-input="sliderChange" :step="0.0001" :tip-format="hideFormat"></Slider>
// 滑动条的提示 hideFormat() { return this.passedTime }
-
移动状态
// 移动过程中 that.navgtr.on('move', function() { // 走到了第几个点 let idx = this.getCursor().idx // 至下一个节点的比例位置 let tail = this.getCursor().tail // 总路程 let totalIdx = idx + tail // 计算下一个距离速度 let point = linePath[idx] if (idx < len - 1) { // 判断速度是否为0 如果为0 需要给个0.1的速度 point.nextSpeed === 0 && (point.nextSpeed = 0.1) // 这里的速度记得×倍数 that.navgtr.setSpeed(point.nextSpeed * that.times) } // 剩余公里数,窗体随时移动展示 // 判断是否还有下一个点和时间 point && point.time && infoWindow.setContent( `<p class="info-window">时间:<span>${point.time}` ) // 设置提示框 infoWindow.open(that.map, that.navgtr.getPosition()) // 进度条实时展示tail !that.isOnSlider && (that.sliderVal = (totalIdx / len) * 100) // 已经播放时间 let sTime = parseInt( (((endPoint.stampTime - startPoint.stampTime) / 1000) * that.sliderVal) / 100 ) // 格式化时间 that.passedTime = that.getTime(sTime) // 如果到头了,回到初始状态 if (that.navgtr.isCursorAtPathEnd()) { // 设置为暂停按钮 that.start = false that.passedTime = that.totalTime } })
-
小车移动
-
首先我们可以先了解一下小车的事件有哪些
- resume 继续(不会绘制已经走过的颜色)
- pause 暂停
- start 开始 这里会回到原位
-
所以根据这个我们应该就可以有清楚的思路了
- 如果开始按钮设置了start ,那么你重新开始的时候就会回到原位
- 所以移动过程需要设置resume 继续是最好的
- pause暂停之后只能使用resume 继续
- 所以我们的思路就路程结束之后,需要设置start
-
修改开始按钮
<!-- 开始按钮 --> <Icon v-if="!start" class="play-icon" type="ios-play" @click="navgControl(playIcon)"/>
-
修改暂停按钮
<!-- 暂停按钮 --> <Icon v-else class="play-icon" type="ios-pause" @click="navgControl('pause')"/>
-
设置playIcon的初始数据
-
playIcon: 'start' //开始按钮是重新开始还是继续
-
-
修改setpath中路程结束的代码
// 如果到头了,回到初始状态 if (that.navgtr.isCursorAtPathEnd()) { // 设置为开始状态 让小车回到原来位置 that.playIcon = 'start' // 设置图标状态 that.start = false // 设置已经走过的时间 that.passedTime = that.totalTime // 设置滑动条状态 that.sliderVal = 100 }
-
暂停开始按钮
// 暂停和播放按钮 navgControl(action) { if (action === 'start') { let that = this this.passedTime = '00:00:00' setTimeout(() => { that.navgtr[action]() }, 300) } else { this.navgtr[action]() } // 修改图标状态 this.start = !this.start },
-
在移动过程也需要进行设置
// 移动过程中 that.navgtr.on('move', function() { that.playIcon = 'resume' //……………………
-
设置滑块(之前忘记设计了)
-
给滑块绑定数据 v-model=“sliderVal”
<Slider class="map-slider" v-model="sliderVal" @on-input="sliderChange" :step="0.0001" :tip-format="hideFormat"></Slider> ```
-
-
然后发现修改速度的也不行。。。。。。原来是我传值的时候传了1。。。(设置代码时候还是要注意呀)
-
changeSpeed(item) 这里记住是item哦
<li v-for="item in speedList" :key="item" :class="{active:times==item}" @click="changeSpeed(item)">倍速 {{item}}</li> ```
-
然后发现。。。ul的关闭也忘记设置为false了
// 修改速度 changeSpeed(item) { this.times = item this.isTimesChoose = false },
-