Browse Source

优化波纹图层:任意缩放层级只要是单个marker就加波纹

master
xh 7 days ago
parent
commit
f114c2451d
  1. 2
      web/.env
  2. 28
      web/src/views/HandDevice/Home/components/composables/useMapServices.ts
  3. 39
      web/src/views/HandDevice/Home/components/constants/map.constants.ts
  4. 91
      web/src/views/HandDevice/Home/components/services/animation.service.ts
  5. 32
      web/src/views/HandDevice/Home/components/services/marker.service.ts

2
web/.env

@ -15,6 +15,8 @@ VITE_APP_CAPTCHA_ENABLE=false
# 地图瓦片URL # 地图瓦片URL
VITE_APP_MAP_TILE_URL = 'https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}' VITE_APP_MAP_TILE_URL = 'https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'
# 天地图瓦片URL:确少token、坐标偏移、叠加标记图层
# VITE_APP_MAP_TILE_URL = 'https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles&tk='
#VITE_APP_MAP_TILE_URL = 'http://qtbj.icpcdev.site/roadmap/{z}/{x}/{y}.png' #VITE_APP_MAP_TILE_URL = 'http://qtbj.icpcdev.site/roadmap/{z}/{x}/{y}.png'
# 默认账户密码 # 默认账户密码

28
web/src/views/HandDevice/Home/components/composables/useMapServices.ts

@ -3,11 +3,11 @@
*/ */
// import type { Map } from 'ol' // import type { Map } from 'ol'
import { ref, onUnmounted, reactive } from 'vue'
import { onUnmounted } from 'vue'
import type { MapProps } from '../types/map.types' import type { MapProps } from '../types/map.types'
import { MapService } from '../services/map.service' import { MapService } from '../services/map.service'
import { MarkerService } from '../services/marker.service' import { MarkerService } from '../services/marker.service'
import { AnimationService } from '../services/animation.service'
// import { AnimationService } from '../services/animation.service'
import { PopupService } from '../services/popup.service' import { PopupService } from '../services/popup.service'
import { TrajectoryService } from '../services/trajectory.service' import { TrajectoryService } from '../services/trajectory.service'
import { FenceService } from '../services/fence.service' import { FenceService } from '../services/fence.service'
@ -16,7 +16,7 @@ import { FenceDrawService } from '../services/fence-draw.service'
interface ServiceInstances { interface ServiceInstances {
mapService: MapService | null mapService: MapService | null
markerService: MarkerService | null markerService: MarkerService | null
animationService: AnimationService | null
// animationService: AnimationService | null
popupService: PopupService | null popupService: PopupService | null
trajectoryService: TrajectoryService | null trajectoryService: TrajectoryService | null
fenceService: FenceService | null fenceService: FenceService | null
@ -28,7 +28,7 @@ export const useMapServices = () => {
const services: ServiceInstances = { const services: ServiceInstances = {
mapService: null, mapService: null,
markerService: null, markerService: null,
animationService: null,
// animationService: null,
popupService: null, popupService: null,
trajectoryService: null, trajectoryService: null,
fenceService: null, fenceService: null,
@ -48,7 +48,7 @@ export const useMapServices = () => {
// 重新初始化服务,确保markerService有地图实例 // 重新初始化服务,确保markerService有地图实例
services.mapService = mapService services.mapService = mapService
services.markerService = new MarkerService(mapService.map) services.markerService = new MarkerService(mapService.map)
services.animationService = new AnimationService(mapService.map)
// services.animationService = new AnimationService(mapService.map)
services.popupService = new PopupService() services.popupService = new PopupService()
services.trajectoryService = new TrajectoryService(mapService.map) services.trajectoryService = new TrajectoryService(mapService.map)
services.fenceService = new FenceService(mapService.map) services.fenceService = new FenceService(mapService.map)
@ -56,8 +56,6 @@ export const useMapServices = () => {
// 创建marker图层 // 创建marker图层
services.markerService.createMarkerLayer(props) services.markerService.createMarkerLayer(props)
// 创建波纹图层
services.animationService.createRippleLayer(props.markers || [], props.enableCluster)
// 创建轨迹图层 // 创建轨迹图层
services.trajectoryService.createTrajectoryLayer() services.trajectoryService.createTrajectoryLayer()
// 创建围栏图层 // 创建围栏图层
@ -72,10 +70,10 @@ export const useMapServices = () => {
const setMarkersVisible = (visible: boolean) => { const setMarkersVisible = (visible: boolean) => {
if (visible) { if (visible) {
services.markerService?.show() services.markerService?.show()
services.animationService?.show()
// services.animationService?.show()
} else { } else {
services.markerService?.hide() services.markerService?.hide()
services.animationService?.hide()
// services.animationService?.hide()
} }
} }
@ -135,19 +133,16 @@ export const useMapServices = () => {
* *
*/ */
const updateMarkers = (markers: any[], currentProps?: MapProps) => { const updateMarkers = (markers: any[], currentProps?: MapProps) => {
if (services.markerService && services.animationService) {
if (services.markerService) {
const map = services.mapService?.getMap() const map = services.mapService?.getMap()
const enableCluster = currentProps?.enableCluster ?? true
if (map) { if (map) {
// console.log('updateMarkers', markers) // console.log('updateMarkers', markers)
// 更新marker service(这会创建新的layer)
// 更新marker service(这可能会创建新的layer)
services.markerService.createMarkerLayer({ services.markerService.createMarkerLayer({
...currentProps, ...currentProps,
markers markers
}) })
// 重新创建波纹图层
services.animationService.createRippleLayer(markers, enableCluster)
} }
} }
} }
@ -169,7 +164,7 @@ export const useMapServices = () => {
const servicesToDestroy = [ const servicesToDestroy = [
services.mapService, services.mapService,
services.markerService, services.markerService,
services.animationService,
services.trajectoryService, services.trajectoryService,
services.fenceService, services.fenceService,
services.fenceDrawService services.fenceDrawService
@ -184,7 +179,6 @@ export const useMapServices = () => {
// 重置服务字段(保持 reactive 对象引用不变) // 重置服务字段(保持 reactive 对象引用不变)
services.mapService = null services.mapService = null
services.markerService = null services.markerService = null
services.animationService = null
services.popupService = null services.popupService = null
services.trajectoryService = null services.trajectoryService = null
services.fenceService = null services.fenceService = null

39
web/src/views/HandDevice/Home/components/constants/map.constants.ts

@ -29,7 +29,6 @@ export const STATUS_DICT = {
// 状态优先级定义 (数字越小优先级越高) // 状态优先级定义 (数字越小优先级越高)
export const STATUS_PRIORITY = { export const STATUS_PRIORITY = {
gasStatus_2: 1, gasStatus_2: 1,
gasStatus_1: 2, gasStatus_1: 2,
// battery_2: 3, // battery_2: 3,
@ -63,13 +62,37 @@ export const MAP_DEFAULTS = {
// 动画配置 // 动画配置
export const ANIMATION_CONFIG = { export const ANIMATION_CONFIG = {
duration: 3, // 动画周期(秒)
rippleCount: 5, // 波纹圈数量
phaseOffset: 0.6, // 波纹圈错开时间(秒)
targetFPS: 60, // 目标帧率
clusterThreshold: 12, // 聚合阈值
minRadius: 6, // 最小半径
maxRadius: 31, // 最大半径
/**
*
*/
duration: 3,
/**
*
*/
rippleCount: 5,
/**
*
*/
phaseOffset: 0.6,
/**
*
*/
targetFPS: 60,
/**
*
*/
clusterThreshold: 12,
/**
*
*/
minRadius: 6,
/**
*
*/
maxRadius: 31,
/**
*
*/
minOpacity: 0.05 // 最小透明度阈值 minOpacity: 0.05 // 最小透明度阈值
} }
export const DEFAULT_FENCES = [] as FenceData[] export const DEFAULT_FENCES = [] as FenceData[]

91
web/src/views/HandDevice/Home/components/services/animation.service.ts

@ -18,60 +18,20 @@ export class AnimationService {
private animationTimer: number | null = null private animationTimer: number | null = null
private map: Map | null = null private map: Map | null = null
private enableCluster: boolean = true
constructor(map: Map) { constructor(map: Map) {
this.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)
})
}
this.createRippleLayer()
} }
/** /**
* *
* enableCluster
*/ */
createRippleLayer(
markers: MarkerData[],
enableCluster: boolean = true
): VectorLayer<VectorSource> {
if (this.rippleLayer && this.map) {
this.updateData(markers)
return this.rippleLayer
}
this.enableCluster = enableCluster
private createRippleLayer() {
const source = new VectorSource() const source = new VectorSource()
this.rippleLayer = new VectorLayer({ this.rippleLayer = new VectorLayer({
source: source, source: source,
zIndex: 1, zIndex: 1,
style: (feature) => { style: (feature) => {
// 检查当前缩放级别,如果缩放级别较低(聚合状态),不显示波纹
const currentZoom = this.map?.getView().getZoom() || 0
// 如果启用了聚合且zoom级别较低,不显示波纹
if (this.enableCluster && currentZoom < ANIMATION_CONFIG.clusterThreshold) {
return [] // 不显示波纹
}
const startTime = feature.get('animationStart') const startTime = feature.get('animationStart')
const color = feature.get('rippleColor') const color = feature.get('rippleColor')
const elapsed = (Date.now() - startTime) / 1000 // 秒 const elapsed = (Date.now() - startTime) / 1000 // 秒
@ -119,13 +79,36 @@ export class AnimationService {
return styles return styles
} }
}) })
this.updateData(markers)
if (this.map) { if (this.map) {
this.map?.addLayer(this.rippleLayer) this.map?.addLayer(this.rippleLayer)
} }
}
return this.rippleLayer
clear() {
const source = this.rippleLayer?.getSource()
source?.clear()
} }
add(marker: MarkerData) {
const source = this.rippleLayer?.getSource()
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)
}
addAll(markers: MarkerData[]) {
this.clear()
// 为每个标记添加波纹效果
markers.forEach((marker) => {
this.add(marker)
})
}
show() { show() {
this.rippleLayer?.setVisible(true) this.rippleLayer?.setVisible(true)
this.startAnimation() this.startAnimation()
@ -162,29 +145,15 @@ export class AnimationService {
} }
/** /**
*
*/
updateRipples(): void {
if (this.rippleLayer) {
this.rippleLayer.getSource()?.changed()
}
}
/**
*
*/
getRippleLayer(): VectorLayer<VectorSource> | null {
return this.rippleLayer
}
/**
* *
*/ */
destroy(): void { destroy(): void {
this.stopAnimation()
if (this.rippleLayer && this.map) { if (this.rippleLayer && this.map) {
this.map?.removeLayer(this.rippleLayer) this.map?.removeLayer(this.rippleLayer)
} }
this.stopAnimation()
this.rippleLayer = null this.rippleLayer = null
this.map = null this.map = null
} }

32
web/src/views/HandDevice/Home/components/services/marker.service.ts

@ -9,29 +9,31 @@ import { Point } from 'ol/geom'
import { Style } from 'ol/style' import { Style } from 'ol/style'
import { fromLonLat } from 'ol/proj' import { fromLonLat } from 'ol/proj'
import { AnimationService } from './animation.service'
import type { MarkerData, MapProps } from '../types/map.types' import type { MarkerData, MapProps } from '../types/map.types'
import {
createMarkerStyle,
getClusterMarkerData,
getStatusColor
} from '../utils/map.utils'
import { createMarkerStyle, getClusterMarkerData, getStatusColor } from '../utils/map.utils'
import { ANIMATION_CONFIG } from '../constants/map.constants' import { ANIMATION_CONFIG } from '../constants/map.constants'
export class MarkerService { export class MarkerService {
private map: Map | null = null private map: Map | null = null
markerLayer: VectorLayer<VectorSource | Cluster> | null = null markerLayer: VectorLayer<VectorSource | Cluster> | null = null
// 当前图层模式(single或cluster聚合)
// 当前图层模式(single或cluster聚合):避免重复创建图层
private currentLayerMode: 'single' | 'cluster' | '' = '' private currentLayerMode: 'single' | 'cluster' | '' = ''
private vectorSource: VectorSource private vectorSource: VectorSource
private animationService: AnimationService | null = null
constructor(map: Map) { constructor(map: Map) {
this.map = map this.map = map
this.vectorSource = new VectorSource() this.vectorSource = new VectorSource()
this.animationService = new AnimationService(map)
} }
show() { show() {
this.markerLayer?.setVisible(true) this.markerLayer?.setVisible(true)
this.animationService?.show()
} }
hide() { hide() {
this.markerLayer?.setVisible(false) this.markerLayer?.setVisible(false)
this.animationService?.hide()
} }
/** /**
* *
@ -69,6 +71,8 @@ export class MarkerService {
*/ */
// : VectorLayer<VectorSource | Cluster> // : VectorLayer<VectorSource | Cluster>
private createMarkerLayerFromProps(props: MapProps) { private createMarkerLayerFromProps(props: MapProps) {
// console.log('createMarkerLayerFromProps')
this.animationService?.clear()
this.updateData(props) this.updateData(props)
// 检查是否应该强制使用单个marker模式 // 检查是否应该强制使用单个marker模式
@ -101,11 +105,14 @@ export class MarkerService {
if (!features || features.length === 0) { if (!features || features.length === 0) {
return new Style() // 返回空样式,隐藏无效的feature return new Style() // 返回空样式,隐藏无效的feature
} }
// console.log('聚合元素', features)
if (features.length === 1) { if (features.length === 1) {
// 单个marker // 单个marker
const markerData: MarkerData = features[0].get('markerData') const markerData: MarkerData = features[0].get('markerData')
this.animationService?.add(markerData)
return markerData ? createMarkerStyle(markerData.statusColor || '') : new Style() return markerData ? createMarkerStyle(markerData.statusColor || '') : new Style()
} else { } else {
// 聚合marker // 聚合marker
@ -117,17 +124,19 @@ export class MarkerService {
} }
}) })
} else { } else {
// markers可能变化,必须更新
this.animationService?.addAll(props.markers || [])
if (this.currentLayerMode === 'single') return if (this.currentLayerMode === 'single') return
this.currentLayerMode = 'single' this.currentLayerMode = 'single'
// console.log('基础marker') // console.log('基础marker')
newLayer = new VectorLayer({ newLayer = new VectorLayer({
source: this.vectorSource, source: this.vectorSource,
zIndex: 1, zIndex: 1,
renderOrder: (a, b) => { renderOrder: (a, b) => {
// console.log('renderOrder',a,b.get('markerData').time);
// 按xxx属性排列 // 按xxx属性排列
return b.get('markerData').statusPriority - a.get('markerData').statusPriority return b.get('markerData').statusPriority - a.get('markerData').statusPriority
} }
@ -136,16 +145,12 @@ export class MarkerService {
if (this.markerLayer) { if (this.markerLayer) {
const isVisible = this.markerLayer?.getVisible() || false const isVisible = this.markerLayer?.getVisible() || false
newLayer.setVisible(isVisible)
newLayer.setVisible(isVisible) // 新图层保持当前可见状态
this.map?.removeLayer(this.markerLayer) this.map?.removeLayer(this.markerLayer)
} }
this.markerLayer = newLayer this.markerLayer = newLayer
this.map?.addLayer(this.markerLayer) this.map?.addLayer(this.markerLayer)
// 获取所有图层
// const layers = this.map?.getLayers().getArray() || []
// console.log('this.currentLayerMode', this.currentLayerMode, layers)
} }
/** /**
@ -162,6 +167,7 @@ export class MarkerService {
*/ */
destroy(): void { destroy(): void {
this.markerLayer = null this.markerLayer = null
this.animationService?.destroy()
// this.currentProps = null // this.currentProps = null
this.map = null this.map = null
} }

Loading…
Cancel
Save