支持放大缩小 调动播放进度 搜索轨迹时段 调整播放速度等。
步骤1:准备工作
1.1 获取地图API密钥
首先,你需要在百度地图开放平台注册一个开发者账号,并创建一个应用,以获取API密钥。API密钥是用于标识你的应用身份的重要凭证。
步骤2:引入地图JavaScript API 在index.html添加以下代码
1.3 引入地图API和样式表
在项目的public/index.html文件中引入百度地图API和样式表:
<script type="text/javascript" src="http://api.map.baidu.com/api?v=3.0&ak=换成自己的API秘钥"></script> <script type="text/javascript" src="https://api.map.baidu.com/library/TextIconOverlay/1.2/src/TextIconOverlay_min.js"></script> <script type="text/javascript" src="https://api.map.baidu.com/library/MarkerClusterer/1.2/src/MarkerClusterer_min.js"></script>
常用API表格:
API | 描述 | 使用示例 |
BMap.Map(container [, opts]) | 创建地图实例 | new BMap.Map(“map-container”); |
BMap.Point(lng, lat) | 创建地理坐标点实例 | new BMap.Point(114.161291, 22.644619); |
BMap.Marker(point [, opts]) | 创建标记点实例 | new BMap.Marker(point); |
BMap.Icon(url, size [, opts]) | 创建标记点图标实例 | new BMap.Icon(“marker.png”, new BMap.Size(30, 30)); |
BMap.Polyline(points [, opts]) | 创建折线实例 | new BMap.Polyline([point1, point2]); |
BMap.InfoWindow(content [, opts]) | 创建信息窗口实例 | new BMap.InfoWindow(“内容”); |
BMap.NavigationControl(opts) | 创建地图缩放控件实例 | new BMap.NavigationControl(); |
BMapLib.MarkerClusterer(map, opts) | 创建点聚合实例 | new BMapLib.MarkerClusterer(map, { markers: markers }); |
map.centerAndZoom(center, zoom) | 设置地图中心点和缩放级别 | map.centerAndZoom(point, 15); |
map.addOverlay(overlay) | 添加覆盖物到地图 | map.addOverlay(marker); |
map.clearOverlays() | 清除地图上的所有覆盖物 | map.clearOverlays(); |
map.enableScrollWheelZoom() | 启用鼠标滚轮缩放 | map.enableScrollWheelZoom(); |
map.getDistance(point1, point2) | 计算两点间的直线距离 | map.getDistance(point1, point2); |
marker.addEventListener(type, handler) | 为标记点添加事件监听器 | marker.addEventListener(“click”, function() { console.log(“点击标记点”); }); |
polyline.getPath() | 获取折线的路径 | polyline.getPath(); |
InfoWindow.setContent(content) | 设置信息窗口的内容 | infoWindow.setContent(“新内容”); |
注意:
- opts 为选项参数,具体可参考官方文档。
- 示例中的 point1、point2 等是 BMap.Point 的实例。
- 在示例中可能使用了一些虚拟的数据,具体情况根据项目实际需求来设置。
以上是一些基础的API,百度地图提供了丰富的功能,可以根据实际需求查阅百度地图JavaScript API官方文档获取更多详细信息。
步骤3:编写
父组件:BaiduMap.vue
模板部分: <div ref="map" class="map-container"></div>:地图容器。 <a-modal>:断油电操作的模态框,包括确认按钮和输入备注的文本框。 <a-modal>:恢复油电操作的模态框,同样包括确认按钮和输入备注的文本框。 <a-modal>:轨迹回放的模态框,包含了子组件 <run-map> 来展示实际的轨迹。 数据: map:百度地图实例。 markers:存储标记的数组。 infoWindows:存储信息窗口的数组。 markerCluster:MarkerClusterer 实例,用于标记点聚合。 devices:设备信息数据。 modalBreak和modalRestore:控制断油电和恢复油电的模态框显示。 modaldrivingTrajectory:控制轨迹回放的模态框显示。 url:包含一些后端接口的URL。 deviceNum、optRemark、numberplate:一些操作所需的参数。 方法: initMap():初始化地图,设置中心点和缩放级别,添加控件,并加载地图标点数据。 loadRandomDevices():加载地图标点数据,清除之前的标记、信息窗口和点聚合,添加新的标记、信息窗口和点聚合。 createPopupContent(point):根据设备信息创建信息窗口内容。 handleBreakOk()和handleRestoreOk():处理断油电和恢复油电的确认事件。 cancellation():关闭轨迹回放的模态框。
<template> <div class="parent-container"> <div ref="map" class="map-container"></div> <!-- <button @click="loadRandomDevices">加载随机设备</button> --> <a-modal v-model="modalBreak" title="断油电" @ok="handleBreakOk"> <div style="font-size: 16px; font-weight: bolder;"><img src="../../assets/jingao.png" width="40px" height="40px"></img>您确定要下发<span style="font-size: 18px; color: red;"> 断油电</span> 指令吗?</div> <!-- <a-radio-group v-model="triggerType" style="margin-top: 30px;"> --> <!-- <a-radio :value="合同未交款断油电">合同未交款断油电</a-radio> <a-radio :value="违竟断油电">违竟断油电</a-radio> <a-radio :value="其他">其他:</a-radio> --> <!-- </a-radio-group> --> <a-input v-model="optRemark" placeholder="请输入备注" style="margin-top: 30px;"> </a-input> </a-modal> <a-modal v-model="modalRestore" title="恢复油电" @ok="handleRestoreOk"> <div style="font-size: 16px; font-weight: bolder;"><img src="../../assets/jingao.png" width="40px" height="40px"></img>您确定要执行<span style="font-size: 18px; color: red;"> 恢复油电</span> 指令吗?</div> <a-input v-model="optRemark" placeholder="请输入备注" style="margin-top: 30px;"> </a-input> </a-modal> <a-modal width="100%" height="100%" v-model="modaldrivingTrajectory" title="轨迹回放" > <run-map :deviceNum="deviceNum"></run-map> <template slot="footer"> <a-button @click="cancellation">关闭</a-button> </template> </a-modal> </div> </template> <script> import Vue from 'vue' import { getAction, httpAction, postAction } from '../../api/manage' import RunMap from './GjMap.vue' export default { props: { gpsInfo: { type: Object, } }, components: { RunMap }, data() { return { map: null, markers: [], // 存储标记 infoWindows: [], // 存储信息窗口 markerCluster: null, // MarkerClusterer 实例 devices: [], modalBreak: false, // 模态框可见状态 modaldrivingTrajectory:false,//轨迹回放 modalRestore: false, url: { //断油电 恢复油电 executeCmd: '/jeecg-customers/car/carCheXiaoGpsController/executeCmd', //获取轨迹url trajectory: '/jeecg-customers/ car/carCheXiaoGpsController/trajectory', }, //设备编号 deviceNum: null, optRemark: null, numberplate: null, }; }, mounted() { this.initMap(); }, methods: { initMap() { // 创建地图实例 this.map = new BMap.Map(this.$refs.map); // 设置地图中心点和缩放级别 const point = new BMap.Point(114.16129136801659, 22.64461948509109); this.map.centerAndZoom(point, 10); // 添加地图缩放控件 const navigationControl = new BMap.NavigationControl(); this.map.addControl(navigationControl); // 启用鼠标滚轮缩放 this.map.enableScrollWheelZoom(); //加载地图点数据 this.loadRandomDevices(); }, createPopupContent(point) { // 创建信息窗口内容,包括设备属性 return ` <div class="popup-container"> <div class="popup-header">设备信息</div> <div class="popup-content"> <p>设备号:<spen id="deviceNum"> ${point.deviceNum}</spen></p> <p>车牌号码:<spen id="licenseName"> ${point.licenseName}</spen></p> <p>最近定位位置: ${point.address}</p> <p>经度: ${point.lng}</p> <p>纬度: ${point.lat}</p> <p>设备状态: ${point.deviceState}</p> <span id="handleBreak" style="color: red; font-size: 15px;" οnmοuseοver="this.style.backgroundColor='lightgray'" οnmοuseοut="this.style.backgroundColor='initial'">断油电</span> <span id="handleRestore" style="color: blue; font-size: 15px;" οnmοuseοver="this.style.backgroundColor='lightgray'" οnmοuseοut="this.style.backgroundColor='initial'">恢复油电</span> <span id="drivingTrajectory" style="color: green; font-size: 15px;" οnmοuseοver="this.style.backgroundColor='lightgray'" οnmοuseοut="this.style.backgroundColor='initial'">行驶轨迹</span> </div> </div> `; }, /** * 加载地图标点数据 */ loadRandomDevices() { // 清除之前的标记、信息窗口和点聚合 this.map.clearOverlays(); this.markers = []; this.infoWindows = []; if (this.markerCluster) { this.markerCluster.clearMarkers(); } // 定义自定义图标的样式 // const myIcon = new BMap.Icon(require("@/assets/car.png"), new BMap.Size(32, 32), { // anchor: new BMap.Size(16, 32), // 图标的定位点相对于图标左上角的偏移 // imageSize: new BMap.Size(32, 32) // 图标的大小 // }); // 根据 gpsInfo 的经纬度设置地图中心点 const gpsInfo = this.gpsInfo; const centerPoint = new BMap.Point(gpsInfo.lng, gpsInfo.lat); this.map.setCenter(centerPoint); // 设置缩放级别 this.map.setZoom(18); const point = new BMap.Point(gpsInfo.lng, gpsInfo.lat); // const marker = new BMap.Marker(point, { // icon: myIcon // }); const marker = new BMap.Marker(point); //创建信息窗口 const infoWindow = new BMap.InfoWindow(this.createPopupContent(gpsInfo)); //添加单击事件侦听器以打开信息窗口 marker.addEventListener("click", () => { this.map.openInfoWindow(infoWindow, point); const deviceIdElement = document.getElementById("deviceNum"); const deviceNum = deviceIdElement.textContent.trim(); const licenseNameElement = document.getElementById("licenseName"); const licenseName = licenseNameElement.textContent.trim(); this.deviceNum = deviceNum; this.numberplate = licenseName // 获取按钮元素并添加点击事件 const handleBreak = document.getElementById("handleBreak"); handleBreak.addEventListener("click", () => { this.modalBreak = true; // 打开模态框 // console.log("执行断油电操作"); console.log("设备编号:", deviceNum); }); const handleRestore = document.getElementById("handleRestore"); handleRestore.addEventListener("click", () => { console.log("执行恢复油电操作"); this.modalRestore=true; }) const drivingTrajectory = document.getElementById("drivingTrajectory"); drivingTrajectory.addEventListener("click", () => { console.log("行驶轨迹执行事件"); this.modaldrivingTrajectory=true }) }); // 将标记和信息窗口添加到各自的数组中 this.markers.push(marker); this.infoWindows.push(infoWindow); // 添加标记到地图 this.map.addOverlay(marker); // }); // 点聚合 this.markerCluster = new BMapLib.MarkerClusterer(this.map, { markers: this.markers, gridSize: 80, // 根据需要进行调整 maxZoom: 18, }); }, cancellation(){ this.modaldrivingTrajectory=false }, /** * 确定断油电事件 */ handleBreakOk() { if (this.optRemark == null) { this.$message.info("备注不能为空") return } httpAction(this.url.executeCmd, { "deviceNum": this.deviceNum, "switchKey": "14", "remark": this.optRemark, "numberplate": this.numberplate }, 'post').then((res) => { if (res) { console.log(res) this.$message.info(res.result) } }) this.modalBreak = false; // 关闭模态框 }, /** * 确定恢复油电事件 */ handleRestoreOk() { if (this.optRemark == null) { this.$message.info("备注不能为空") return } httpAction(this.url.executeCmd, { "deviceNum": this.deviceNum, "switchKey": "15", "remark": this.optRemark, "numberplate": this.numberplate, }, 'post').then((res) => { if (res) { console.log(res) this.$message.info(res.result) } }) this.modalRestore = false; // 关闭模态框 }, }, }; </script> <style scoped> .parent-container { width: 100%; height: 800px; display: flex; flex-direction: column; align-items: center; justify-content: center; } .map-container { width: 100%; height: 100%; } button { margin-top: 10px; } </style>
轨迹回放组件:RunMap.vue
模板部分: <div ref="mapContainer" style="width: 100%; height: 100vh;"></div>:地图容器。 控制面板:包含轨迹时段选择、搜索、播放、暂停、进度调节等功能。 数据: dateData:轨迹时段的日期数据。 startDate和endDate:选择的开始和结束日期时间。 map:百度地图实例。 pathData:轨迹点的数据。 marker:地图上的标记点。 currentIndex:当前轨迹点索引。 speed:轨迹播放速度。 isPlaying:是否正在播放。 progressPercent:轨迹播放进度百分比。 url:包含后端接口的URL。 totalPoints:轨迹点总数。 方法: handleDateFilterChange():处理日期范围变化。 searchData():执行搜索轨迹数据的操作。 decreaseProgress()和increaseProgress():调整轨迹播放进度。 updateMarkerPosition():更新标记的位置。 pauseAnimation()和startAnimation():暂停和播放轨迹动画。 generatePathData():生成轨迹点数据。 initMap():初始化地图,添加标记、标点、路径等,并开始动画。 animateMarker():播放轨迹动画的主要逻辑。 calculateZoomLevel():根据距离计算新的缩放级别。 clearMap()和clearMapAndSearch():清除地图上的标记和地图实例。
<template> <div> <div ref="mapContainer" style="width: 100%; height: 100vh;"></div> <!-- 包含轨迹时段和控制按钮的浮动层 --> <div class="control-panel"> 轨迹时段: <a-range-picker :value="dateData" @change="handleDateFilterChange" :show-time="true" :disabledDate="disabledDate"> </a-range-picker> <!-- 搜索按钮 --> <a-button @click="clearMapAndSearch">搜索</a-button> <a-button @click="startAnimation">播放</a-button> <a-button @click="pauseAnimation">暂停</a-button> <a-button @click="decreaseProgress" style="margin-left: 20px;">-</a-button> <a-button @click="increaseProgress">+</a-button> <a-row> <a-col :span="12"> 播放速度: <a-radio-group v-model="speed"> <a-radio :value="100">慢速</a-radio> <a-radio :value="500">正常</a-radio> <a-radio :value="1000">快速</a-radio> <a-radio :value="1500">最快速</a-radio> </a-radio-group> </a-col> </a-row> <a-slider v-model="progressPercent" :min="0" :max="100" /> </div> </div> </template> <script> import Vue from 'vue'; import { getAction, httpAction, postAction } from '../../api/manage'; import moment from 'dayjs' export default { props: { deviceNum: { type: String, // 参数的数据类型 required: true // 参数是否必需 }, }, watch: { progressPercent(newProgress) { // 在进度变化时更新标记的位置 this.updateMarkerPosition(); }, }, data() { return { dateData: [], startDate: null, // 开始日期时间 endDate: null, // 结束日期时间 map: null, pathData: [], marker: null, currentIndex: 0, speed: 100, //是否播放 isPlaying: false, progressPercent: 0, url: { trajectory: '/jeecg-customers/car/carCheXiaoGpsController/trajectory', }, totalPoints: 0, }; }, mounted() { const startDate = moment().subtract(5, 'hours').format('YYYY-MM-DD HH:mm:ss'); console.log(startDate, '开始时间'); const endDate = moment().format('YYYY-MM-DD HH:mm:ss'); console.log(endDate, '结束时间'); this.dateData = [startDate, endDate] this.startDate = startDate this.endDate = endDate this.searchData(); }, methods: { disabledDate(current) { // 禁用日期的函数 if (!this.startDate || !this.endDate) { // 如果没有选择日期范围,不禁用任何日期 return false; } const maxDate = moment(this.startDate).add(3, 'days'); // 允许选择日期范围的最大结束日期 return current && (current < this.startDate || current > maxDate); }, handleDateFilterChange(dates, dateString) { this.dateData = dates; this.startDate = dateString[0] this.endDate = dateString[1] console.log(this.startDate, this.endDate) }, searchData() { // 获取开始日期时间和结束日期时间 if (!this.startDate || !this.endDate) { // 显示错误消息或进行其他处理 this.$message.warning('请选择时间'); } this.generatePathData().then((res) => { if (res) { this.initMap(); } }); }, decreaseProgress() { if (this.progressPercent > 0) { this.progressPercent -= 1; this.updateMarkerPosition(); } }, increaseProgress() { if (this.progressPercent < 100) { this.progressPercent += 1; this.updateMarkerPosition(); } }, updateMarkerPosition() { const newIndex = Math.round((this.progressPercent / 100) * (this.totalPoints - 1)); if (newIndex !== this.currentIndex) { this.currentIndex = newIndex; const point = this.pathData[newIndex]; const position = new BMap.Point(point.lng, point.lat); this.marker.setPosition(position); this.map.panTo(position); } }, pauseAnimation() { this.isPlaying = false; }, startAnimation() { this.isPlaying = true; this.animateMarker(); }, generatePathData() { console.log(this.startDate) console.log(this.endDate) return new Promise((resolve) => { getAction(this.url.trajectory, { // deviceNum: "10213134933", deviceNum: this.deviceNum, baseStateLBS: "1", startTime: this.startDate, endTime: this.endDate, stopPoint: "0", stopPointTime: "10", }).then((res) => { resolve(res); this.pathData = res.result; this.totalPoints = this.pathData.length; // Set totalPoints }); }); }, initMap() { this.map = new BMap.Map(this.$refs.mapContainer); const firstPoint = this.pathData[0]; // 添加缩放控件 this.map.addControl(new BMap.NavigationControl()); this.map.centerAndZoom(new BMap.Point(firstPoint.lng, firstPoint.lat), 15); // 启用鼠标滚轮缩放 this.map.enableScrollWheelZoom(); // 遍历pathData添加图标覆盖物 for (let i = 0; i < this.pathData.length; i++) { const point = this.pathData[i]; const myIcon = new BMap.Icon(require("@/assets/stop.png"), new BMap.Size(20, 20), { // anchor: new BMap.Size(25, 25), // 图标的定位点相对于图标左上角的偏移 imageSize: new BMap.Size(20, 20) // 图标的大小 }); const marker = new BMap.Marker(new BMap.Point(point.lng, point.lat), { icon: myIcon, }); // 根据sp的值判断是否添加覆盖物 if (point.sp === "0.0") { this.map.addOverlay(marker); } } const myIcon = new BMap.Icon(require("@/assets/carrun.png"), new BMap.Size(50, 50), { anchor: new BMap.Size(25, 25), // 图标的定位点相对于图标左上角的偏移 imageSize: new BMap.Size(50, 50) // 图标的大小 }); // const myIcon = new BMap.Icon("https://webapi.amap.com/images/car.png", new BMap.Size(60, 30), { // // anchor: new BMap.Size(25, 25), // 图标的定位点相对于图标左上角的偏移 // imageSize: new BMap.Size(60, 30) // 图标的大小 // }); this.marker = new BMap.Marker(new BMap.Point(firstPoint.lng, firstPoint.lat), { icon: myIcon }); this.map.addOverlay(this.marker); const polyline = new BMap.Polyline( this.pathData.map((point) => new BMap.Point(point.lng, point.lat)), { enableEditing: false, enableClicking: true, strokeWeight: '8', strokeOpacity: 0.8, strokeColor: "#18a45b", } ); this.map.addOverlay(polyline); // 开始动画 this.animateMarker(); }, animateMarker() { if (this.currentIndex < this.totalPoints - 1 && this.isPlaying) { const currentPoint = this.pathData[this.currentIndex]; const nextPoint = this.pathData[this.currentIndex + 1]; const startPosition = new BMap.Point(currentPoint.lng, currentPoint.lat); const endPosition = new BMap.Point(nextPoint.lng, nextPoint.lat); const distance = this.map.getDistance(startPosition, endPosition); // 基于速度和两个点之间的距离来计算时长 const duration = (distance / this.speed) * 1000; const angle = this.calculateAngle(startPosition, endPosition); this.marker.setRotation(angle); // 调整地图的缩放级别以使标点可见 // const newZoomLevel = this.calculateZoomLevel(distance); // 自定义函数来计算缩放级别 // this.map.centerAndZoom(endPosition, newZoomLevel); this.progressPercent = Math.round((this.currentIndex / (this.totalPoints - 1)) * 100); if (this.isPlaying) { setTimeout(() => { this.marker.setPosition(endPosition); this.currentIndex++; requestAnimationFrame(this.animateMarker); }, duration); } } }, calculateZoomLevel(distance) { // 根据距离或其他条件来计算新的缩放级别 if (distance < 1000) { return 19; } else { return 14; } }, clearMap() { if (this.map) { this.map.clearOverlays(); // 清除地图上的所有标记点 this.map = null; // 销毁地图实例 } }, clearMapAndSearch() { // 清除地图上的标点和地图实例 this.clearMap(); // 执行搜索并加载标点的逻辑 this.searchData(); }, /**根据点位旋转角度 * @param {Object} point1 * @param {Object} point2 */ calculateAngle(point1, point2) { // 计算两点之间的方向(角度) const lat1 = point1.lat * (Math.PI / 180); const lng1 = point1.lng * (Math.PI / 180); const lat2 = point2.lat * (Math.PI / 180); const lng2 = point2.lng * (Math.PI / 180); const y = Math.sin(lng2 - lng1) * Math.cos(lat2); const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1); let angle = Math.atan2(y, x); angle = (angle * 180) / Math.PI; // 调整角度以匹配图标朝向 angle -= 90; return angle; } }, }; </script> <style> .control-panel { position: absolute; top: 10px; /* 调整 bottom 属性来控制浮动层距离底部的距离 */ left: 10px; /* 调整 left 属性来控制浮动层距离左侧的距离 */ background-color: white; padding: 10px; border: 1px solid #ccc; border-radius: 5px; } </style>