巡航器
功能与参考文档
暂时未找到leaflet巡航器插件,supermap也并未对此进行封装。于是打算自己着手写一个。
- 轨迹
L.polying
绘制 - 箭头方向
leaflet-rotatedmarker
npm 下载一下 - 进度条
MovingMarker
引入js文件- 加速、减速
- 播放、暂停
- 进度条
由于本巡航器要使用进度条功能,因此必须识别当前巡航器所在点,因此采用点与点之间添加动哈的方式完成巡航。暂停并非就地暂停,而是暂停到下一个轨迹点上。 如果你不需要进度条功能,可以更为简单的完成巡航器,
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: '©s; 高德地图', //用来进行属性控制的字符串,描述了图层数据
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
:
- 避免数据残余,清除之前巡航器
- 创建一个从
latlngs[0] --> latlngs[1]
的movingMarker
,添加到地图上。这里我添加到了轨迹线的图层this.detailLayer
上,你也可以直接添加到this.map
上,或者自己新建一个图层。 - 添加一个监控时间:当当前动画结束后,调用
this.addPoint()
函数。并且this.startIndex = 1
,startIndex
是addPoint()
的一个重要变量,下一步马上讲到。
这一步只是添加巡航器,并不播放动画,因此看不到动画效果,只能看到地图上多了一个巡航器图标
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
记录当前点的索引。
- 判断当前动画是否在播放状态
isPlaying
,如果不在播放状态,还等什么,直接return就好 - 判断用户有无使用进度条
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);
},
- 进度条点击事件
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])
}
},
- 加减速、播放、暂停
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++;
}
}
},
最终效果展示