/** * 动画服务类 */ import type { Map } from 'ol' 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 { ANIMATION_CONFIG } from '../constants/map.constants' export class AnimationService { private rippleLayer: VectorLayer | null = null private animationTimer: number | null = null private map: Map | null = null private enableCluster: boolean = true constructor(map: Map) { this.map = map } updateData(markers: MarkerData[]) { const source = this.rippleLayer?.getSource() if (source) { source?.clear() // 为每个标记添加波纹效果 markers.forEach((marker) => { const feature = new Feature({ geometry: new Point(fromLonLat(marker.coordinates)), markerData: marker }) // 设置动画开始时间 feature.set('animationStart', Date.now()) feature.set('rippleColor', marker.statusColor ) source?.addFeature(feature) // this.rippleLayer?.setSource(source) }) } } /** * 创建波纹图层 * enableCluster 是否启用聚合 */ createRippleLayer( markers: MarkerData[], enableCluster: boolean = true ): VectorLayer { if (this.rippleLayer && this.map) { this.updateData(markers) return this.rippleLayer } this.enableCluster = enableCluster const source = new VectorSource() this.rippleLayer = new VectorLayer({ source: source, zIndex: 1, 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 } }) this.updateData(markers) if (this.map) { this.map?.addLayer(this.rippleLayer) } return this.rippleLayer } show() { this.rippleLayer?.setVisible(true) this.startAnimation() } hide() { this.stopAnimation() this.rippleLayer?.setVisible(false) } /** * 启动波纹动画 */ 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 | null { return this.rippleLayer } /** * 销毁动画服务 */ destroy(): void { if (this.rippleLayer && this.map) { this.map?.removeLayer(this.rippleLayer) } this.stopAnimation() this.rippleLayer = null this.map = null } }