You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
5.0 KiB

2 months ago
/**
*
*/
import type { Map } from 'ol'
2 months ago
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource } from 'ol/source'
import { Feature } from 'ol'
import { Point } from 'ol/geom'
import { Style, Circle, Fill, Stroke } from 'ol/style'
import { fromLonLat } from 'ol/proj'
import type { MarkerData } from '../types/map.types'
import { getHighestPriorityStatus, getStatusColor } from '../utils/map.utils'
import { ANIMATION_CONFIG } from '../constants/map.constants'
export class AnimationService {
private rippleLayer: VectorLayer<VectorSource> | null = null
private animationTimer: number | null = null
private map: Map | null = null
2 months ago
private enableCluster: boolean = true
2 months ago
constructor(map: Map) {
this.map = map
}
2 months ago
/**
*
*/
createRippleLayer(
markers: MarkerData[],
2 months ago
2 months ago
enableCluster: boolean = true
): VectorLayer<VectorSource> {
2 months ago
if (this.rippleLayer && this.map) {
this.map?.removeLayer(this.rippleLayer)
}
2 months ago
this.enableCluster = enableCluster
const source = new VectorSource()
// 为每个标记添加波纹效果
markers.forEach((marker) => {
const feature = new Feature({
geometry: new Point(fromLonLat(marker.coordinates)),
markerData: marker
})
const status = getHighestPriorityStatus(marker)
const color = getStatusColor(status)
// 设置动画开始时间
feature.set('animationStart', Date.now())
feature.set('rippleColor', color)
source.addFeature(feature)
})
this.rippleLayer = new VectorLayer({
source: source,
2 months ago
zIndex: 1,
2 months ago
style: (feature) => {
// 检查当前缩放级别,如果缩放级别较低(聚合状态),不显示波纹
const currentZoom = this.map?.getView().getZoom() || 0
// 如果启用了聚合且zoom级别较低,不显示波纹
if (this.enableCluster && currentZoom < ANIMATION_CONFIG.clusterThreshold) {
return [] // 不显示波纹
}
const startTime = feature.get('animationStart')
const color = feature.get('rippleColor')
const elapsed = (Date.now() - startTime) / 1000 // 秒
// 创建多个波纹圈
const styles: Style[] = []
for (let i = 0; i < ANIMATION_CONFIG.rippleCount; i++) {
const phase = (elapsed + i * ANIMATION_CONFIG.phaseOffset) % ANIMATION_CONFIG.duration
const progress = phase / ANIMATION_CONFIG.duration // 0-1的进度
// 使用缓动函数使动画更平滑
const easeProgress = 1 - Math.pow(1 - progress, 3) // ease-out cubic
// 计算半径和透明度
const radius =
ANIMATION_CONFIG.minRadius +
easeProgress * (ANIMATION_CONFIG.maxRadius - ANIMATION_CONFIG.minRadius)
const opacity = Math.max(0, 1 - easeProgress) // 1到0的透明度
if (opacity > ANIMATION_CONFIG.minOpacity) {
// 计算颜色透明度
const alpha = Math.floor(opacity * 255)
.toString(16)
.padStart(2, '0')
const strokeColor = color + alpha
styles.push(
new Style({
image: new Circle({
radius: radius,
fill: new Fill({
color: 'transparent'
}),
stroke: new Stroke({
color: strokeColor,
width: Math.max(1, 3 - i * 0.4) // 动态调整线宽
})
})
})
)
}
}
return styles
}
})
2 months ago
if (this.rippleLayer && this.map) {
this.map?.addLayer(this.rippleLayer)
}
2 months ago
return this.rippleLayer
}
2 months ago
show() {
this.rippleLayer?.setVisible(true)
this.startAnimation()
}
hide() {
this.stopAnimation()
this.rippleLayer?.setVisible(false)
}
2 months ago
/**
*
*/
startAnimation(): void {
let lastUpdateTime = 0
const frameInterval = 1000 / ANIMATION_CONFIG.targetFPS // 帧间隔
const animateRipples = (currentTime: number) => {
if (this.rippleLayer && currentTime - lastUpdateTime >= frameInterval) {
this.rippleLayer.getSource()?.changed()
lastUpdateTime = currentTime
}
this.animationTimer = requestAnimationFrame(animateRipples)
}
animateRipples(0)
}
/**
*
*/
stopAnimation(): void {
if (this.animationTimer) {
cancelAnimationFrame(this.animationTimer)
this.animationTimer = null
}
}
/**
*
*/
updateRipples(): void {
if (this.rippleLayer) {
this.rippleLayer.getSource()?.changed()
}
}
/**
*
*/
getRippleLayer(): VectorLayer<VectorSource> | null {
return this.rippleLayer
}
/**
*
*/
destroy(): void {
2 months ago
if (this.rippleLayer && this.map) {
this.map?.removeLayer(this.rippleLayer)
}
2 months ago
this.stopAnimation()
this.rippleLayer = null
this.map = null
}
}