文章目录

  • 前言
  • 效果
  • 封装后的代码
  • 在mapbox中使用
  • 总结

前言

本文参考网上的例子,封装一个 mapbox 的轨迹回放图层,具体做的工作为:

  • 将轨迹回放图层封装为一个类,可以传入自己的图标图片
  • 将常用的方法暴露出来,并做了一些改进

参考:mapboxgl实现带箭头轨迹线


效果

封装后的代码

import * as turf from '@turf/turf'
const svgXML =`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> <path d="M529.6128 512L239.9232 222.4128 384.7168 77.5168 819.2 512 384.7168 946.4832 239.9232 801.5872z" p-id="9085" fill="#ffffff"></path> </svg>`
//给图片对象写入base64编码的svg流
const svgBase64 = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgXML)));export default class RouteReplay {/*** * @param {*} map mapbox实例对象* @param {*} routejson 路径geojson type = lineString* @param {*} iconImg 图标img*/constructor(map, routejson, iconImg) {this.map = mapthis._json = routejsonthis._img = iconImgthis._animated = falsethis._counter = 0this._steps = 0this._newRouteGeoJson = nullthis._timer = nullthis._layerList = ['routeLayer', 'realRouteLayer', 'arrowLayer', 'animatePointLayer']// 车辆行进中的路线this._realRouteGeoJson = {'type': 'FeatureCollection','features': [{'type': 'Feature','geometry': {'type': 'LineString','coordinates': []}}]}// 小车位置点的this._animatePointGeoJson = {'type': 'FeatureCollection','features': [{'type': 'Feature','properties': {},'geometry': {'type': 'Point','coordinates': []}}]}this._init()}_init() {let arrowIcon = new Image(20, 20)arrowIcon.src = svgBase64arrowIcon.onload = () => {//     console.log(this.map)this.map.addImage('arrowIcon', arrowIcon)this.map.loadImage(this._img, (error, carIcon) => {if (error) throw error;this.map.addImage('carIcon', carIcon);this._animatePointGeoJson.features[0].geometry.coordinates = this._json.features[0].geometry.coordinates[0]// 小车轨迹点jsonthis._newRouteGeoJson = this._resetRoute(this._json.features[0], 1000, 'kilometers')// 小车轨迹点json的点数量this._steps = this._newRouteGeoJson.geometry.coordinates.lengththis._addRoutelayer() // 添加轨迹线图层this._addRealRouteSource() // 添加实时轨迹线图层this._addArrowlayer() // 添加箭头图层this._addAnimatePointSource() // 添加动态点图层})}}_animate() {if (this._counter >= this._steps) {return}let startPnt, endPntif (this._counter == 0) { // 开始this._realRouteGeoJson.features[0].geometry.coordinates = []startPnt = this._newRouteGeoJson.geometry.coordinates[this._counter]endPnt = this._newRouteGeoJson.geometry.coordinates[this._counter + 1]} else if (this._counter !== 0) {startPnt = this._newRouteGeoJson.geometry.coordinates[this._counter - 1]endPnt = this._newRouteGeoJson.geometry.coordinates[this._counter]}// 计算角度,用于小车的指向角度this._animatePointGeoJson.features[0].properties.bearing = turf.bearing(turf.point(startPnt),turf.point(endPnt)) - 90;this._animatePointGeoJson.features[0].geometry.coordinates = this._newRouteGeoJson.geometry.coordinates[this._counter];this._realRouteGeoJson.features[0].geometry.coordinates.push(this._animatePointGeoJson.features[0].geometry.coordinates)// 小车的位置更新this.map.getSource('animatePointLayer').setData(this._animatePointGeoJson);// 已经走过的轨迹更新this.map.getSource('realRouteLayer').setData(this._realRouteGeoJson);if (this._animated) {this._timer = requestAnimationFrame(() => { this._animate() });}this._counter++;}_addRoutelayer() {console.log(222)this.map.addLayer({'id': 'routeLayer','type': 'line','source': {'type': 'geojson','lineMetrics': true,'data': this._json},'paint': {'line-width': 10,'line-opacity': 1,'line-color': '#7ec1ff',}});}_addRealRouteSource() {this.map.addLayer({'id': 'realRouteLayer','type': 'line','source': {'type': 'geojson','lineMetrics': true,'data': this._realRouteGeoJson},'paint': {'line-width': 10,'line-opacity': 1,'line-color': 'rgba(243,229,11,1)',}});}_addArrowlayer() {this.map.addLayer({'id': 'arrowLayer','type': 'symbol','source': {'type': 'geojson','data': this._json //轨迹geojson格式数据},'layout': {'symbol-placement': 'line','symbol-spacing': 50, // 图标间隔,默认为250'icon-image': 'arrowIcon', //箭头图标'icon-size': 0.5}});}// 添加动态点图层--小车_addAnimatePointSource() {this.map.addLayer({'id': 'animatePointLayer','type': 'symbol','source': {'type': 'geojson','data': this._animatePointGeoJson},'layout': {'icon-image': 'carIcon','icon-size': 0.5,'icon-rotate': ['get', 'bearing'],'icon-rotation-alignment': 'map','icon-allow-overlap': true,'icon-ignore-placement': true}});}_resetRoute(route, nstep, units) {const newroute = {'type': 'Feature','geometry': {'type': 'LineString','coordinates': []}}// 指定点集合的总路长const lineDistance = turf.lineDistance(route);// 每一段的平均距离const nDistance = lineDistance / nstep;const length =  this._json.features[0].geometry.coordinates.length;for (let i = 0; i < length - 1; i++) {let from = turf.point(route.geometry.coordinates[i]); // type 为 point的featurelet to = turf.point(route.geometry.coordinates[i + 1]);let lDistance = turf.distance(from, to, {  // 两个点之间的距离units: units});if (i == 0) { // 起始点直接推入newroute.geometry.coordinates.push(route.geometry.coordinates[0])}if (lDistance > nDistance) { // 两点距离大于每段值,将这条线继续分隔let rings = this._splitLine(from, to, lDistance, nDistance, units)newroute.geometry.coordinates = newroute.geometry.coordinates.concat(rings)} else { // 两点距离小于每次移动的距离,直接推入newroute.geometry.coordinates.push(route.geometry.coordinates[i + 1])}}return newroute}// 过长的两点轨迹点分段_splitLine(from, to, distance, splitLength, units) {var step = parseInt(distance / splitLength)const leftLength = distance - step * splitLengthconst rings = []const route = turf.lineString([from.geometry.coordinates, to.geometry.coordinates])for (let i = 1; i <= step; i++) {let nlength = i * splitLength// turf.alone返回沿着route<LineString>距离为nlength<number>的点let pnt = turf.along(route, nlength, {units: units});rings.push(pnt.geometry.coordinates)}if (leftLength > 0) {rings.push(to.geometry.coordinates)}return rings}start() {if (!this._animated) {this._animated = truethis._animate()}}pause() {this._animated = falsethis._animate()}end() {this._animated = falsethis._counter = 0this._animate()}remove() {window.cancelAnimationFrame(this._timer)this._layerList.map(layer => {// console.log(layer)if (this.map.getSource(layer)) {this.map.removeLayer(layer)this.map.removeSource(layer)}});}
}

在mapbox中使用

import React, { useRef, useEffect, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import * as turf from '@turf/turf'
import MapboxLanguage from '@mapbox/mapbox-gl-language';
import carImg from '../../img/car2.jpg'
import RouteReplay from '../libs/routeReplay'
import routeGeoJson from '../../testData/json/routeGeoJson.json';  // 基础图层
import 'antd/dist/antd.css';function App() {const mapContainerRef = useRef();const mapRef = useRef();const routeReplayRef = useRef();// 初始化基础图层useEffect(() => {mapboxgl.accessToken = 'token'mapRef.current = new mapboxgl.Map({center: [116.761, 39.452], // starting position [lng, lat]zoom: 10,// starting zoompitch: 60,style: 'mapbox://styles/mapbox/streets-v11',container: mapContainerRef.current,antialias: true,},);mapRef.current.addControl(new MapboxLanguage({ defaultLanguage: "zh-Hans" }))mapRef.current.on('load', (e) => {routeReplayRef.current = new RouteReplay(mapRef.current, routeGeoJson, carImg)});}, []);function startClick() {routeReplayRef.current.start()console.log(routeGeoJson)}function pauseClick() {routeReplayRef.current.pause()}function endClick() {routeReplayRef.current.end()}function removeClick() {routeReplayRef.current.remove()}return (<div style={{ display: 'flex' }}><divid="map-container"ref={mapContainerRef}style={{ height: '100vh', width: '100vw' }}/><div style={{ position: 'fixed', top: '0', right: '0' }}><button onClick={() => { startClick() }} style={{ marginRight: '10px' }}>开始</button><button onClick={() => { pauseClick() }} style={{ marginRight: '10px' }}>暂停</button><button onClick={() => { endClick() }} style={{ marginRight: '10px' }}>停止</button><button onClick={() => { removeClick() }} style={{ marginRight: '10px' }}>移除</button></div></div>);
}export default App;

总结

该功核心的思路在参考中都已经实现,笔者只不过在它的基础上进行了封装,代码结构还存在着优化点,若有侵权,联系可删~

mapbox-gl封装轨迹动画图层相关推荐

  1. [Mapbox GL]点的动画效果

    更新每帧的GeoJSON资源实现点的动画效果 <!DOCTYPE html> <html> <head><meta charset='utf-8' /> ...

  2. Mapbox GL JS介绍及使用

    Mapbox GL JS介绍及使用:(以web端基本交互实现为例) Mapbox GL JS 是一个 JavaScript 库,它使用 WebGL,以 vector tiles 和 Mapbox st ...

  3. Mapbox GL JS实现移动端H5实时多边形(涂鸦)绘制

    关于使用MapBox GL进行图形绘制,很多人都在使用mapbox-gl-draw.但是它只是封装了一些简单的点.线.面的绘制功能.最近的一个项目提出了要求在手机上用手指拖动实现多边形涂鸦绘制,要求实 ...

  4. 初识mapbox GL

    一.概述 最近由于项目的需求,借此机会对mapbox GL做了一个系统的学习,同时也对整个学习过程做一个记录,一方面留作自用,另一方面也希望看到此文的人在学习mapbox GL的时候,能够有所启发.有 ...

  5. 进阶mapbox GL之paint和filter

    概述 通过前面的文章初识mapbox GL我们对mapbox GL有了一个相对比较全面的认识,本节结合一些示例,重点讲述一下mapbox GL里面的filter和paint的用法. 说明 本文中的示例 ...

  6. Mapbox GL JS 表达式概述

    表达式(expressions)是Mapbox GL JS的一个高级功能,它为数据的添加和渲染展示提供了更多的灵活性. 表达式的功能包括: 数据驱动样式:根据一个或多个数据属性指定样式规则. 算术:对 ...

  7. vue项目中 使用百度地图 轨迹动画

    在上篇博客中,介绍了如何在vue项目中集成百度地图,这篇博客主要是说如何在vue项目中使用轨迹动画 在项目开发过程中,比如你需要实时的观察一个人的行走路线,行走过程.  这个时候我们就需要在地图上使用 ...

  8. GIS开发:mapbox gl几种底图的加载

    mapbox gl除了加载客户端可控制样式的底图,也能够加载常规的影像底图. 这里指发布的影像或者矢量地图切片,在线经常使用的有天地图,谷歌地图.高德百度等,底图的加载,mapbox gl中,主要是修 ...

  9. Mapbox GL可视化之热力图

    本篇使用Mapbox GL JS实现数据的热力图可视化,以截止到2020-03-01日的新冠疫情作为示例数据. 一 什么是热力图 热力图使用颜色的深浅表示数值的变化,从而表示数据的分布情况,这使得用户 ...

最新文章

  1. NET许可证及License
  2. 098 Validate Binary Search Tree 验证二叉搜索树
  3. 剑指 Offer 44. 数字序列中某一位的数字
  4. dfs中return回溯问题
  5. linux外接显示屏,关掉本身的笔记本电脑
  6. vue动态生成下拉框_解决vue动态下拉菜单 有数据未反应的问题
  7. java服务端集成极光消息推送--详细开发步骤
  8. 创造与魔法241服务器系统什么时候修好,《创造与魔法》数据互通,交流不愁 4月27日更新维护公告...
  9. [转载] Python 字典(Dictionary) get()方法
  10. Discuz = 7.2 SQL注入漏洞详情
  11. Linux系统编程 -- 多线程之基于环形队列的生产者与消费者模型
  12. centos配置maven环境
  13. mac的python换字体_Mac OSX Yosemite系统字体修改
  14. tp1900芯片对比7621a_TP无线路由器WDR7660千兆版,厉害了单芯片TP1900
  15. RegExp-2 【正则量词、属性、方法、使用技巧】
  16. mysql 本月老客户次月留存率_用mysql计算用户留存率
  17. spark Hash Shuffle内幕彻底解密
  18. flask----后续
  19. 计算机视觉之人脸识别学习(六)
  20. uni-app 打开第三方程序

热门文章

  1. 【操作系统】生产者消费者问题实现
  2. margin 无效的情形
  3. 流量控制算法——漏桶算法和令牌桶算法
  4. Nexus3 搭建私服 maven 仓库
  5. CSS @supports解析
  6. Spring Cloud初初初初初级教学
  7. vmware虚拟机中系统开机有的显示黑屏无法正常使用
  8. 单引号、双引号、不加引号和反引号
  9. 计算机兔同笼的方法,“鸡兔同笼”问题,4种不同的、有趣的解法,简单易懂...
  10. 网站备案靠谱吗_代理网站备案是怎么回事,代理网站备案靠谱吗? | 帮助信息-动天数据...