Leaflet轨迹动画实现
在GIS和位置服务应用中,轨迹回放是一个常见且重要的功能。本文将详细介绍如何使用Leaflet.js和Vue.js构建一个完整的车辆历史轨迹回放系统。我们将基于一个实际的项目代码,解析从地图初始化、轨迹数据获取到动画播放的完整实现过程。
点这里下载L.MoveMarker
1. 地图初始化与配置
// 地图初始化 - 只执行一次,避免重复创建
initializeMap() {
if (this.isMapInitialized && this.map) {
return
}
let latlngs = [this.data[0].lat * 1, this.data[0].lng * 1]
this.map = L.map(this.$refs['map'], {
renderer: L.canvas(), // 使用Canvas渲染器提升性能
preferCanvas: true,
center: latlngs,
zoom: this.zoom || 13,
zoomControl: true,
doubleClickZoom: true,
attributionControl: false,
maxZoom: 20,
maxNativeZoom: 20
})
// 添加默认底图
this.addTileLayer('moren')
this.isMapInitialized = true
}
关键点:
- 使用Canvas渲染器提升大量轨迹点位的渲染性能
- 通过
isMapInitialized标志防止重复初始化 - 支持多种底图类型(默认、影像、专题)
2. 轨迹数据处理流程
// 轨迹数据处理方法
processTrackData(rawData) {
// 1. 过滤重复点位
const arr = rawData.filter((item, index) => {
if (index == 0) return true
return (
item.direction != rawData[index - 1].direction ||
(item.lat != rawData[index - 1].lat && item.lng != rawData[index - 1].lng)
)
})
// 2. 转换数据格式
this.data = arr.map(item => {
return {
lat: item.lng * 1,
lng: item.lat * 1,
speed: Math.floor(item.meters * 20),
heading: 0,
time: dayjs(item.timeStampStr).format('YYYY-MM-DD HH:mm:ss')
}
})
}
3. 轨迹动画实现
3.1. 移动标记创建
createMoveMarker() {
this.dataMarker = L.moveMarker(
[
[this.data[0].lng, this.data[0].lat],
[this.data[1].lng, this.data[1].lat]
],
{
animatePolyline: true,
color: '#0057ff',
weight: 6,
hidePolylines: false,
duration: this.data[1].speed / this.currentSpeed,
removeFirstLines: false,
maxLengthLines: this.data[1].speed / this.currentSpeed
},
{
animate: true,
hideMarker: false,
duration: this.data[1].speed / this.currentSpeed,
followMarker: false,
rotateMarker: true,
rotateAngle: this.data[0].heading,
icon: L.divIcon({
className: 'position-relative rotate--marker',
html: '<div style="width:2px; height:2px">' +
'<img style="width: 40px;margin-top:-21px;margin-left:-21px" ' +
'src="' + this.carImgs[this.cartype] + '" /></div>'
})
},
{}
).addTo(this.map)
}
3.2. 动画播放控制
startTrackAnimation() {
let num = 2
const setLine = async () => {
clearInterval(this.timer1)
if (!this.isplay) return
this.currentIndex = num - 1
if (num < this.data.length) {
this.timer1 = setInterval(() => {
num++
this.lineTime = this.data[num].minTime
setLine()
}, this.data[num].speed / this.currentSpeed)
// 添加新的轨迹段
this.dataMarker.addMoreLine(
[this.data[num].lng, this.data[num].lat],
{
rotateAngle: this.data[num - 1].heading,
animatePolyline: false,
duration: this.data[num].speed / this.currentSpeed
}
)
} else {
this.clearAllTimers()
this.isplay = false
this.lineTime = this.maxTime
}
}
setTimeout(() => {
setLine()
}, this.data[1].speed / this.currentSpeed)
}
4. 时间轴控制实现
4.1. 时间轴滑块(带防抖)
// 创建防抖函数
created() {
this.sliderDebounce = debounce(this.handleSliderChange, 300)
}
// 滑块变化处理
onSliderChange(value) {
this.lineTime = value
this.sliderDebounce(value)
}
// 实际滑块变化逻辑
handleSliderChange(time) {
if (this.isChangingTime) return
this.isChangingTime = true
this.isFirter = false
// 查找当前时间对应的轨迹点
let i = 0
this.data.forEach((item, index) => {
if (item.minTime <= time && time < item.maxTime) {
i = index
}
})
this.lineTime = this.data[i].minTime
this.isplay = false
this.clearAllTimers()
this.currentIndex = i
// 重新定位标记
if (this.dataMarker) {
this.dataMarker.stop()
this.dataMarker.onRemove(this.dataMarker)
}
this.changeSpeed()
this.changeMoveMarker()
setTimeout(() => {
this.isFirter = true
this.isChangingTime = false
}, 100)
}
5. 方向计算
calculateAngle(points) {
var lastPrePoi = points[0]
var lastPoi = points[1]
if (lastPoi.lng == lastPrePoi.lng) {
if (lastPoi.lat == lastPrePoi.lat) {
return 0
} else {
return lastPoi.lat > lastPrePoi.lat ? 0 : 180
}
} else {
// 三角函数计算角度
let first_side_length = lastPoi.lng - lastPrePoi.lng
let second_side_length = lastPoi.lat - lastPrePoi.lat
let third_side_length = Math.sqrt(
Math.pow(first_side_length, 2) + Math.pow(second_side_length, 2)
)
let cosine_value = first_side_length / third_side_length
let radian_value = Math.acos(cosine_value)
let angle_value = (radian_value * 180) / Math.PI
if (angle_value < 90) {
return second_side_length > 0 ? -0 - angle_value : -0 + angle_value
} else {
return second_side_length > 0 ? -0 - angle_value : angle_value - 0
}
}
}
本文来自博客园,作者:战立标,转载请注明原文链接:https://www.cnblogs.com/zhanlibiao/p/19363423

浙公网安备 33010602011771号