前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >mapboxGL轨迹展示与播放

mapboxGL轨迹展示与播放

作者头像
牛老师讲GIS
发布于 2021-12-21 13:26:15
发布于 2021-12-21 13:26:15
1.5K00
代码可运行
举报
运行总次数:0
代码可运行

概述

历史轨迹回放是GIS很常见的一个功能,本文结合turf.js实现轨迹的展示与播放动画。

效果

实现功能

  • 轨迹的展示;
  • 轨迹的方向的箭头展示;
  • 随着轨迹播放的小车,并调整方向与轨迹方向一致;
  • 已播放路径的展示;
  • 多条轨迹线的同时播放展示;

实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAMAAAC13D+jAAADAFBMVEWs1eL/0T7+zTv+lwD81FfXqSZNNA/84nXSixXJt5kjS2z/uCn8rBVxz+yn09/mtymmvMavxrX9sxn/zTLnxmrJ9v/+uzNarMOHqsb1vGb9ynuDwdK7ycn7t0r85WpMpLuHrLn/4kDFmiRUaYL9xDT/0THlgAH01XH9rSz/1ULHqWP723N3ucv+2D/7wWeyoVTbs1S02uWP1OzIp1OidDBNjKe46vyYtKjit2n/zSzmmzncvWlzTAf/3UFrtMgwg6+4rJb+ogGtkVr/swiSxtr/5j76xG7VsVOQrKxXXFnx0Wimwuola5T84Fztx5fglxw5PDPftVBXu9r9v0f/rgmWn5a3l1adbhP/0Tmftcbltljqu4WDdl3w6eGs5fmepJj2qSupiD2wvLN7pbXTtF//98f4pC321m+rwsT63Wv9+vZXam/Cq4zFsIz/2zVxe3X+xC1rwtqgdhvomjDtwXr/qwL/0mS/pGH31GPtvHn/0y3m07vByZXUqVf/0y+smGv/80X900r/1D293OY1JgrnqVnx49LM5Ov5uFT/0Cr0oi/69vP9/PpkWCxrmbr/1znz1XjTrk7YtVt1udPdul+64u6bn5D/5i4aJSj/z3i4hSfm/v/322ry///vnir/0CzbjSTVrWSw2d328ej933r2v2j/1C//0C/kvjC/kkGukkCBmJeu2OK+spbZ6vDT/f+hutbCfgrz3HXxrTPFlkvtkATzhwDnp03RzLvvs0d3kZ6SsKj/yh6MsrmRqbD/2Y7zqSL/5n3/ryb/1CvlwmOOu8D5wlpoodNqvtz/+VCoiE1ZveCgilVovfO/j0SZj3KEm7VRscpWXmjOq1JNZFzv0njxxST3zSj/wCT/zSLekzDPy8T/yWv+4E56gXSjo5KrsJW4qI6jz9/swpDxu275zJf33LiWwszqy6Hx06zz0nD/pwr/7UsKDhADHDHrw00+FQD9yDcROFiXbSrtz2ysjizmsWuzkiW7lzRghaZkkKRgZ1dwm7322Wdgi4X///+RNRZLAAABAHRSTlP///8AU/cHJQAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAB1pVFh0U29mdHdhcmUAAAAAAEFkb2JlIEltYWdlUmVhZHkGrQKXAAADD2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS41LWMwMTQgNzkuMTUxNDgxLCAyMDEzLzAzLzEzLTEyOjA5OjE1ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2NUZGMDA3MTYwREExMUVDOURGOEIxQTMwNjhFRTA1OCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2NUZGMDA3MDYwREExMUVDOURGOEIxQTMwNjhFRTA1OCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSIxREI5NjEzNURBODA0MkYzMzk5NDA4NzZFN0YzQkM2RCIgc3RSZWY6ZG9jdW1lbnRJRD0iMURCOTYxMzVEQTgwNDJGMzM5OTQwODc2RTdGM0JDNkQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6ffRDqAAADEklEQVQ4jWP4jx/clstCF2L4/z/rxZMSKT09PckylqV6ICB5Z2bwsaVA4CZ5J4r//ZOZM0sqS0AgunoxWEvb1lLn0ln15eXlNqe0gGS5lkWijdZCIKi1eFI7q4Vt68aNN4SEhHh4XjtKRb8Eaune+nrlOZ5ZvxQUFGZkH00GAQHPtwICkZGRGySUL3l63vP4M4PflgkIPnKLpbY++89Q+dpfouYai/vEie4ik4DEJPdJKSnWl42NjbVYe3fJNMeZma3nCnf9+PEjE5Omqm6jWhZDcciWDMX+L1fVweCV9hLtgweXpKunp6dP6NdfP3cqHwOfqLJNAyMj00cVlY8qeY5+DPNe18uoRqgergKDZVWdVZadnctA7MPTE71nr+FjYBD9yebaxMioqanJVMCy8xtDF3PoGuEIRiwgwvXrhtmGDHx8ZhIBEboQsWWbcrYzpG3dx4ddC6Pr98i57e3NDFNdAvhtIUKqc3KsgFoUFglHaGpi06JkGufyaF3z1EcB/IpwLcVALRKLhJtUVLDoUVTaFScur/PIkCGAFablb84CiJZ+MTEsWlyVfom2M9jZXbwI17I8MxekZS/QYbrYHfaTV8fQkGGNHdxhQC23QN5fY4nD+0p9RnZ20jouCO/PZ+e0AgUyzhD7YW8kLS8uftEeFsjCe5YAtcxzPP9cNaJuWR0SmF9VNX9+Z+f0VfZGk4FaTvuwKTZFRAhbMv31lYtqZZgXErY3r9/rixcS0E5P1wYCW/2zhXbi4uK8XDYNTI2NLOxy7L6KwATzrdT/eM31iqjAwECvI+4GIDBRZMKkKVOmWPP3/tE5EXQ6iPdT0UldX7m7cnerbjZu62ZYzPz6jBOP0mpzc/OAfMHdICD4u0UwCQgyLijnZ1/5Z2r/bsUbzSbVjwU3hFJlHwOzWDVzqbOQyer4+GlsMQ+mAcH9tTFsD9c6AAFnrtPnD2wcReEF3Nzchw5p8Ox/Gg3OyLGVO2VlNTQ00sTEEjRAIIE7+MCBBDBQ2azvLsZU1tHhBgJLH7dB8j4C9GCydsgtRi8uAEXmog5wB7NZAAAAAElFTkSuQmCC'
const arrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAnElEQVQ4T63TsQ0CMQyF4f/NgMQQ0CBR0FIx190cFIiWhhFoKdgEiRUeSoF0gO8cjkub+Evs2OLPpc9422dgBRwlNZn/BtjeApdOUJsh0QuuwKYWiYAFcAKWHaSR1EbpfAHlkO0ICdMJgV+QXmAAmUu6v9IZA8wkPVKgtg7TF7H25t4UbN+A9ahGmqqVD8AO2GdzUF45+I3ZJJb9JxbwRhEhB66xAAAAAElFTkSuQmCC'

class AnimationRoute {
  constructor(map, json, play = true) {
    this._map = map
    this._json = json
    this._play = play
    this.init()
  }

  init() {
    const that = this
    that._index = 0
    that._count = 1500
    that._step = turf.length(that._json) / that._count
    that._flag = 0
    that._playId = 'play-' + Date.now()

    // 添加路径图层
    that._map.addSource(that._playId, {
      type: 'geojson',
      data: that._json
    })
    that._map.addLayer({
      id: that._playId,
      type: 'line',
      source: that._playId,
      'layout': {
        'line-cap': 'round',
        'line-join': 'round'
      },
      'paint': {
        'line-color': '#aaaaaa',
        'line-width': 10
      }
    })
    // 添加已播放路径
    that._map.addSource(that._playId + '-played', {
      type: 'geojson',
      data: that._json
    })
    that._map.addLayer({
      id: that._playId + '-played',
      type: 'line',
      source: that._playId + '-played',
      'layout': {
        'line-cap': 'round',
        'line-join': 'round'
      },
      'paint': {
        'line-color': '#09801a',
        'line-width': 10
      }
    })

    // 添加路径上的箭头
    that._map.loadImage(arrow, function(error, image) {
      if (error) throw error
      that._map.addImage(that._playId + '-arrow', image)
      that._map.addLayer({
        'id': that._playId + '-arrow',
        'source': that._playId,
        'type': 'symbol',
        'layout': {
          'symbol-placement': 'line',
          'symbol-spacing': 50,
          'icon-image': that._playId + '-arrow',
          'icon-size': 0.6,
          'icon-allow-overlap': true
        }
      })
    })

    // 添加动态图标
    that._map.loadImage(icon, function(error, image) {
      if (error) throw error
      that._map.addImage(that._playId + '-icon', image)
      that._map.addSource(that._playId + '-point', {
        'type': 'geojson',
        'data': that._getDataByCoords()
      })
      that._map.addLayer({
        'id': that._playId + '-point',
        'source': that._playId + '-point',
        'type': 'symbol',
        'layout': {
          'icon-image': that._playId + '-icon',
          'icon-size': 0.75,
          'icon-allow-overlap': true,
          'icon-rotation-alignment': 'map',
          'icon-pitch-alignment': 'map',
          'icon-rotate': 50
        }
      })
      that._animatePath()
    })
  }

  _animatePath() {
    if(this._index > this._count) {
      window.cancelAnimationFrame(this._flag)
    } else {
      const coords = turf.along(this._json, this._step * this._index).geometry.coordinates
      // 已播放的线
      const start = turf.along(this._json, 0).geometry.coordinates
      this._map.getSource(this._playId + '-played').setData(turf.lineSlice(start, coords, this._json))

      // 车的图标位置
      this._map.getSource(this._playId + '-point').setData(this._getDataByCoords(coords))
      // 计算旋转角度
      const nextIndex = this._index === this._count ? this._count - 1 : this._index + 1
      const coordsNext = turf.along(this._json, this._step * nextIndex).geometry.coordinates
      let angle = turf.bearing(
        turf.point(coords),
        turf.point(coordsNext)
      ) - 90
      if(this._index === this._count) angle += 180
      this._map.setLayoutProperty(this._playId + '-point', 'icon-rotate', angle)

      this._index++
      if(this._play) this._flag = requestAnimationFrame(() => {
        this._animatePath()
      })
    }
  }

  _getDataByCoords(coords) {
    if(!coords || coords.length !== 2) return null
    return turf.point(coords, {
      'label': this._formatDistance(this._step * this._index)
    })
  }

  _formatDistance(dis) {
    if(dis < 1) {
      dis = dis * 1000
      return dis.toFixed(0) + '米'
    } else {
      return dis.toFixed(2) + '千米'
    }
  }

  destory() {
    window.cancelAnimationFrame(this._flag)
    if(this._map.getSource(this._playId + '-point')) {
      this._map.removeLayer(this._playId + '-point')
      // this._map.removeLayer(this._playId + '-label')
      this._map.removeSource(this._playId + '-point')
    }
    if(this._map.getSource(this._playId)) {
      this._map.removeLayer(this._playId)
      this._map.removeSource(this._playId)
    }
  }
}

测试调用代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const route1 = {'type':'Feature','properties':{},'geometry':{'type':'LineString','coordinates':[[106.669,22.5785],[106.6374,22.5974],[106.6206,22.608],[106.6037,22.5553],[106.5784,22.4858],[106.5595,22.4373],[106.5637,22.3804],[106.5827,22.3298],[106.6543,22.313],[106.6859,22.2561],[106.7006,22.195],[106.688,22.1613],[106.6943,22.0897],[106.6964,22.018],[106.6838,21.9717],[106.7386,21.9864],[106.7554,22.0138],[106.8334,21.9759],[106.9008,21.9738],[106.9261,21.9422],[106.9767,21.9316],[107.0209,21.9485],[107.0609,21.919],[107.0125,21.8705],[107.0104,21.8305],[107.0609,21.8031],[107.1031,21.7862],[107.1473,21.7483],[107.2063,21.7125],[107.2611,21.6935],[107.2927,21.7251]]}}
new AnimationRoute(map, route1)

说明:如果为多个轨迹同时展示,多次调用new AnimationRoute即可。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/12/20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
mapboxGL中的航线动画
牛老师讲GIS
2024/05/24
2300
mapboxGL中的航线动画
使用高德API和MapboxGL实现路径规划并语音播报
本文使用高德API实现位置查询和路径规划,使用MapboxGL完成地图交互与界面展示,并使用Web Speech API实现行驶中路线的实时语音播报。
牛老师讲GIS
2024/11/11
1970
使用高德API和MapboxGL实现路径规划并语音播报
仿高德地图实现输入起点和终点规划路径并可切换
本文结合高德API和MapboxGL,仿照手机版高德地图实现用户输入起点和终点位置并模糊搜索选择具体位置,根据选择的起始点位置规划路径,并实现多条路径的切换展示。
牛老师讲GIS
2025/05/31
890
仿高德地图实现输入起点和终点规划路径并可切换
leaflet和mapboxGL中网格聚类的实现
前面的文章openlayers中网格聚类的实现发出来后,有好多童鞋问到了其他框架的实现,本文就大家看看在leaflet和mapboxGL中如何实现。
牛老师讲GIS
2023/06/10
5370
leaflet和mapboxGL中网格聚类的实现
turf.js实现多边形分割
概述 在做编辑的时候,难免会遇到多边形的分割问题,本文用turf.js实现此功能。 效果 实现思路 实现代码 function polygonCut(poly, line, tolerance = .001, toleranceType ='kilometers') { // 1. 条件判断 if (poly.geometry === void 0 || poly.geometry.type !== 'Polygon') throw ('传入的必须为polygon'); if
牛老师讲GIS
2021/12/06
2.6K0
turf.js实现多边形分割
mapboxGL中楼层与室内地图的结合展示
质量不够,数量来凑,没错,本文就是来凑数的。前面的几篇文章实现了楼栋与楼层单体化的展示、室内地图的展示,本文结合前面的几篇文章,做一个综合的展示效果。
牛老师讲GIS
2023/07/31
4170
mapboxGL中楼层与室内地图的结合展示
mapboxGL中山体背景+边界阴影的一种实现方案
很多地图可视化的项目中有要求实现如下的效果,本文借助QGIS、PS和turf.js,在mapboxGL中实现山体背景+边界阴影的效果。
牛老师讲GIS
2024/05/27
2660
mapboxGL中山体背景+边界阴影的一种实现方案
一种基于高德Web API实现沿路画面的实现
牛老师讲GIS
2024/12/30
1620
一种基于高德Web API实现沿路画面的实现
websocket实现GPS数据的实时推送与地图的展示(优化)
前两天,发布了一片文章websocket实现GPS数据的实时推送与地图的展示,文章发出后引来了不少读者的关注,也有不少读者要求做进步一优化。本文应大家的要求,对上文的内容做一个优化,优化地方包括:
牛老师讲GIS
2022/04/28
7880
websocket实现GPS数据的实时推送与地图的展示(优化)
mapboxGL中室内地图的实现
室内地图的实现最大的难点在于数据的收集,常见的方式有:1.基于施工CAD图纸转换;2.基于商场室内导视图进行绘制。本文的数据是截图高德地图SDK室内地图,并在QGIS中叠加高德地图进行配准后进行,对配准后的图像进行数字化而得到的。获取到数据后将数据叠加到mapboxGL中进行展示,并根据数据添加了楼层控制控件。
牛老师讲GIS
2024/12/30
2160
mapboxGL中室内地图的实现
OL4结合turf.js实现等值线
本文分享一个结合turf.js实现前端等值线的生成,并对结果做了圆滑处理,并在OL4中进行展示。
牛老师讲GIS
2018/10/23
3K1
OL4结合turf.js实现等值线
openlayers4+中台风路径播放优化
本文在以前风圈生成文章的基础上,将openlayers4+中台风路径的播放展示做了优化,并进行了封装。
牛老师讲GIS
2020/02/27
1.6K0
openlayers4+中台风路径播放优化
mapbox GL台风路径的播放实现
前面的文章中写了基于openlayers4的台风路径播放,最近用到mapbox GL,也要实现相似的功能,网上找了好久都没有找到,于是就放弃了“拿来主义”的想法,只能自己动手了。经过一下午的努力,终于有了一个雏形,在此分享出来,希望对你有用!
牛老师讲GIS
2020/03/23
1.7K0
mapbox GL台风路径的播放实现
mapboxGL中楼层的的展示与单体化
前面有文章说到了室内地图的展示,在本文讲述如何在mapboxGL中如何实现楼层的展示与单体化选中效果。
牛老师讲GIS
2023/07/25
4361
mapboxGL中楼层的的展示与单体化
前端解析csv或geojson文件并展示
本位通过FileReader实现csv或geojson文件的前端解析并在地图上展示。
牛老师讲GIS
2023/07/11
6980
前端解析csv或geojson文件并展示
mapboxGL中楼层的的展示与单体化(二)
前文讲述如何在mapboxGL中如何实现楼层的展示与单体化选中效果,本文在前文的基础上优化高亮方式,实现点击楼层时弹出楼层。
牛老师讲GIS
2023/07/25
2650
mapboxGL中楼层的的展示与单体化(二)
mapboxGL和高德API结合实现路径规划
高德地图路径规划API说明如上图,有行走、公交、驾车等多种路径,本文以行走为例来说明。
牛老师讲GIS
2020/04/08
1.9K0
websocket实现GPS数据的实施推送与地图的展示
实时展示GPS的数据是webgis中比较常见的一个需求,本文通过nodejs-websocket模拟GPS数据的更新,实现GPS数据的实施推送与地图数据的展示。
牛老师讲GIS
2022/04/28
8200
跟牛老师一起学WEBGIS——WEBGIS实现(绘制点)
为了更加灵活,本文讲述sprite图标的绘制方式。sprite图标参考了mapboxGL的实现方式,分为两个文件:.png和.json,示例图标如下:
牛老师讲GIS
2020/12/07
7460
跟牛老师一起学WEBGIS——WEBGIS实现(绘制点)
mapboxGL测量实现
讲真,MapboxGL里面虽然有测量的功能,但是不太好用,于是就萌生了自己实现的方法。本文几个turf.js来说说mapboxGL中测量的实现。
牛老师讲GIS
2020/04/22
1K0
mapboxGL测量实现
相关推荐
mapboxGL中的航线动画
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验