Vue使用百度地图以及实现轨迹回放 附完整代码

2023-12-13 04:44:31

百度地图开放平台 https://lbs.baidu.com/index.php?title=%E9%A6%96%E9%A1%B5
javaScript API https://lbs.baidu.com/index.php?title=jspopularGL
百度地图实例 https://lbsyun.baidu.com/index.php?title=open/jsdemo
Vue Baidu Map文档 https://dafrok.github.io/vue-baidu-map/#/zh/index

效果图:

image.png

支持放大缩小 调动播放进度 搜索轨迹时段 调整播放速度等。

image.png

步骤1:准备工作

1.1 获取百度地图API密钥

首先,你需要在百度地图开放平台注册一个开发者账号,并创建一个应用,以获取API密钥。API密钥是用于标识你的应用身份的重要凭证。
1701942229175.png
1701942117490.png

步骤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 为选项参数,具体可参考官方文档
  • 示例中的 point1point2 等是 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>

文章来源:https://blog.csdn.net/weixin_45213266/article/details/134861697
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。