leaflet 巡航器 播放历史轨迹

巡航器

功能与参考文档

暂时未找到leaflet巡航器插件,supermap也并未对此进行封装。于是打算自己着手写一个。

  1. 轨迹 L.polying 绘制
  2. 箭头方向 leaflet-rotatedmarker npm 下载一下
  3. 进度条 MovingMarker 引入js文件
    1. 加速、减速
    2. 播放、暂停
    3. 进度条

由于本巡航器要使用进度条功能,因此必须识别当前巡航器所在点,因此采用点与点之间添加动哈的方式完成巡航。暂停并非就地暂停,而是暂停到下一个轨迹点上。 如果你不需要进度条功能,可以更为简单的完成巡航器,movingMarker 支持多轨迹点动画,自带播放、停止功能。

参考: Leaflet MovingMarker 轨迹回放功能 | Ryan's Blog (ryancui.com)

环境准备

环境准备:

css 复制代码
npm install leaflet --save
npm install leaflet-rotatedmarker --save

引入 "MovingMarker.js"

我再这里使用了sass,elementui,大家可以按照自己实际需求引入。

json 复制代码
"dependencies": {
    "@supermap/iclient-leaflet": "^11.1.1",
    "axios": "^1.6.2",
    "core-js": "^3.8.3",
    "element-ui": "^2.15.14",
    "leaflet": "^1.9.4",
    "leaflet-rotatedmarker": "^0.2.0",
    "leaflet.markercluster": "^1.5.3",
    "vue": "^2.6.14",
    "vue-router": "^3.6.5"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@supermap/babel-plugin-import": "^0.0.1",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "node-sass": "^9.0.0",
    "sass-loader": "^13.3.2",
    "style-loader": "^3.3.3",
    "vue-template-compiler": "^2.6.14"
  },

详解

1. 引入

js 复制代码
import { Message } from "element-ui";
import L from 'leaflet';
import MovingMarker from "./MovingMarker.js"
import 'leaflet-rotatedmarker'
import myData from "./myData" // 自己写的伪数据

2. 数据准备

js 复制代码
  data () {
    return {
      isShow: false,
      boxLoading: false, //盒子loading
      loading: false,
      // step 播放器
      renderBottom: {
        //表格筛选参数
        startTime: "",
        endTime: "",
        removeTime: "",
      },
      startIndex: 0,
      isPlaying: false,
      isPause: false,
      isPlayed: false,
      currSpeed: 1, //速度
      FILL_WIDTH: 235, // 进度条宽度
      fillWidth: 0, // 进度条宽度
      percentage: 0, // 进度条进度
      hasChangeIndex: false,
      // step map
      map: null,
      detailLayer: null,
      polyline: null,
      movingArrow: null,
      popup: null,
      mapData: [],
      latlngs: [],
      iconUrl: [
        //起终点icon
        require("./imgs/start.png"), // 起点
        require("./imgs/end.png"),  // 终点
        require("./imgs/leader.png") // 巡航器
      ],
      leader: null,
    };
  },
  computed: {
    per () {
      const per = this.fomatFloat(this.FILL_WIDTH / this.mapData.length, 2)
      return per
    } // 进度条进度
  },

3. 地图初始化

mounted的时候,调用此函数。这里采用高德地图

html 复制代码
    <div class="map-container"
         id="map-container">
    </div>
js 复制代码
  mounted () {
    this.initMap()
  },
js 复制代码
    initMap () {
      // 加载 open street map 图层服务
      this.amapLayer = L.tileLayer("http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}", {
        attribution: '&copys; 高德地图', //用来进行属性控制的字符串,描述了图层数据
        maxZoom: 18, //map 对象中的 maxZoom 属性是必须的,否则在创建 **makerClusterGroup ** 会报错
        minZoom: 3,
        subdomains: "1234", // 子域名,替换{s}
      })
      this.map = L.map('map-container', {
        center: [34.1529, 108.5632], // 地图中心点
        zoomControl: false, //是否显示缩放比空间
        zoom: 10, //初始缩放比
      });
      this.amapLayer.addTo(this.map)
    },

引入成功后,页面上出现高德地图;初始定位点为西安。

4. 绘制轨迹线

需要提前准备一个数组 latlngs : [[lat,lng],[lat,lng],...] 所有轨迹点经纬度数组

js 复制代码
    // 绘制轨迹线
    createPolyline () {
      // 清除之前的轨迹
      if (this.detailLayer) {
        this.map.removeLayer(this.detailLayer)
        this.detailLayer.clearLayers()
        this.polyline = null
      }
      // 起点、终端marker
      const start = L.marker(this.latlngs[0], {
        icon: L.icon({
          iconSize: [30, 35],
          iconUrl: this.iconUrl[0],
          iconAnchor: [15, 35]
        })
      })
      const end = L.marker(this.latlngs[this.latlngs.length - 1], {
        icon: L.icon({
          iconSize: [30, 35],
          iconUrl: this.iconUrl[1],
          iconAnchor: [15, 35]
        })
      })
      this.detailLayer = L.layerGroup([start, end]).addTo(this.map)
      if (this.latlngs.length > 1) {
        //  轨迹点大于 1 ,画轨迹
        this.polyline = L.polyline(this.latlngs, { color: 'blue' })
        this.detailLayer.addLayer(this.polyline)
        // zoom the map to the polyline
        this.map.fitBounds(this.polyline.getBounds());
        // this.createLeader() // 添加巡航器, 写完createLeader函数后记得放开
      }
      this.loading = false;
      this.boxLoading = false;
    },

调用此函数效果如图所示。 暂未添加巡航器,写完createLeader函数后记得调用 this.createLeader()

4. 添加巡航器

先写一个进度条,html/css 代码如下。利用 fillWidth 控制进度条进度

html 复制代码
  <div id="wrapper"
	   @click="handleSlider">
	<div id="fill"
		 :style="{ width: fillWidth }"></div>
	<div class="btn"
		 :style="{ left: fillWidth }"></div>
	<div id="tool"></div>
  </div>
css 复制代码
// 巡航器
#wrapper {
  background-color: #fff;
  position: relative;
  border-radius: 15px;
  width: 230px; /* 自定义*/
}
#fill {
  display: inline-block;
  position: absolute;
  left: 0;
  height: 10px;
  border-radius: 5px;
  background: linear-gradient(45deg, pink, transparent);
}
.video-btn {
  display: inline-block;
  position: absolute;
  left: 0;
  width: 10px;
  height: 10px;
  background: hotpink;
  border-radius: 50%;
}

大概能得到上图效果,当然你也可以根据自己的喜欢调节样式。

改变 fillWidth 的值,得到以下效果

下面来添加巡航器 createLeader

  1. 避免数据残余,清除之前巡航器
  2. 创建一个从 latlngs[0] --> latlngs[1]movingMarker ,添加到地图上。这里我添加到了轨迹线的图层 this.detailLayer 上,你也可以直接添加到 this.map 上,或者自己新建一个图层。
  3. 添加一个监控时间:当当前动画结束后,调用 this.addPoint() 函数。并且 this.startIndex = 1 , startIndexaddPoint() 的一个重要变量,下一步马上讲到。

这一步只是添加巡航器,并不播放动画,因此看不到动画效果,只能看到地图上多了一个巡航器图标

js 复制代码
    createLeader () {
      // 清除之前巡航器
      if (this.movingArrow) {
        this.detailLayer.removeLayer(this.movingArrow)
        this.movingArrow = null
        console.log('removeLayer(this.movingArrow))')
      }
      // 巡航器初始化
      this.movingArrow = L.Marker.movingMarker(this.latlngs.slice(0, 2), 2000).addTo(this.detailLayer);
      this.movingArrow.setIcon(L.icon({
        iconUrl: this.iconUrl[3], //  巡航器icon
        iconSize: [30, 40],
        iconAnchor: [15, 40]
      }))
      // 巡航器角度改变
      this.movingArrow.setRotationAngle(this.mapData[this.startIndex].dir)
      // 视野锁定巡航器
      this.map.setView(this.movingArrow.getLatLng())
      this.startIndex = 1
      //  下一步,从latlngs[1]-->latlngs[2]
      this.movingArrow.on('end', () => {
        this.addPoint()
      })
    },

然后再来写 addPoint ,看看当前动画结束后要干什么。当然时开启另一段动画啦 ~ addPoint , 添加 latlngs 轨迹数组点中从当前点到 startIndex + 1 处点的位移动画。startIndex 记录当前点的索引。

  1. 判断当前动画是否在播放状态 isPlaying,如果不在播放状态,还等什么,直接return就好
  2. 判断用户有无使用进度条 hasChangeIndex 。如果使用了,需要改变 startIndex 这个属性,改变到进度条对应的值 (在 moveTopoint 函数中改变),这里需要改变当前点的位置到用户期待的位置。****
js 复制代码
    addPoint () {
      if (!this.isPlaying) {
        return   // 完成已绑定的动画后直接退出,后续无法监听到end时间,动画结束
      }
      if (this.hasChangeIndex) {
        //  使用进度条,让巡航器快进/ 快退到某点,视野
        this.hasChangeIndex = false
        this.movingArrow.setLatLng(this.latlngs[this.startIndex])
        this.movingArrow.setRotationAngle(this.mapData[this.startIndex].dir)
        this.map.setView(this.latlngs[this.startIndex])
      }
      // 进度条进度
      this.fillWidth = parseFloat(this.per * this.startIndex) + "px";
      setTimeout(() => {
        if (this.startIndex < this.mapData.length - 1) {
          this.renderBottom.removeTime = this.mapData[this.startIndex].dataTime
          let duration // 点与点之间移动的时间
          if (this.latlngs[this.startIndex][0] == this.latlngs[this.startIndex + 1][0] && this.latlngs[this.startIndex][1] == this.latlngs[this.startIndex + 1][1]) {
            duration = 1
          } else {
            const temp = (new Date(this.mapData[this.startIndex + 1].dataTime).getTime() - new Date(this.mapData[this.startIndex].dataTime).getTime()) / 10 / this.currSpeed
            duration = temp > 2000 ? 2000 : temp
          }
          this.map.setView(this.movingArrow.getLatLng())
          this.movingArrow.moveTo(this.latlngs[++this.startIndex], duration)
          this.movingArrow.setRotationAngle(this.mapData[this.startIndex].dir)
          if (this.startIndex == this.latlngs.length - 1) {
            Message.warning("历史轨迹播放完毕!");
            this.isPlaying = false
          }
        }
      }, 0);
    },
    fomatFloat (src, pos) {
      return Math.round(src * Math.pow(10, pos)) / Math.pow(10, pos);
    },
  1. 进度条点击事件
JS 复制代码
    // 进度条点击
    handleSlider (e) {
      if (this.movingArrow) {
        let offsetX = e.offsetX;
        let width = "";
        if (offsetX > 230) {
          width = 230;
        } else if (offsetX < 20) {
          width = 0;
        } else {
          width = offsetX - 10;
        }
        let num = this.mapData.length;
        let per = width / 230;
        let index = Math.floor(per * num);
        this.moveToPoint(index); // 动画位置改变
        this.startIndex = index;
        this.fillWidth = width + "px"; // 进度条长度改变
      } else {
        Message.warning("请先查询车辆历史轨迹!");
      }
    },
    // 移动到某点
    moveToPoint (index) {
      this.startIndex = index // startIndex 控制动画开始于latlngs数组的哪一个index
      if (this.isPlaying) {
        this.hasChangeIndex = true
      } else {
        this.movingArrow.setLatLng(this.latlngs[this.startIndex])
	    this.movingArrow.setRotationAngle(this.mapData[this.startIndex].dir)
        this.map.setView(this.latlngs[this.startIndex])
      }
    },
  1. 加减速、播放、暂停
js 复制代码
    // 开始
    startAnimation () {
      if (this.movingArrow && !this.isPlaying) {
        this.isPlaying = true
        this.map.setZoom(18)
        this.addPoint()
      }
    },
    // 暂停
    pauseAnimation () {
      if (this.movingArrow && this.isPlaying) {
        this.isPlaying = false
      }
    },
    // 加减速按钮
    handleLowSpeed (type) {
      if (!this.movingArrow) return
      if (type == 1) {
        if (this.currSpeed == 1) {
          this.currSpeed = 0.5;
        } else if (this.currSpeed > 1) {
          this.currSpeed--;
        }
      } else {
        if (this.currSpeed == 0.5) {
          this.currSpeed = 1;
        } else if (this.currSpeed < 4) {
          this.currSpeed++;
        }
      }
    },

最终效果展示

相关推荐
GIS工具-gistools20215 小时前
如何在 QGIS 中打开 Esri 文件地理数据库(GDB)
gis·qgis
放逐者-保持本心,方可放逐1 天前
一文了解 gis 相关服务=》及前端地图服务相关总结
前端·wms·gis·wmts·wfs·wcs·ogc
supermapsupport2 天前
iClent3D for Cesium 实现无人机巡检飞行效果
gis·cesium·supermap·webgis
枝上棉蛮4 天前
免费GIS工具箱:轻松将glb文件转换成3DTiles文件
gis·数字孪生·数据格式转换·3dtiles·地理信息系统·glb·gis软件
supermapsupport7 天前
iClient3D for Cesium 实现限高分析
3d·vue·gis·cesium·supermap·webgis
松果猿15 天前
独家攻略!GISer必备:轻松获取GIS数据的实用技巧,赶紧收藏!(二)
gis
moonless022216 天前
【GISer精英计划_00】从二进制到协议、到计算机通信、到服务器
网络协议·gis·计算机组成原理
兔子小姐_19 天前
如何下载Blender插件BlanderGIS
arcgis·gis·blender
安迁岚20 天前
【遥感综合实习】专题一 多时相多波段遥感影像的机器学习地物分类研究
人工智能·机器学习·arcgis·分类·gis
疯狂学习GIS21 天前
全球气象数据ERA5的下载方法
python·gis·rs·学术工作效率·gis数据·遥感数据·气象数据