利用openlayers实现台风轨迹

1,787 阅读5分钟

近期项目中涉及使用openlayer 绘制台风轨迹的效果,事后坐下分享与整理,效果就像这样:

台风轨迹.gif

功能描述

  • 台风轨迹点实时绘制,根据不同点的类型绘制不同的轨迹点颜色
  • 轨迹线绘制,涉及实时轨迹线段与预报轨迹线,根据台风类型绘制成不同颜色
  • 当前正在发生的台风还需增加当前台风所风圈位置
  • 台风轨迹点点击弹框显示轨迹点信息

openlayers(简称ol)这里不做介绍,刚开始写此类文章,直接上代码

创建一个地图容器

引入地图相关对象

import Map from 'ol/Map';
import View from 'ol/View';
import XYZ from 'ol/source/XYZ';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';

创建地图对象

都是一些基本活

const center = [-5639523.95, -3501274.52];
const map = new Map({
  target: document.getElementById('map'),
  view: new View({
    center: center,
    zoom: 10,
    minZoom: 2,
    maxZoom: 19,
  }),
  layers: [ ],
});
this.addEventMapClick()

监听地图点击事件

 addEventMapClick () {
    const nameDom = document.createElement('div')
    nameDom.setAttribute('class', 'typhoon-popup')
    const nameOverlay = new ol.Overlay({
      element: nameDom,
      position: [0, 0],
      positioning: 'right-center',
      stopEvent: false,
      insertFirst: false,
      autoPanAnimation: {
        duration: 250
      }
    })
    this.viewer.addOverlay(nameOverlay)
    this._popup = nameOverlay
    // 监听地图点击事件
    this.viewer.on('singleclick', e => {
      this._popup.getElement().parentElement.style.display = 'none'
      this.viewer.forEachFeatureAtPixel(
        e.pixel,
        (result) => {
          if (result) {
            let Properties = result.get('properties')
            let layerType = result.get('layerType')
            // 台风点点击
            // && layerType === 'typhoonpoint'
            if (layerType === 'typhoonLyer') {
              let html = `<div class="typhoonLyer"><div class="con">名称: ${Properties.CODE || ''} ${Properties.NAME_CN || ''} ${Properties.NAME_EN || ''}</div>
              <div class="con">风速: ${Properties.MOVE_SPEED || '--'} km/h</div>
              <div class="con">中心气压: ${Properties.PRESS || '--'}</div>
              <div class="con">时间: ${Properties.time || '--'}</div>
              <div class="con">中心位置: ${Properties.LON}/${Properties.LAT}</div></div>`
              this._popup.getElement().innerHTML = html
              this._popup.setPosition([Properties.LON, Properties.LAT])
              // this._popup.setOffset([25, 0])
              this._popup.getElement().parentElement.style.display = 'block'
            } else {
              this._popup.getElement().parentElement.style.display = 'none'
            }
          }
        }
      )
    })
  }

开始绘制

准备台风数据和图层

台风数据我是用的JSON。这里就简单描述一下数据中只要用到的字段信息

[
      {
        CODE: "202122",//台风编号
        DB7: null,//七级东北
        DB10: null,//十级东北
        DB12: null,//十二级东北
        DN7: null,//七级东南
        DN10: null,//十级东南
        DN12: null,//十二级东南
        LAT: 5.5,//维度
        LON: 140.9,//经度
        MOVE_DIR: null,//风向
        MOVE_SPEED: null,//风向速度
        OBJECTID: 27848,//id
        PRESS: 998,//中心气压
        SHIJIAN: null,
        STRENGTH: "台风(热带风暴级)",//强度
        TH: null,
        TIME: "2021-12-13-14",//日期
        WIND: 18,//最大风速
        XB7: null,//七级西北
        XB10: null,//十级西北
        XB12: null,//十二级西北
        XN7: null,//七级西南
        XN10: null,//十级西南
        XN12: null,//十二级西南
      },
   ]
  let tfsource = new ol.source.Vector({
      crossOrigin: 'anonymous',
      features: []
    })
    let tflayer = new ol.layer.Vector({
      source: tfsource
    })

   map.addLayer(tflayer)

绘制台风名称

// 利用第一个点 创建名称Overlay层 显示台风名称
 const nameDom = document.createElement('div')
    nameDom.setAttribute('class', 'typhoon-name-panel')
    nameDom.classList.add(lx)
    nameDom.innerHTML = label
    const position = [point.LON, point.LAT]
    const nameOverlay = new ol.Overlay({
      element: nameDom,
      position: position,
      wz: position,
      positioning: 'center-left',
      offset: [15, 0]
    })
    map.addOverlay(nameOverlay)
    map.getView().setCenter(position)

绘制台风轨迹点和轨迹线

    //point 为数组对象中每一个点数据
    // 点颜色 根据强度区分不同点的颜色
    let pointColor = this.setPointFillColor(point.STRENGTH)
    // 添加点
    if (point.LON && point.LAT) {
      const feature = new ol.Feature({
        geometry: new ol.geom.Point([point.LON, point.LAT]),
        layerType: 'typhoonLyer',
        properties: point
      })
      // this.tfStyle为我提前定义到的各种类型的点样式
      feature.setStyle(this.tfStyle[pointColor.index])
     tflayer.getSource().addFeature(feature)
    }
   
  // 添加线
  let startPoint = [point.LON, point.LAT] 开始点
  let endPonit = [points[index - 1].LON, points[index - 1].LAT] 结束点
  let coords = [startPoint, endPonit]
  let lineDash 线段样式 实线或者虚线
  if (lx !== 'ss') {
    lineDash = [0]
  } else {
    lineDash = (!point.predict && !points[index - 1].predict) ? [0] : [10]
  }

// this.tfLinStyle 为提前定义好的线段样式
  let lineStyle = lineDash[0] === 0 ? this.tfLinStyle[pointColor.index] : this.tfLinStyle[6]
  let feature = new ol.Feature({
    geometry: new ol.geom.LineString(coords),
    layerType: 'typhoonLyer',
    properties: point
  })
  feature.setStyle(lineStyle)
  tflayer.getSource().addFeature(feature)
    

代码解析 文中提到的this.tfLinStyle 和 this.tfStyle 为提前定义好的点和线的样式,这么做的目的是为了提高性能,减少oplayer中new 一堆重复性的样式堆栈,消耗内存

this.tfStyle = [
      new ol.style.Style({
        image: new ol.style.Circle({
          radius: 6,
          fill: new ol.style.Fill({
            color: '#eed139'
          }),
          stroke: new ol.style.Stroke({
            color: 'rgba(0, 0, 0, 0.6)',
            width: 1
          })
        })
      }),
      new ol.style.Style({
        image: new ol.style.Circle({
          radius: 6,
          fill: new ol.style.Fill({
            color: '#0000ff'
          }),
          stroke: new ol.style.Stroke({
            color: 'rgba(0, 0, 0, 0.6)',
            width: 1
          })
        })
      }),
      new ol.style.Style({
        image: new ol.style.Circle({
          radius: 6,
          fill: new ol.style.Fill({
            color: '#0f8000'
          }),
          stroke: new ol.style.Stroke({
            color: 'rgba(0, 0, 0, 0.6)',
            width: 1
          })
        })
      }),
      new ol.style.Style({
        image: new ol.style.Circle({
          radius: 6,
          fill: new ol.style.Fill({
            color: '#fe9c45'
          }),
          stroke: new ol.style.Stroke({
            color: 'rgba(0, 0, 0, 0.6)',
            width: 1
          })
        })
      }),
      new ol.style.Style({
        image: new ol.style.Circle({
          radius: 6,
          fill: new ol.style.Fill({
            color: '#fe00fe'
          }),
          stroke: new ol.style.Stroke({
            color: 'rgba(0, 0, 0, 0.6)',
            width: 1
          })
        })
      }),
      new ol.style.Style({
        image: new ol.style.Circle({
          radius: 6,
          fill: new ol.style.Fill({
            color: '#fe0000'
          }),
          stroke: new ol.style.Stroke({
            color: 'rgba(0, 0, 0, 0.6)',
            width: 1
          })
        })
      })
    ]
    this.tfLinStyle = [
      new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#eed139',
          width: 2,
          lineDash: [0]
        })
      }),
      new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#0000ff',
          width: 2,
          lineDash: [0]
        })
      }),
      new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#0f8000',
          width: 2,
          lineDash: [0]
        })
      }),
      new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#fe9c45',
          width: 2,
          lineDash: [0]
        })
      }),
      new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#fe00fe',
          width: 2,
          lineDash: [0]
        })
      }),
      new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#fe0000',
          width: 2,
          lineDash: [0]
        })
      }),
      new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#fe0000',
          width: 2,
          lineDash: [10]
        })
      })]

setPointFillColor函数判别点类型

setPointFillColor (type) {
    let pointFillColor = '#eed139'
    let index = 0
    switch (type) {
      case '台风(热带低压)':
      case '热带低压':
        pointFillColor = '#eed139'
        index = 0
        break
      case '热带风暴':
      case '热带风暴级':
      case '台风(热带风暴)':
      case '台风(热带风暴级)':
        pointFillColor = '#0000ff'
        index = 1
        break
      case '台风(强热带风暴级)':
      case '强热带风暴级':
      case '强热带风暴':
        pointFillColor = '#0f8000'
        index = 2
        break
      case '台风':
        pointFillColor = '#fe9c45'
        index = 3
        break
      case '强台风':
      case '台风(强台风级)':
      case '台风(强台风)':
        pointFillColor = '#fe00fe'
        index = 4
        break
      case '超强台风':
      case '台风(超强台风级)':
      case '台风(超强台风)':
        pointFillColor = '#fe0000'
        index = 5
        break
    }
    return {pointFillColor, index}
  }

以上代码很完整,我加了注释,整体思路总结如下:

  • 先获取台风数据
  • 构造台风轨迹图层并添加到地图容器
  • 循环构造点、线及名称要素对象 添加到台风图层数据源中
  • 添加风圈动画

添加台风风圈动画

根据判断台风类型是否是实时台风还是历史台风进行添加台风风圈,在数据中获取到最后一个实时点位数据,利用Overlay添加一个动态图标,我是利用的gif图片

    let key = LX + '-' + tfbh
    const points = this._typhoonData[key].point
    let fqindex = points.findIndex(item => !item.predict)
    // 添加风圈
    if (fqindex) {
      const nameDom = document.createElement('div')
      nameDom.setAttribute('class', 'typhoon-area')
      let nameOverlay = new ol.Overlay({
        position: [points[fqindex].LON, points[fqindex].LAT],
        positioning: 'center-bottom',
        element: nameDom, // 绑定上面添加的元素
        stopEvent: false,
        offset: [-15, -15]// 图片偏移量
      })
      this.viewer.addOverlay(nameOverlay)
      this._tfenterCollection[key]['areaoverlay'] = nameOverlay
    }

让台风轨迹动起来

加载的时候利用定时器,一个点位一个点位的绘制,这样看起来就有动画效果啦

 this._typhoonPlayFlag[key] = setInterval(() => {
      去干些事请
    }, 50)

结尾

这里我只是大致的描述了我绘制台风轨迹的一些思路和大致方法,具体项目中,我把整个过场都封装成一个class 类,毕竟台风可能是有多条的,而且还需要做一些显示隐藏等的一些操作,我这里不做过多描述,仅做为一些思路分享,谢谢