openlayer聚合 + 告警动画

311 阅读1分钟

创建地图

initMap() {
      if (this.map) return

      const projection = 'EPSG:3857'

      const url = 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'

      this.map = new ol.Map({
        target: 'olMap',
        projection,
        view: new ol.View({
          center,
          zoom,
          minZoom: 8, // 最小缩放级别
          maxZoom: 18, // 最大缩放级别
          projection,
          // 因为存在非整数的缩放级别,所以设置该参数为true来让每次缩放结束后自动缩放到距离最近的一个整数级别,这个必须要设置,当缩放在非整数级别时地图会糊
          constrainResolution: true, 
        }),
        controls: ol.control.defaults({
          zoom: false,
          attribution: false,
        }),
        layers: [
          new ol.layer.Tile({
            source: new ol.source.XYZ({
              url: url,
              wrapX: true,
              style: 'default',
            }),
            projection,
          }),
        ],
      })

      this.map.on('singleclick', this.mapClick)
      //   this.map.on('moveend', this.moveEnd)
    },

聚合

// 点聚合
    cluster(data) {
      const source = new ol.source.Vector()
      data.forEach((item, index) => {
        const coordinates = ol.proj.transform([item.longitude, item.latitude], 'EPSG:4326', 'EPSG:3857')
        const feature = new ol.Feature({
          id: `point_${index}`,
          data: item,
          geometry: new ol.geom.Point(coordinates),
        })
        // 创建样式 - 一定要给每个点位设置单独的样式
        const style = new ol.style.Style({
          image: new ol.style.Icon({
            anchor: [0.5, 1], // 显示位置
            size: [36, 36], // 尺寸
            src: this.getPointIconByType(item),
          }),
        })
        feature.setStyle(style)
        source.addFeature(feature)
      })

      // 聚合
      const cluster = new ol.source.Cluster({
        source: source,
        distance: 100,
      })

      // 创建图层
      const that = this
      this.clusterLayer = new ol.layer.Vector({
        source: cluster,
        style: function (feature, resolution) {
          const size = feature.get('features').length
          if (size == 1) {
            //const data = feature?.getProperties()?.features[0]?.getProperties()?.data
            //that.setAlarm(data, true)
            const style = feature?.getProperties()?.features[0].getStyle()
            return style
          } else {
            //feature?.getProperties()?.features.map((v) => v.getProperties().data)
              //.forEach((v) => {
               // that.setAlarm(v, false)
              //})
            return new ol.style.Style({
              image: new ol.style.Icon({
                anchor: [0.32, 0.35], // 显示位置
                size: [48, 48], // 尺寸
                src: require('@/assets/gis/blue.png'), // 图片url
              }),
              text: new ol.style.Text({
                text: size.toString(),
                fill: new ol.style.Fill({
                  color: 'white',
                }),
              }),
            })
          }
        },
      })
      // 添加图层
      this.map.addLayer(this.clusterLayer)
    },

image.png

聚合里面的点位有告警时,替换点位图标,展示告警动画

  • alarmArr是告警数组,在页面上循环创建多个div,作为动画容器

  • 注意这里使用的v-show

  • 我在使用v-if时发现,多次缩放聚合图层时,只有第一次缩放时效果正常,而v-show则每次缩放时都正常

<!-- 批量告警闪烁 -->
    <template v-for="item of alarmArr">
      <div :key="item.alarmId" v-show="item.active" class="point_animation" :id="item.alarmId">
        <p></p>
        <span></span>
      </div>
    </template>
    
    <style lang="scss">
    .point_animation {
  background: transparent;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  position: absolute;

  p,
  span {
    position: absolute;
    width: 4px;
    height: 4px;
    // background-color: #f004;
    animation: point_animation 1.5s infinite;
    box-shadow: 0px 0px 5px #f00;
    margin: 0px;
    border-radius: 50%;
  }

  span {
    animation-delay: 0.8s;
  }
  @keyframes point_animation {
    10% {
      transform: scale(10);
    }

    100% {
      transform: scale(8);
    }
  }
}
    </style>

告警动画

  • 把上面聚合图层代码里面注释的代码打开

image.png

  • 再加上下面的代码
setAlarm(data, flag) {
      if (!data?.info) return
      const item = this.alarmArr.filter((v) => v.alarmId === data.info.alarmId)[0]
      item.active = flag

      if (flag) {
        this.$nextTick(() => {
          const popup = new ol.Overlay({
            id: item.alarmId,
            element: document.getElementById(item.alarmId),
            autoPan: true, //跟随地图移动
            positioning: 'center-center',
            offset: [-8, -24],
            stopEvent: false,
            autoPanAnimation: {
              duration: 250,
            },
          })
          // 弹出popup
          popup.setPosition(ol.proj.transform([item.longitude, item.latitude], 'EPSG:4326', 'EPSG:3857'))

          this.map.addOverlay(popup)
        })
      }
    },
  • 告警动画出来了,聚合图层缩放时,动画效果也随之展开消失

image.png

image.png

告警动画二

  • 上面的动画dom绑定的是告警id

  • 一个设备是可以有多个告警的,告警id是会变化的

  • 当处理完一个告警,展示下一个告警时,告警id已经发生变化,此时动画弹框dom被删除了

  • 我使用的是setPosition, 但是dom还是被删了

  • 那就换一种实现方式

  • 用设备id绑定告警dom,设备id是不会变的

  • 初始渲染n个告警动画,使用setPositon(undefined)隐藏,在需要显示的时候,设置正确的位置

  • 修改代码如下

<!-- 批量告警闪烁动画 -->
    <div v-for="item of alarmArr" :key="item.deviceId">
      <div class="point_animation" :id="item.deviceId">
        <p></p>
        <span></span>
      </div>
    </div>
    
      // 此处为获取设备列表后代码
      this.alarmArr = JSON.parse(JSON.stringify(res.resultList))
      this.$nextTick(() => {
        this.alarmArr.forEach((v) => {
          const popup = new ol.Overlay({
            id: v.deviceId,
            element: document.getElementById(v.deviceId),
            autoPan: true, //跟随地图移动
            positioning: 'center-center',
            offset: [-this.keepScale(2) + 16, -this.keepScale(2) + 19], //32,38
            stopEvent: false,
            autoPanAnimation: {
              duration: 250,
            },
          })
          // 弹出popup
          this.alarmPopupCollect.push(popup)
          popup.setPosition(undefined)
          this.map.addOverlay(popup)
        })
      })
      
      // 控制告警动画是否展示
      setAlarm(data, flag) {
      const curPop = this.alarmPopupCollect.filter((v) => v.getId() === data.deviceId)[0]
      if (!flag) {
        this.$nextTick(() => {
          curPop.setPosition(undefined)
        })
        return
      }

      if (data.info) {
        this.$nextTick(() => {
          curPop.setPosition(this.coordTransform([data.longitude, data.latitude]))
        })
      } else {
        this.$nextTick(() => {
          curPop.setPosition(undefined)
        })
      }
    },

踩坑

  • map.removeOverlay(_overlay)
    用于移除指定的overlay,该方法会删除overley以及dom节点,
    (重新添加overlay时,添加dom会有问题,需要注意,比如弹窗)