支持线形动画,曲线平滑,车头方向跟随
This commit is contained in:
parent
e3c50d0ec1
commit
5f56c28eb0
|
|
@ -1171,6 +1171,7 @@ import {
|
|||
getTerminalGeofenceStatus,
|
||||
getTerminalsInGeofence
|
||||
} from '@/api/jtt808/geofence'
|
||||
import { isPointInGeofence } from '@/utils/geofence'
|
||||
import { formatDateTimeForAPI, formatDateTime, getStartOfToday, getEndOfToday } from '@/utils/dateUtil'
|
||||
|
||||
export default {
|
||||
|
|
@ -1199,7 +1200,14 @@ export default {
|
|||
currentPoint: null,
|
||||
playedPath: [],
|
||||
animationMarker: null,
|
||||
playedPolyline: null
|
||||
playedPolyline: null,
|
||||
speedLabel: null,
|
||||
currentAngle: 0,
|
||||
isInGeofence: false, // 当前是否在围栏内
|
||||
geofenceAlertActive: false, // 围栏警报是否激活
|
||||
alertAnimationTimer: null, // 警报动画定时器
|
||||
rippleAnimationTimer: null, // 波纹动画定时器
|
||||
rippleProgress: 0 // 波纹动画进度 (0-1)
|
||||
},
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
|
|
@ -3191,6 +3199,54 @@ export default {
|
|||
this.$message.success('轨迹回放已准备就绪,点击播放开始回放')
|
||||
},
|
||||
|
||||
// 使用贝塞尔曲线平滑路径
|
||||
smoothPathWithBezier(points) {
|
||||
if (points.length < 3) return points
|
||||
|
||||
const smoothedPoints = []
|
||||
smoothedPoints.push(points[0]) // 保留起点
|
||||
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const p0 = points[Math.max(0, i - 1)]
|
||||
const p1 = points[i]
|
||||
const p2 = points[i + 1]
|
||||
const p3 = points[Math.min(points.length - 1, i + 2)]
|
||||
|
||||
// 使用 Catmull-Rom 样条曲线生成平滑点
|
||||
const segments = 10 // 每段生成10个插值点
|
||||
for (let t = 0; t <= segments; t++) {
|
||||
const u = t / segments
|
||||
const point = this.catmullRomSpline(p0, p1, p2, p3, u)
|
||||
smoothedPoints.push(point)
|
||||
}
|
||||
}
|
||||
|
||||
smoothedPoints.push(points[points.length - 1]) // 保留终点
|
||||
return smoothedPoints
|
||||
},
|
||||
|
||||
// Catmull-Rom 样条曲线插值
|
||||
catmullRomSpline(p0, p1, p2, p3, t) {
|
||||
const t2 = t * t
|
||||
const t3 = t2 * t
|
||||
|
||||
const lng = 0.5 * (
|
||||
(2 * p1.getLng()) +
|
||||
(-p0.getLng() + p2.getLng()) * t +
|
||||
(2 * p0.getLng() - 5 * p1.getLng() + 4 * p2.getLng() - p3.getLng()) * t2 +
|
||||
(-p0.getLng() + 3 * p1.getLng() - 3 * p2.getLng() + p3.getLng()) * t3
|
||||
)
|
||||
|
||||
const lat = 0.5 * (
|
||||
(2 * p1.getLat()) +
|
||||
(-p0.getLat() + p2.getLat()) * t +
|
||||
(2 * p0.getLat() - 5 * p1.getLat() + 4 * p2.getLat() - p3.getLat()) * t2 +
|
||||
(-p0.getLat() + 3 * p1.getLat() - 3 * p2.getLat() + p3.getLat()) * t3
|
||||
)
|
||||
|
||||
return new AMap.LngLat(lng, lat)
|
||||
},
|
||||
|
||||
// 绘制完整轨迹线
|
||||
drawFullTrackLine(trackData) {
|
||||
// 清除之前的轨迹线
|
||||
|
|
@ -3203,17 +3259,20 @@ export default {
|
|||
this.polyline = null
|
||||
}
|
||||
|
||||
const path = trackData.map(point =>
|
||||
const path = trackData.map(point =>
|
||||
new AMap.LngLat(parseFloat(point.longitude), parseFloat(point.latitude))
|
||||
)
|
||||
|
||||
// 使用贝塞尔曲线平滑路径
|
||||
const smoothedPath = this.smoothPathWithBezier(path)
|
||||
|
||||
// 创建完整轨迹线(浅色,作为背景)
|
||||
this.polyline = new AMap.Polyline({
|
||||
path: path,
|
||||
path: smoothedPath,
|
||||
strokeColor: '#E0E0E0',
|
||||
strokeWeight: 3,
|
||||
strokeOpacity: 0.6,
|
||||
strokeStyle: 'dashed'
|
||||
strokeStyle: 'solid'
|
||||
})
|
||||
|
||||
this.map.add(this.polyline)
|
||||
|
|
@ -3222,56 +3281,332 @@ export default {
|
|||
// 创建动画标记
|
||||
createAnimationMarker(firstPoint) {
|
||||
const position = new AMap.LngLat(
|
||||
parseFloat(firstPoint.longitude),
|
||||
parseFloat(firstPoint.longitude),
|
||||
parseFloat(firstPoint.latitude)
|
||||
)
|
||||
|
||||
// 初始化围栏状态
|
||||
this.trackPlayback.isInGeofence = this.checkGeofenceStatus(
|
||||
parseFloat(firstPoint.latitude),
|
||||
parseFloat(firstPoint.longitude)
|
||||
)
|
||||
|
||||
// 创建动画标记(移动的车辆图标)
|
||||
this.trackPlayback.animationMarker = new AMap.Marker({
|
||||
position: position,
|
||||
icon: this.createCarIcon(),
|
||||
title: '当前位置',
|
||||
anchor: 'center'
|
||||
anchor: 'center',
|
||||
offset: new AMap.Pixel(0, 0)
|
||||
})
|
||||
|
||||
this.map.add(this.trackPlayback.animationMarker)
|
||||
|
||||
// 创建速度标签
|
||||
this.createSpeedLabel(position, firstPoint.speed || 0)
|
||||
|
||||
// 初始化已播放路径
|
||||
this.trackPlayback.playedPath = [position]
|
||||
this.updatePlayedPolyline()
|
||||
},
|
||||
|
||||
// 创建车辆图标
|
||||
createCarIcon() {
|
||||
// 创建速度标签
|
||||
createSpeedLabel(position, speed) {
|
||||
// 如果已存在标签,先移除
|
||||
if (this.trackPlayback.speedLabel) {
|
||||
this.map.remove(this.trackPlayback.speedLabel)
|
||||
}
|
||||
|
||||
// 创建速度文本标记
|
||||
const speedText = new AMap.Text({
|
||||
text: `${Math.round(speed)} km/h`,
|
||||
position: position,
|
||||
anchor: 'center',
|
||||
offset: new AMap.Pixel(0, -30), // 在车辆图标上方显示
|
||||
style: {
|
||||
'background-color': 'rgba(0, 0, 0, 0.75)',
|
||||
'border': '1px solid #1890FF',
|
||||
'border-radius': '4px',
|
||||
'color': '#FFFFFF',
|
||||
'font-size': '12px',
|
||||
'padding': '2px 6px',
|
||||
'white-space': 'nowrap'
|
||||
}
|
||||
})
|
||||
|
||||
this.trackPlayback.speedLabel = speedText
|
||||
this.map.add(speedText)
|
||||
},
|
||||
|
||||
// 更新速度标签
|
||||
updateSpeedLabel(position, speed) {
|
||||
if (this.trackPlayback.speedLabel) {
|
||||
this.trackPlayback.speedLabel.setPosition(position)
|
||||
this.trackPlayback.speedLabel.setText(`${Math.round(speed)} km/h`)
|
||||
}
|
||||
},
|
||||
|
||||
// 计算两点之间的方向角度
|
||||
calculateAngle(startLng, startLat, endLng, endLat) {
|
||||
const deltaLng = endLng - startLng
|
||||
const deltaLat = endLat - startLat
|
||||
|
||||
// 使用 atan2 计算角度(弧度)
|
||||
const angleRad = Math.atan2(deltaLng, deltaLat)
|
||||
|
||||
// 转换为角度(0-360)
|
||||
let angleDeg = (angleRad * 180) / Math.PI
|
||||
|
||||
// 确保角度在 0-360 范围内
|
||||
if (angleDeg < 0) {
|
||||
angleDeg += 360
|
||||
}
|
||||
|
||||
return angleDeg
|
||||
},
|
||||
|
||||
// 检测车辆是否在任何围栏内
|
||||
checkGeofenceStatus(lat, lng) {
|
||||
if (!this.geofences || this.geofences.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否在任何启用的围栏内
|
||||
for (const geofence of this.geofences) {
|
||||
if (geofence.status === 1) { // 只检查启用的围栏
|
||||
if (isPointInGeofence(lat, lng, geofence)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
// 触发围栏警报
|
||||
triggerGeofenceAlert(isEntering) {
|
||||
console.log(`🚨 围栏警报: ${isEntering ? '进入' : '离开'}围栏`)
|
||||
|
||||
// 如果已经在警报状态,不重复触发
|
||||
if (this.trackPlayback.geofenceAlertActive) {
|
||||
console.log('⏭️ 警报已激活,跳过重复触发')
|
||||
return
|
||||
}
|
||||
|
||||
// 激活警报状态
|
||||
this.trackPlayback.geofenceAlertActive = true
|
||||
this.trackPlayback.rippleProgress = 0
|
||||
|
||||
// 清除之前的定时器
|
||||
if (this.trackPlayback.alertAnimationTimer) {
|
||||
clearTimeout(this.trackPlayback.alertAnimationTimer)
|
||||
}
|
||||
if (this.trackPlayback.rippleAnimationTimer) {
|
||||
clearInterval(this.trackPlayback.rippleAnimationTimer)
|
||||
}
|
||||
|
||||
// 启动波纹动画(60fps)
|
||||
this.trackPlayback.rippleAnimationTimer = setInterval(() => {
|
||||
if (!this.trackPlayback.geofenceAlertActive) {
|
||||
clearInterval(this.trackPlayback.rippleAnimationTimer)
|
||||
return
|
||||
}
|
||||
|
||||
// 更新波纹进度(0-1循环)
|
||||
this.trackPlayback.rippleProgress += 0.02 // 每帧增加2%
|
||||
if (this.trackPlayback.rippleProgress >= 1) {
|
||||
this.trackPlayback.rippleProgress = 0
|
||||
}
|
||||
|
||||
// 更新图标显示波纹效果
|
||||
if (this.trackPlayback.animationMarker) {
|
||||
this.trackPlayback.animationMarker.setIcon(
|
||||
this.createCarIcon(
|
||||
this.trackPlayback.currentAngle,
|
||||
true,
|
||||
this.trackPlayback.rippleProgress
|
||||
)
|
||||
)
|
||||
}
|
||||
}, 1000 / 60) // 60fps
|
||||
|
||||
// 3秒后关闭警报效果
|
||||
this.trackPlayback.alertAnimationTimer = setTimeout(() => {
|
||||
this.trackPlayback.geofenceAlertActive = false
|
||||
|
||||
// 清除波纹动画
|
||||
if (this.trackPlayback.rippleAnimationTimer) {
|
||||
clearInterval(this.trackPlayback.rippleAnimationTimer)
|
||||
this.trackPlayback.rippleAnimationTimer = null
|
||||
}
|
||||
|
||||
// 恢复正常图标
|
||||
if (this.trackPlayback.animationMarker) {
|
||||
this.trackPlayback.animationMarker.setIcon(
|
||||
this.createCarIcon(this.trackPlayback.currentAngle, false, 0)
|
||||
)
|
||||
}
|
||||
|
||||
console.log('✅ 警报结束')
|
||||
}, 3000)
|
||||
|
||||
// 显示提示消息
|
||||
this.$message({
|
||||
message: `⚠️ 车辆${isEntering ? '进入' : '离开'}电子围栏!`,
|
||||
type: 'warning',
|
||||
duration: 2000
|
||||
})
|
||||
},
|
||||
|
||||
// 创建车辆图标(支持旋转角度和警报光效)
|
||||
createCarIcon(angle = 0, showAlert = false, rippleProgress = 0) {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = 24
|
||||
canvas.height = 24
|
||||
canvas.width = 80 // 增大画布以容纳波纹
|
||||
canvas.height = 80
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
// 绘制车辆图标
|
||||
ctx.fillStyle = '#FF4444'
|
||||
ctx.beginPath()
|
||||
// 使用兼容的方式绘制圆角矩形
|
||||
this.drawRoundedRect(ctx, 4, 6, 16, 12, 2)
|
||||
ctx.fill()
|
||||
// 启用抗锯齿
|
||||
ctx.imageSmoothingEnabled = true
|
||||
ctx.imageSmoothingQuality = 'high'
|
||||
|
||||
// 绘制车窗
|
||||
ctx.fillStyle = '#FFFFFF'
|
||||
ctx.fillRect(6, 8, 12, 3)
|
||||
// 平移到画布中心并旋转
|
||||
ctx.save()
|
||||
ctx.translate(40, 40) // 调整中心点
|
||||
|
||||
// 绘制方向指示
|
||||
ctx.fillStyle = '#FF4444'
|
||||
// 如果显示警报,绘制红色波纹效果
|
||||
if (showAlert && rippleProgress > 0) {
|
||||
// 绘制3个波纹圆环,每个相位不同
|
||||
for (let i = 0; i < 3; i++) {
|
||||
// 计算每个波纹的相位偏移
|
||||
const phaseOffset = i * 0.33
|
||||
let phase = (rippleProgress + phaseOffset) % 1
|
||||
|
||||
// 波纹半径从10px扩散到35px
|
||||
const minRadius = 10
|
||||
const maxRadius = 35
|
||||
const radius = minRadius + (maxRadius - minRadius) * phase
|
||||
|
||||
// 透明度从0.8逐渐减小到0
|
||||
const opacity = (1 - phase) * 0.8
|
||||
|
||||
// 绘制波纹圆环
|
||||
ctx.strokeStyle = `rgba(255, 0, 0, ${opacity})`
|
||||
ctx.lineWidth = 3
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, radius, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
|
||||
// 绘制内部填充(更淡)
|
||||
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, radius)
|
||||
gradient.addColorStop(0, `rgba(255, 0, 0, ${opacity * 0.3})`)
|
||||
gradient.addColorStop(0.7, `rgba(255, 77, 79, ${opacity * 0.2})`)
|
||||
gradient.addColorStop(1, 'rgba(255, 0, 0, 0)')
|
||||
ctx.fillStyle = gradient
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, radius, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
// 绘制中心高亮
|
||||
const centerGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 12)
|
||||
centerGradient.addColorStop(0, 'rgba(255, 0, 0, 0.6)')
|
||||
centerGradient.addColorStop(0.5, 'rgba(255, 77, 79, 0.3)')
|
||||
centerGradient.addColorStop(1, 'rgba(255, 0, 0, 0)')
|
||||
ctx.fillStyle = centerGradient
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, 12, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
// 旋转画布(角度转弧度)
|
||||
if (angle !== 0) {
|
||||
ctx.rotate((angle * Math.PI) / 180)
|
||||
}
|
||||
|
||||
// 绘制阴影
|
||||
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'
|
||||
ctx.shadowBlur = 4
|
||||
ctx.shadowOffsetX = 1
|
||||
ctx.shadowOffsetY = 2
|
||||
|
||||
// 绘制车辆主体(蓝色)
|
||||
ctx.fillStyle = '#1890FF'
|
||||
ctx.strokeStyle = '#0050B3'
|
||||
ctx.lineWidth = 1.5
|
||||
|
||||
// 车身主体
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(12, 2)
|
||||
ctx.lineTo(8, 6)
|
||||
ctx.lineTo(16, 6)
|
||||
ctx.moveTo(-8, -12) // 车头顶部
|
||||
ctx.lineTo(-8, 8) // 左侧
|
||||
ctx.lineTo(-6, 12) // 左后轮上方
|
||||
ctx.lineTo(6, 12) // 右后轮上方
|
||||
ctx.lineTo(8, 8) // 右侧
|
||||
ctx.lineTo(8, -12) // 车头右侧
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
ctx.stroke()
|
||||
|
||||
// 清除阴影
|
||||
ctx.shadowColor = 'transparent'
|
||||
ctx.shadowBlur = 0
|
||||
ctx.shadowOffsetX = 0
|
||||
ctx.shadowOffsetY = 0
|
||||
|
||||
// 绘制车窗(浅蓝色)
|
||||
ctx.fillStyle = '#91D5FF'
|
||||
ctx.fillRect(-6, -10, 12, 6)
|
||||
|
||||
// 绘制车窗分隔线
|
||||
ctx.strokeStyle = '#0050B3'
|
||||
ctx.lineWidth = 1
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(-6, -7)
|
||||
ctx.lineTo(6, -7)
|
||||
ctx.stroke()
|
||||
|
||||
// 绘制车灯(黄色)
|
||||
ctx.fillStyle = '#FADB14'
|
||||
ctx.beginPath()
|
||||
ctx.arc(-5, -12, 1.5, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.beginPath()
|
||||
ctx.arc(5, -12, 1.5, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
// 绘制车轮(深灰色)
|
||||
ctx.fillStyle = '#434343'
|
||||
// 左前轮
|
||||
ctx.fillRect(-9, -6, 2, 4)
|
||||
// 右前轮
|
||||
ctx.fillRect(7, -6, 2, 4)
|
||||
// 左后轮
|
||||
ctx.fillRect(-9, 4, 2, 4)
|
||||
// 右后轮
|
||||
ctx.fillRect(7, 4, 2, 4)
|
||||
|
||||
// 绘制方向指示箭头(红色)
|
||||
ctx.fillStyle = '#FF4D4F'
|
||||
ctx.strokeStyle = '#FFFFFF'
|
||||
ctx.lineWidth = 1
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, -16) // 箭头顶点
|
||||
ctx.lineTo(-4, -12) // 左边
|
||||
ctx.lineTo(-1.5, -12) // 左内
|
||||
ctx.lineTo(-1.5, -10) // 左下
|
||||
ctx.lineTo(1.5, -10) // 右下
|
||||
ctx.lineTo(1.5, -12) // 右内
|
||||
ctx.lineTo(4, -12) // 右边
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
ctx.stroke()
|
||||
|
||||
ctx.restore()
|
||||
|
||||
return new AMap.Icon({
|
||||
size: new AMap.Size(24, 24),
|
||||
size: new AMap.Size(80, 80), // 调整图标尺寸以匹配画布
|
||||
image: canvas.toDataURL(),
|
||||
imageOffset: new AMap.Pixel(0, 0)
|
||||
imageOffset: new AMap.Pixel(0, 0),
|
||||
imageSize: new AMap.Size(80, 80)
|
||||
})
|
||||
},
|
||||
|
||||
|
|
@ -3331,14 +3666,9 @@ export default {
|
|||
if (!this.trackPlayback.hasData) return
|
||||
|
||||
this.trackPlayback.isPlaying = true
|
||||
|
||||
// 计算播放间隔(基础间隔500ms,根据速度调整)
|
||||
const baseInterval = 500
|
||||
const interval = baseInterval / this.trackPlayback.speed
|
||||
|
||||
this.trackPlayback.timer = setInterval(() => {
|
||||
this.playNextPoint()
|
||||
}, interval)
|
||||
// 开始播放下一个点
|
||||
this.playNextPoint()
|
||||
|
||||
this.$message.success('轨迹回放开始')
|
||||
},
|
||||
|
|
@ -3346,10 +3676,7 @@ export default {
|
|||
// 暂停播放
|
||||
pausePlayback() {
|
||||
this.trackPlayback.isPlaying = false
|
||||
if (this.trackPlayback.timer) {
|
||||
clearInterval(this.trackPlayback.timer)
|
||||
this.trackPlayback.timer = null
|
||||
}
|
||||
// 动画会在下一帧检查 isPlaying 状态并停止
|
||||
},
|
||||
|
||||
// 停止播放并重置
|
||||
|
|
@ -3366,6 +3693,8 @@ export default {
|
|||
|
||||
// 播放下一个点
|
||||
playNextPoint() {
|
||||
if (!this.trackPlayback.isPlaying) return
|
||||
|
||||
if (this.trackPlayback.currentIndex >= this.trackPlayback.totalPoints - 1) {
|
||||
// 播放完成
|
||||
this.pausePlayback()
|
||||
|
|
@ -3380,27 +3709,130 @@ export default {
|
|||
this.updateAnimationPosition()
|
||||
},
|
||||
|
||||
// 更新动画位置
|
||||
// 更新动画位置(使用平滑动画)
|
||||
updateAnimationPosition() {
|
||||
const currentPoint = this.trackPlayback.currentPoint
|
||||
const position = new AMap.LngLat(
|
||||
if (!this.trackPlayback.animationMarker) return
|
||||
|
||||
const currentIndex = this.trackPlayback.currentIndex
|
||||
|
||||
// 获取当前点和上一个点
|
||||
const prevIndex = currentIndex - 1
|
||||
if (prevIndex < 0) {
|
||||
// 没有上一个点,直接返回
|
||||
return
|
||||
}
|
||||
|
||||
const prevPoint = this.trackPlayback.trackData[prevIndex]
|
||||
const currentPoint = this.trackPlayback.trackData[currentIndex]
|
||||
|
||||
const startPosition = new AMap.LngLat(
|
||||
parseFloat(prevPoint.longitude),
|
||||
parseFloat(prevPoint.latitude)
|
||||
)
|
||||
const endPosition = new AMap.LngLat(
|
||||
parseFloat(currentPoint.longitude),
|
||||
parseFloat(currentPoint.latitude)
|
||||
)
|
||||
|
||||
// 移动标记到新位置
|
||||
if (this.trackPlayback.animationMarker) {
|
||||
this.trackPlayback.animationMarker.setPosition(position)
|
||||
// 计算移动方向角度
|
||||
const angle = this.calculateAngle(
|
||||
startPosition.getLng(),
|
||||
startPosition.getLat(),
|
||||
endPosition.getLng(),
|
||||
endPosition.getLat()
|
||||
)
|
||||
|
||||
// 添加到已播放路径
|
||||
this.trackPlayback.playedPath.push(position)
|
||||
this.updatePlayedPolyline()
|
||||
// 更新车辆图标方向
|
||||
this.trackPlayback.currentAngle = angle
|
||||
|
||||
// 地图跟随移动(可选)
|
||||
if (this.trackPlayback.isPlaying) {
|
||||
this.map.setCenter(position)
|
||||
// 获取当前速度
|
||||
const currentSpeed = currentPoint.speed || 0
|
||||
|
||||
// 计算动画持续时间(基础时间1000ms,根据速度调整)
|
||||
const baseDuration = 1000
|
||||
const duration = baseDuration / this.trackPlayback.speed
|
||||
const steps = 30 // 动画帧数
|
||||
const stepDuration = duration / steps
|
||||
let currentStep = 0
|
||||
|
||||
// 使用 setTimeout 实现平滑动画
|
||||
const animate = () => {
|
||||
if (!this.trackPlayback.isPlaying || currentStep >= steps) {
|
||||
// 动画完成,设置到最终位置
|
||||
this.trackPlayback.animationMarker.setPosition(endPosition)
|
||||
|
||||
// 添加到已播放路径
|
||||
if (this.trackPlayback.playedPath.length === 0 ||
|
||||
!this.trackPlayback.playedPath[this.trackPlayback.playedPath.length - 1].equals(endPosition)) {
|
||||
this.trackPlayback.playedPath.push(endPosition)
|
||||
this.updatePlayedPolyline()
|
||||
}
|
||||
|
||||
// 地图跟随移动
|
||||
if (this.trackPlayback.isPlaying) {
|
||||
this.map.setCenter(endPosition)
|
||||
}
|
||||
|
||||
// 播放下一个点
|
||||
if (this.trackPlayback.isPlaying) {
|
||||
setTimeout(() => {
|
||||
this.playNextPoint()
|
||||
}, 50)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
currentStep++
|
||||
const progress = currentStep / steps
|
||||
|
||||
// 线性插值计算中间位置
|
||||
const lng = startPosition.getLng() + (endPosition.getLng() - startPosition.getLng()) * progress
|
||||
const lat = startPosition.getLat() + (endPosition.getLat() - startPosition.getLat()) * progress
|
||||
const intermediatePosition = new AMap.LngLat(lng, lat)
|
||||
|
||||
// 更新标记位置
|
||||
this.trackPlayback.animationMarker.setPosition(intermediatePosition)
|
||||
|
||||
// 更新速度标签位置
|
||||
this.updateSpeedLabel(intermediatePosition, currentSpeed)
|
||||
|
||||
// 🔍 在每一帧检测围栏进出状态(使用当前实际位置)
|
||||
const wasInGeofence = this.trackPlayback.isInGeofence
|
||||
const isNowInGeofence = this.checkGeofenceStatus(lat, lng)
|
||||
|
||||
// 检测是否发生围栏进出事件
|
||||
if (wasInGeofence !== isNowInGeofence) {
|
||||
this.trackPlayback.isInGeofence = isNowInGeofence
|
||||
this.triggerGeofenceAlert(isNowInGeofence)
|
||||
console.log(`🎯 围栏状态变化 - 位置: [${lng.toFixed(6)}, ${lat.toFixed(6)}], ${isNowInGeofence ? '进入' : '离开'}围栏`)
|
||||
}
|
||||
|
||||
// 更新车辆图标(考虑警报状态)
|
||||
const showAlert = this.trackPlayback.geofenceAlertActive
|
||||
if (!showAlert) {
|
||||
// 非警报状态时更新图标方向
|
||||
this.trackPlayback.animationMarker.setIcon(
|
||||
this.createCarIcon(angle, false, 0)
|
||||
)
|
||||
}
|
||||
|
||||
// 更新已播放路径(每隔几帧更新一次以提高性能)
|
||||
if (currentStep % 5 === 0) {
|
||||
this.trackPlayback.playedPath.push(intermediatePosition)
|
||||
this.updatePlayedPolyline()
|
||||
}
|
||||
|
||||
// 地图跟随移动
|
||||
if (this.trackPlayback.isPlaying && currentStep % 10 === 0) {
|
||||
this.map.setCenter(intermediatePosition)
|
||||
}
|
||||
|
||||
// 继续动画
|
||||
setTimeout(animate, stepDuration)
|
||||
}
|
||||
|
||||
// 开始动画
|
||||
animate()
|
||||
},
|
||||
|
||||
// 重置到第一个点
|
||||
|
|
@ -3424,15 +3856,19 @@ export default {
|
|||
|
||||
// 速度改变
|
||||
onSpeedChange() {
|
||||
if (this.trackPlayback.isPlaying) {
|
||||
// 重新开始播放以应用新速度
|
||||
this.pausePlayback()
|
||||
this.startPlayback()
|
||||
}
|
||||
// 速度改变会在下一次移动时自动应用
|
||||
// 新的速度会在下一个点的动画中生效
|
||||
},
|
||||
|
||||
// 进度改变
|
||||
onProgressChange(value) {
|
||||
const wasPlaying = this.trackPlayback.isPlaying
|
||||
|
||||
// 暂停播放
|
||||
if (wasPlaying) {
|
||||
this.pausePlayback()
|
||||
}
|
||||
|
||||
this.trackPlayback.currentIndex = value
|
||||
this.trackPlayback.currentPoint = this.trackPlayback.trackData[value]
|
||||
|
||||
|
|
@ -3444,8 +3880,31 @@ export default {
|
|||
parseFloat(point.latitude)
|
||||
))
|
||||
|
||||
this.updateAnimationPosition()
|
||||
// 直接跳转到新位置(不使用动画)
|
||||
const currentPoint = this.trackPlayback.currentPoint
|
||||
const position = new AMap.LngLat(
|
||||
parseFloat(currentPoint.longitude),
|
||||
parseFloat(currentPoint.latitude)
|
||||
)
|
||||
|
||||
if (this.trackPlayback.animationMarker) {
|
||||
this.trackPlayback.animationMarker.setPosition(position)
|
||||
this.map.setCenter(position)
|
||||
}
|
||||
|
||||
// 更新速度标签
|
||||
if (this.trackPlayback.speedLabel) {
|
||||
this.updateSpeedLabel(position, currentPoint.speed || 0)
|
||||
}
|
||||
|
||||
this.updatePlayedPolyline()
|
||||
|
||||
// 如果之前在播放,继续播放
|
||||
if (wasPlaying) {
|
||||
setTimeout(() => {
|
||||
this.startPlayback()
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
|
||||
// 显示完整轨迹
|
||||
|
|
@ -3471,12 +3930,26 @@ export default {
|
|||
this.trackPlayback.animationMarker = null
|
||||
}
|
||||
|
||||
// 清理速度标签
|
||||
if (this.trackPlayback.speedLabel) {
|
||||
this.map.remove(this.trackPlayback.speedLabel)
|
||||
this.trackPlayback.speedLabel = null
|
||||
}
|
||||
|
||||
// 清理已播放路径
|
||||
if (this.trackPlayback.playedPolyline) {
|
||||
this.map.remove(this.trackPlayback.playedPolyline)
|
||||
this.trackPlayback.playedPolyline = null
|
||||
}
|
||||
|
||||
// 清除警报定时器
|
||||
if (this.trackPlayback.alertAnimationTimer) {
|
||||
clearTimeout(this.trackPlayback.alertAnimationTimer)
|
||||
}
|
||||
if (this.trackPlayback.rippleAnimationTimer) {
|
||||
clearInterval(this.trackPlayback.rippleAnimationTimer)
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
this.trackPlayback = {
|
||||
isVisible: false,
|
||||
|
|
@ -3491,7 +3964,14 @@ export default {
|
|||
currentPoint: null,
|
||||
playedPath: [],
|
||||
animationMarker: null,
|
||||
playedPolyline: null
|
||||
playedPolyline: null,
|
||||
speedLabel: null,
|
||||
currentAngle: 0,
|
||||
isInGeofence: false,
|
||||
geofenceAlertActive: false,
|
||||
alertAnimationTimer: null,
|
||||
rippleAnimationTimer: null,
|
||||
rippleProgress: 0
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue