文章目录

  • 前言
  • 效果
  • 封装后的代码
  • 在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. python series转int_分享丨推荐一个 Python 手绘图形库
  2. C语言再学习 -- 时间函数
  3. 你可能不知道的java、python、JavaScript以及jquary循环语句的区别
  4. EntityFramework Core查询数据基本本质
  5. 枚举转中文,通过反射方法与描述的方式获取
  6. Coding:文件网盘高速直链下载无限空间
  7. 职场伦语-感悟职场,分享心得
  8. 你有遇到过最没良心的人吗?
  9. swiper.js插件的使用
  10. 冷知识 —— 计算机科学及编程
  11. 如何使用VideoProc将MKV转换为MP4?
  12. java 解析栅格数据_使用Rasterio读取栅格数据的实例讲解
  13. 题目399-整除个数(满满的套路)
  14. Fortran 注释符号
  15. Matconvnet测试
  16. 数据可视化--实验三:空间可视化
  17. 什么是外汇EA呢?工作的原理又是什么呢?送给不懂外汇EA
  18. [NOIP模拟测试30]题解
  19. java声明一个双精度型数组_java-day4-多种语言的数组创建
  20. 一天上手Aurora 8B/10B IP核(5)----从Framing接口的官方例程学起

热门文章

  1. sogou地图改版了
  2. trunc mysql_详解Oracle常用函数Trunc
  3. 使用Unity开发Android的几种调试方法
  4. 嵌入式入门-32位单片机简介
  5. 中国10大最令人遗憾的闪客
  6. 不怕迷茫焦虑,四招让你自律
  7. 【1】python-opencv3教程:图像数字化(图像灰度化,彩色图像通道的提取等)
  8. MacOSX VMware Fusion 虚拟机黑屏的解决方案
  9. python导入数据库怎么导入_python3 导入 数据库
  10. 如何批量提取文件名到word?