Mapbox绘制台风及台风眼(含动画)

目录

最终效果

实现思路(三个图层)

代码实现

绘制台风路线并添加动画

绘制台风点位并添加动画

绘制台风眼图标并旋转

执行下面两个函数即可


最终效果

实现思路(三个图层)

第一次自己做的时候是按照这个思路来的,如果你也是新手,可以参考一下下面思路

1、绘制台风路线并添加动画

        备注:gif中白色实线是已经发生过的台风路线,虚线是预测的台风路线

        动画思路:使用定时器,定时器每次执行时,每次添加新的坐标

2、绘制台风点位并添加动画

        备注:不同颜色的点位代表不同台风登记(台风路线和台风点位是两个图层)       

        动画思路:使用定时器,定时器每次执行时,每次添加新的坐标

3、绘制台风眼图标

        备注:单独一个图层

4、台风眼图标旋转

代码实现

以下是台风数据

// 台风等级颜色
let tfColor = new Map()
tfColor.set('TD', '#30fc31')
tfColor.set('TS', '#307efa')
tfColor.set('STS', '#fffc00')
tfColor.set('TY', '#ff9c00')
tfColor.set('STY', '#fb7cff')
tfColor.set('SuperTY', '#fa3030')
// 构造实况数据(台风编号,当前时间,经度,纬度,气压)
let liveData = [
  {typhoonID: '2305', datetime: '2023-07-24 02:00:00', longitude: 127.1, latitude: 15.2, pressure: 965, grade: 'TY'},
  {typhoonID: '2305', datetime: '2023-07-24 05:00:00', longitude: 127, latitude: 15.4, pressure: 960, grade: 'TY'},
  {typhoonID: '2305', datetime: '2023-07-25 08:00:00', longitude: 124.6, latitude: 17.6, pressure: 915, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-25 11:00:00', longitude: 124, latitude: 18, pressure: 915, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-25 14:00:00', longitude: 123.8, latitude: 18.2, pressure: 915, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-25 17:00:00', longitude: 123.3, latitude: 18.6, pressure: 915, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-25 20:00:00', longitude: 122.8, latitude: 18.8, pressure: 920, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-25 23:00:00', longitude: 122.2, latitude: 18.9, pressure: 925, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-26 02:00:00', longitude: 121.6, latitude: 19, pressure: 925, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-26 05:00:00', longitude: 121.3, latitude: 18.8, pressure: 925, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-26 11:00:00', longitude: 121.1, latitude: 19, pressure: 930, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-26 14:00:00', longitude: 121, latitude: 19.3, pressure: 930, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-26 17:00:00', longitude: 120.7, latitude: 19.4, pressure: 935, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-26 20:00:00', longitude: 120.6, latitude: 19.6, pressure: 935, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-26 23:00:00', longitude: 120.4, latitude: 19.9, pressure: 935, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-27 02:00:00', longitude: 120, latitude: 20, pressure: 935, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-27 05:00:00', longitude: 120, latitude: 20.1, pressure: 940, grade: 'STY'},
  {typhoonID: '2305', datetime: '2023-07-27 08:00:00', longitude: 119.9, latitude: 20.5, pressure: 940, grade: 'STY'},
  {typhoonID: '2305', datetime: '2023-07-27 11:00:00', longitude: 119.5, latitude: 20.9, pressure: 940, grade: 'STY'},
  {typhoonID: '2305', datetime: '2023-07-27 14:00:00', longitude: 119.3, latitude: 21.1, pressure: 940, grade: 'STY'},
  {typhoonID: '2305', datetime: '2023-07-27 17:00:00', longitude: 119.2, latitude: 21.4, pressure: 935, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-27 20:00:00', longitude: 119.2, latitude: 21.7, pressure: 930, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-27 23:00:00', longitude: 119.1, latitude: 22.2, pressure: 925, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-28 02:00:00', longitude: 119, latitude: 22.8, pressure: 925, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-28 05:00:00', longitude: 118.9, latitude: 23.4, pressure: 930, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-28 07:00:00', longitude: 118.9, latitude: 23.9, pressure: 930, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-28 10:00:00', longitude: 118.6, latitude: 24.7, pressure: 945, grade: 'STY'}
]
// 预测数据
let forecastData = [
  {typhoonID: '2305', datetime: '2023-07-28 14:00:00', longitude: 118.3, latitude: 25.2, pressure: 950, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-28 20:00:00', longitude: 118, latitude: 26.5, pressure: 980, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-29 02:00:00', longitude: 117.5, latitude: 28.2, pressure: 992, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-29 08:00:00', longitude: 116.7, latitude: 30.7, pressure: 998, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-29 20:00:00', longitude: 116, latitude: 32.6, pressure: 998, grade: 'SuperTY'},
  {typhoonID: '2305', datetime: '2023-07-30 08:00:00', longitude: 115.8, latitude: 34.3, pressure: 998, grade: 'SuperTY'}
]

// 先将以上数据处理为目标数据格式后全部放入lineFeatures和pointFeatures。做动画时,
// 在定时器中,将lineFeatures和pointFeatures中的数据 每次 分别加入animationLineFeature和animationPointFeature

// 线路feature(包含实况和预测)
let lineFeatures = []
// 台风点feature(包含实况和预测)
let pointFeatures = []
// 动画-台风点feature(包含实况和预测)
let animationPointFeature = []
// 动画-台风路线feature(包含实况和预测)
let animationLineFeature = []

绘制台风路线并添加动画

目标lineFeature数据格式

// 渲染台风路线
function renderLine() {
  handleLineData()

  // 路径geojson
  let geojsonLine = {
    type: 'FeatureCollection',
    features: animationLineFeature
  }
  map.addSource('tf_live_path', {
    type: 'geojson',
    data: geojsonLine
  })

  animationLine(geojsonLine)

  map.addLayer({
    id: 'tf_live_path',
    type: 'line',
    source: 'tf_live_path',
    paint: {
      'line-color': '#ccc', // 线颜色
      'line-width': 3, // 线宽
      // 根据properties中type字段 forecastve设置为实线 其他设置为虚线
      'line-dasharray': ['match', ['get', 'type'], 'forecastve', ['literal', [1, 1]], ['literal', [1, 0]]]
    }
  })
}

// 处理台风路线数据
function handleLineData() {
  for (let i = 0; i < liveData.length; i++) {
    let firstCoordinates
    let secondCoordinates
    let type
    if (i === liveData.length - 1) {
      firstCoordinates = [liveData[liveData.length - 1].longitude, liveData[liveData.length - 1].latitude]
      secondCoordinates = [forecastData[0].longitude, forecastData[0].latitude]
      type = 'forecastve'
    } else {
      firstCoordinates = [liveData[i].longitude, liveData[i].latitude]
      secondCoordinates = [liveData[i + 1].longitude, liveData[i + 1].latitude]
      type = 'live'
    }
    let feature = {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: [firstCoordinates, secondCoordinates]
      },
      properties: {
        type: type
      }
    }
    lineFeatures.push(feature)
  }
  for (let i = 0; i < forecastData.length - 1; i++) {
    let feature = {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: [
          [forecastData[i].longitude, forecastData[i].latitude],
          [forecastData[i + 1].longitude, forecastData[i + 1].latitude]
        ]
      },
      properties: {
        type: 'forecastve'
      }
    }
    lineFeatures.push(feature)
  }
  // console.log('lineFeatures:', lineFeatures)
}

// 台风路线动画
function animationLine(geojsonLine) {
  let lineIndex = 0
  let lineTimer = setInterval(() => {
    if (lineIndex === lineFeatures.length - 1) clearInterval(lineTimer)
    animationLineFeature.push(lineFeatures[lineIndex])
    if (lineIndex === liveData.length) {
      // renderIcon()
      // 渲染台风眼icon并旋转
      rotateIcon()
    }
    lineIndex++
    map.getSource('tf_live_path').setData(geojsonLine)
  }, 200)
}

绘制台风点位并添加动画

pointFeatures目标数据结构

// 渲染台风点
function renderPoint() {
  handlePointData()

  let geojsonPoint = {
    type: 'FeatureCollection',
    features: animationPointFeature
  }

  map.addSource('tf_live_point', {
    type: 'geojson',
    data: geojsonPoint
  })

  animationPoint(geojsonPoint)

  map.addLayer({
    id: 'tf_live_point',
    type: 'circle',
    source: 'tf_live_point',
    paint: {
      'circle-color': [
        'case',
        ['==', ['get', 'grade'], 'TD'],
        tfColor.get('TD'),
        ['==', ['get', 'grade'], 'TS'],
        '#fb7cff',
        ['==', ['get', 'grade'], 'STS'],
        '#fb7cff',
        ['==', ['get', 'grade'], 'TY'],
        tfColor.get('TY'),
        ['==', ['get', 'grade'], 'STY'],
        tfColor.get('STY'),
        ['==', ['get', 'grade'], 'SuperTY'],
        tfColor.get('SuperTY'),
        '#FF0000'
      ]
    }
  })
}

// 处理台风点数据
function handlePointData() {
  // 将实况点位加入pointFeatures
  for (let i = 0; i < liveData.length; i++) {
    let feature = {
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [liveData[i].longitude, liveData[i].latitude]
      },
      properties: {
        type: 'live',
        grade: liveData[i].grade
      }
    }
    pointFeatures.push(feature)
  }
  // 将预测点位加入pointFeatures
  for (let i = 0; i < forecastData.length; i++) {
    let feature = {
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [forecastData[i].longitude, forecastData[i].latitude]
      },
      properties: {
        type: 'forecastve',
        grade: forecastData[i].grade
      }
    }
    pointFeatures.push(feature)
  }
  console.log('pointFeatures:', pointFeatures)

  // 动画先加载第一个点
  animationPointFeature.push({
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: [liveData[0].longitude, liveData[0].latitude]
    },
    properties: {
      type: 'live',
      grade: liveData[0].grade
    }
  })
}

// 台风点动画
function animationPoint(geojsonPoint) {
  let i = 1
  let timer = setInterval(() => {
    if (i === pointFeatures.length - 1) clearInterval(timer)
    animationPointFeature.push(pointFeatures[i])
    i++
    map.getSource('tf_live_point').setData(geojsonPoint)
  }, 200)
}

绘制台风眼图标并旋转

具体思路我不清楚,代码就在下面了,可以直接复制

function rotateIcon() {
  map.addImage('typhoon', createRotateIconByStaticDiagram('/src/assets/img/typhoon.png'))

  map.addSource('rotate', {
    type: 'geojson',
    data: {
      type: 'FeatureCollection',
      features: [
        {
          type: 'Feature',
          properties: {},
          geometry: {
            type: 'Point',
            coordinates: [liveData[liveData.length - 1].longitude, liveData[liveData.length - 1].latitude]
          }
        }
      ]
    }
  })

  map.addLayer({
    id: 'rotate',
    type: 'symbol',
    source: 'rotate',
    layout: {
      'icon-image': 'typhoon',
      'icon-size': 0.3,
      'icon-offset': [0, 0],
      'icon-rotation-alignment': 'viewport',
      'icon-allow-overlap': true,
      'icon-ignore-placement': true
    }
  })
}

function createRotateIconByStaticDiagram(url) {
  const size = 100
  return {
    width: size,
    height: size,
    data: new Uint8Array(size * size * 4),
    // context: null,
    onAdd: function () {
      const canvas = document.createElement('canvas')
      canvas.width = this.width
      canvas.height = this.height
      this.context = canvas.getContext('2d')
    },
    render: function () {
      const context = this.context
      context.clearRect(0, 0, this.width, this.height)
      const img = new Image()
      img.src = url
      context.drawImage(img, 0, 0)
      img.onload = function () {
        context.clearRect(0, 0, size, size)
        context.translate(50, 50)
        context.rotate(Math.PI / 45)
        context.translate(-50, -50)
        context.drawImage(img, 0, 0)
      }
      this.data = context.getImageData(0, 0, this.width, this.height).data
      map.triggerRepaint()
      return true
    }
  }
}

执行下面两个函数即可

  // 渲染台风路线
  renderLine()

  // 渲染台风点
  renderPoint()

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值