openlayer中批量点位高亮,批量高亮时解除聚合状态,如何实现?数据量大时,如何解决性能卡顿问题?

需求:外部指定多个点位进行跳转到地图并高亮。

1.如果存在聚合状态下的点位,需要放大地图到所有高亮点均不处于聚合(或者放大到最大层级依然聚合,这种情况不去考虑,无实际物理意义)

2.大量高亮点要求不卡顿。

3.其他业务逻辑不再展开说明。

先不比比,先上图

分析:

1.只分析这批设备的点位是当前地图的情况下,并且解决卡顿的思路,其他业务逻辑不必赘述。

2. 思路:用当前层级判断点位是否聚合,如果聚合,则放大地图层级,再次验证,直至不聚合或者到达最大缩放层级。

(能实现但是解决不了点位多时卡顿问题,而且这种方式比较挫,属于赶时间强行实现,不需要的同学可以去后面看方案优化!!!!!)

Day1实现:

/*
     * @param devList Array 设备数组 包含deviceId
     * @param mapHierarchy Number|String 地图层级
     * */
    async locateToCamera(devList, mapHierarchy){
       // 定义检查某个设备是否为聚合状态
       const isDeviceNoClustered = item => {
           const cluster = this.findClusterByDeviceId(item.deviceId)
           // 非聚合状态时 features 长度为 1
           return !cluster || cluster.get('features').length === 1 
       }
       // 等待渲染稳定的函数
       const waitForStability = () => new Promise(resolve => setTimeout(resolve, 500))
    
       // 更新视图并等待渲染完成的函数
       const updateViewAndWaitForRender = (zoomLevel, devList) => {
        return new Promise(resolve => {
          // 随便用一个点位更新视图,放大过程中用任意点即可
          const firstDeviceId = devList[0].deviceId
          const marker = this.cadMap.getMarkerById(firstDeviceId)
          if (marker) {
            const { pointX, pointY } = marker.get('payload')
            this.cadMap.viewAnimate({
              center: [pointX, pointY],
              zoom: zoomLevel
            })

            // 使用 requestAnimationFrame 确保渲染完成
            const checkRenderComplete = () => {
              requestAnimationFrame(() => {
                this.cadMap.map.once('rendercomplete', resolve)
              })
            }

            checkRenderComplete()
          }
        })
      }
      let allDevicesNoClustered = false
      let zoom
      let currentZoom = this.cadMap.getView().getZoom()
      let defaultZoom = parseFloat(mapHierarchy)
      if (devList.length > 0) {
        // 确保默认层级小于当前层级时,直接跳转到当前层级
        zoom = currentZoom >= defaultZoom ? currentZoom : defaultZoom
        // 循环增加层级,直到所有点位都非聚合
        while (!allDevicesNoClustered && zoom < 16) {
          // 更新视图并等待渲染完成
          await updateViewAndWaitForRender(zoom, devList)
          // 等待视图稳定
          await waitForStability()
          // 检查是否所有点位已经非聚合
          allDevicesNoClustered = devList.every(isDeviceNoClustered)
          if (!allDevicesNoClustered) {
            zoom += 1 // 放大层级
          }
        }
        // 查找第一个达到非聚合状态的点位
        const nonClusteredDeviceId = devList.find(isDeviceNoClustered)

        // 最终定位到该点位
        if (nonClusteredDeviceId) {
          const marker = this.cadMap.getMarkerById(nonClusteredDeviceId)
          if (marker) {
            const { pointX, pointY } = marker.get('payload')
            this.cadMap.viewAnimate({
              center: [pointX, pointY],
              zoom
            })
          }
        }
      }
     }

其中cadMap是 CadMap类生成的对象,部分方法如下:

export default class CadMap extends Emitter {
  constructor(url, options = {}) {
    super()
    if (!url) {
      throw new Error('ImageMap need url option')
    }
    this._mapServiceUrl = url
    this._options = {
      cluster: true,
      ...options
    }
    this.map = null
    this.layer = null
    this.source = null
    this.marker = null
    this.popup = null
    this.overviewMapControl = null
    this.featureDraggable = false
    this.clickTimeout = null
  }

    ...
    // 通过设备id获取到指定设备对应的要素feature
    getMarkerById(id) {
        const source = this.getSource()
        return source.getFeatureById(id)
    }
    // 获取数据源
    getSource() {
        // isCluster 代表是否开启聚合
        const { cluster: isCluster } = this.options_
        if (isCluster && this.clusterLayer.getVisible()) {
          return this.clusterLayer.getSource().getSource()
        } else {
          return this.vectorLayer.getSource()
        }
    }
    // 动画
    viewAnimate(AnimationOptions) {
        this.map.getView().animate({
          ...AnimationOptions,
          duration: 500
        })
    }
    // 设置点位高亮状态
    setActive(value, changeImmediate = true) {
        this.active_ = value
        changeImmediate && this.changed()
    }
    ...
}

当数据量正常时正常展示,当数据量大时这个具体的卡顿程度就不展示了,大概需要几分钟甚至更久。

对需求时只是说上限50个点位进行跳转,本机测试大概3-4s,还能接受,但是现网上面100甚至更多点位,这个时长就非常感人了

就着手分析一下卡顿原因

通过定位可知是 监听设置需要高亮的点位会设置每个要素的active,然后地图上每一个点位变更都会引起重绘,也就是openlayer内部的changed方法,所以我们可以试着最后一次性再设置acitve

activeDeviceList: {
      handler(newVal, oldVal) {
        const toggleHighlight = (info, isActive) => {
          info.forEach((item, index) => {
            const feature = this.cadMap.getMarkerById(item.deviceId)
            feature && feature.setActive(isActive, index === info.length - 1)
          })
        }
        if (!this.cadMap?.map) return
        // 如果旧值为空,则只需要高亮新值
        if (!oldVal.length) {
          if (newVal.length) toggleHighlight(newVal, true)
        }
        // 如果有旧值,则取消旧值的高亮,再高亮新值
        else {
          toggleHighlight(oldVal, false)
          if (newVal.length) toggleHighlight(newVal, true)
        }
      },
      deep: true,
      immediate: true
    }

解决了卡顿问题,然后还有高亮点在聚合内部该如何展现呢?

当前的聚合代码如下:

... 
import { Cluster as ClusterSource, Vector as VectorSource } from 'ol/source'
...
if (isCluster) {
      const clusterSource = new ClusterSource({
        distance: 40,
        source: new VectorSource()
      })
      this.clusterLayer = new VectorLayer({
        source: clusterSource,
        zIndex: 100,
        style: clusterStyle
      })
      this.vectorLayer.setVisible(true)
      this.map.addLayer(this.clusterLayer)
    }

试着从样式回调

function clusterStyle(feature) {
  const features = feature.get('features')
  const size = features.length
  if (size > 1) {
    const activeFeatures = features.filter(f => f.getActive())
    return new Style({
      image: new Icon({
        crossOrigin: 'anonymous',
        src: require('@/assets/img/cadMap/cluster_bg.png'),
        size: [40, 40]
      }),
      text: new Text({
        text: (size - activeFeatures.length).toString(),
        fill: new Fill({
          color: '#ffffff'
        }),
        font: '16px Inter-Regular'
      })
    })
  } else {
    const originalFeature = feature.get('features')[0]
    const active = originalFeature.getActive()
    return createStyle(originalFeature.get('payload'), {
      alarm: false,
      active
    })
  }
}

在这边可以按照聚合点内部的高亮点位的数量,去计算剩余聚合的数量,但是发现好像不生效,聚合点仍然包含高亮点,只是数量对上了,所以就想办法改造一下这个方法,结果发现该方法只支持返回一种类型,要么正常点位样式,要么聚合点位样式,不支持组合点位,所以想办法从openlayer的Cluster 入手

import { Cluster } from 'ol/source'
import { createEmpty, createOrUpdateFromCoordinate, buffer } from 'ol/extent'
import { getUid } from 'ol'

export default class CustomCluster extends Cluster {
  /**
   * @param {Options} options Cluster options, with an additional canCluster function.
   * @param {function(Feature): boolean} options.canCluster A function that determines whether
   * the feature should be clustered. It should return true to allow clustering and false to prevent it.
   */
  constructor(options) {
    super(options)

    /**
     * @type {function(Feature): boolean}
     * @private
     */
    this.canCluster =
      options.canCluster ||
      function () {
        return true // Default is to allow clustering.
      }
  }

  /**
   * @protected
   */
  cluster() {
    if (this.resolution === undefined || !this.source) {
      return
    }
    const extent = createEmpty()
    const mapDistance = this.distance * this.resolution
    const features = this.source.getFeatures()

    /** @type {Object<string, true>} */
    const clustered = {}

    for (let i = 0, ii = features.length; i < ii; i++) {
      const feature = features[i]
      if (!(getUid(feature) in clustered)) {
        const geometry = this.geometryFunction(feature)
        if (geometry) {
          const coordinates = geometry.getCoordinates()
          createOrUpdateFromCoordinate(coordinates, extent)
          buffer(extent, mapDistance, extent)
          if (this.canCluster(feature)) {
            const neighbors = this.source.getFeaturesInExtent(extent).filter(neighbor => {
              if (!this.canCluster(neighbor)) {
                return false
              }
              const uid = getUid(neighbor)
              if (uid in clustered) {
                return false
              }
              clustered[uid] = true
              return true
            })
            this.features.push(this.createCluster(neighbors, extent))
          } else {
            const uid = getUid(feature)
            clustered[uid] = true
            this.features.push(this.createCluster([feature], extent))
          }
        }
      }
    }
  }
}

继承一下原来的Cluster类,扩展 canCluster 方法,代表是否聚合,此时外部生成聚合图层时加上刚刚判断的高亮状态即可

const clusterSource = new CustomCluster({
        distance: 40,
        source: new VectorSource(),
        canCluster: feature => {
          return !feature.getActive()
        }
      })

此时当我处于高亮状态时,无论缩放地图,高亮点不会进入聚合点。

到此功能已经完全实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值