• 数据可视化【原创】vue+arcgis+threejs 实现流光边界线效果


    本文适合对vue,arcgis4.x,threejs,ES6较熟悉的人群食用。

    效果图:

     

    素材:

     

    主要思路:

    先用arcgis externalRenderers封装了一个ExternalRendererLayer,在里面把arcgis和threejs的context关联,然后再写个子类继承它,这部分类容在上一个帖子里面有讲过。

    子类AreaLayer继承它,并在里面实现绘制流光边界线的方法,我这里其实就是绘制城市区域的边界线。尝试过直线LineCurve3,三维二次贝塞尔曲线QuadraticBezierCurve3,三维三次贝塞尔曲线CubicBezierCurve3,结果感觉差不多=_=,所以最后还是用CatmullRomCurve3这个来构建管道,这个类使用也比其他的方便。

    1:创建一个基于图片的材质

    复制代码
     1 const lineImg = require('../../../../public/static/img/line.png')
     2         let lineTexture = new THREE.TextureLoader().load(lineImg)
     3         lineTexture.wrapS = lineTexture.wrapT = THREE.RepeatWrapping; //每个都重复
     4         lineTexture.repeat.set(1, 1)
     5         lineTexture.needsUpdate = true
     6         
     7         let lineMaterial = new THREE.MeshBasicMaterial({
     8             map: lineTexture,
     9             side: THREE.DoubleSide,
    10             transparent: true
    11         })
    复制代码

    2:处理坐标转换数据

    复制代码
    1 let linePoints = []
    2         for(let i = 0; i < pointList.length; i++) {
    3             var item = pointList[i];
    4             var renderLinePoints = this.lngLatToXY(this.view, [item[0], item[1], 10]);
    5             var vector3List = renderLinePoints.vector3List;
    6             
    7             linePoints.push(new THREE.Vector3(vector3List.x, vector3List.y, vector3List.z));
    8         }
    复制代码

    3:构建TubeGeometry,创建Mesh

    1 const curvePath = new THREE.CatmullRomCurve3(linePoints) // 曲线路径
    2         
    3         let geometry = new THREE.TubeGeometry(curvePath, 64, 30, 8, true )
    4         let lineMesh = new THREE.Mesh(geometry, lineMaterial);

    4:最后再updateModels里面更新贴图的位置(其实就是render事件)。

    复制代码
    1 updateModels(context) {
    2         super.updateModels(context);
    3         
    4         if (this.textures.length > 0) {
    5             this.textures.forEach(texture => {
    6                 if (texture) texture.offset.x -= 0.01;
    7             })
    8         }
    9     }
    复制代码

     

    ExternalRendererLayer:

      1 import * as THREE from 'three'
      2 import Stats from 'three/examples/jsm/libs/stats.module.js'
      3 import * as webMercatorUtils from "@arcgis/core/geometry/support/webMercatorUtils"
      4 import * as externalRenderers from "@arcgis/core/views/3d/externalRenderers"
      5 
      6 export default class ExternalRendererLayer {
      7     constructor({
      8         view,
      9         options
     10     }) {
     11         this.view = view
     12         this.options = options
     13 
     14         this.objects = []
     15         this.scene = null
     16         this.camera = null
     17         this.renderer = null
     18         
     19         this.setup();
     20     }
     21     
     22     setup() {
     23         if (process.env.NODE_ENV !== "production") {
     24             const sid = setTimeout(() => {
     25                 clearTimeout(sid)
     26                 //构建帧率查看器
     27                 let stats = new Stats()
     28                 stats.setMode(0)
     29                 stats.domElement.style.position = 'absolute'
     30                 stats.domElement.style.left = '0px'
     31                 stats.domElement.style.top = '0px'
     32                 document.body.appendChild(stats.domElement)
     33                 function render() {
     34                   stats.update()
     35                   requestAnimationFrame(render)
     36                 }
     37                 render()
     38             }, 5000)
     39         }
     40     }
     41 
     42     apply() {
     43         let myExternalRenderer = {
     44             setup: context => {
     45                 this.createSetup(context)
     46             },
     47             render: context => {
     48                 this.createRender(context)
     49             }
     50         }
     51         
     52         externalRenderers.add(this.view, myExternalRenderer);
     53     }
     54 
     55     createSetup(context) {
     56         this.scene = new THREE.Scene(); // 场景
     57         this.camera = new THREE.PerspectiveCamera(); // 相机
     58 
     59         this.setLight();
     60 
     61         // 添加坐标轴辅助工具
     62         const axesHelper = new THREE.AxesHelper(10000000);
     63         this.scene.Helpers = axesHelper;
     64         this.scene.add(axesHelper);
     65 
     66         this.renderer = new THREE.WebGLRenderer({
     67             context: context.gl, // 可用于将渲染器附加到已有的渲染环境(RenderingContext)中
     68             premultipliedAlpha: false, // renderer是否假设颜色有 premultiplied alpha. 默认为true
     69             // antialias: true
     70             // logarithmicDepthBuffer: false
     71             // logarithmicDepthBuffer: true 
     72         });
     73         this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比。通常用于避免HiDPI设备上绘图模糊
     74         this.renderer.setViewport(0, 0, this.view.width, this.view.height); // 视口大小设置
     75         
     76         // 防止Three.js清除ArcGIS JS API提供的缓冲区。
     77         this.renderer.autoClearDepth = false; // 定义renderer是否清除深度缓存
     78         this.renderer.autoClearStencil = false; // 定义renderer是否清除模板缓存
     79         this.renderer.autoClearColor = false; // 定义renderer是否清除颜色缓存
     80         // this.renderer.autoClear = false;
     81         
     82         // ArcGIS JS API渲染自定义离屏缓冲区,而不是默认的帧缓冲区。
     83         // 我们必须将这段代码注入到three.js运行时中,以便绑定这些缓冲区而不是默认的缓冲区。
     84         const originalSetRenderTarget = this.renderer.setRenderTarget.bind(
     85             this.renderer
     86         );
     87         this.renderer.setRenderTarget = target => {
     88             originalSetRenderTarget(target);
     89             if (target == null) {
     90                 // 绑定外部渲染器应该渲染到的颜色和深度缓冲区
     91                 context.bindRenderTarget();
     92             }
     93         };
     94         
     95         this.addModels(context);
     96 
     97         context.resetWebGLState();
     98     }
     99 
    100     createRender(context) {
    101         const cam = context.camera;
    102         this.camera.position.set(cam.eye[0], cam.eye[1], cam.eye[2]);
    103         this.camera.up.set(cam.up[0], cam.up[1], cam.up[2]);
    104         this.camera.lookAt(
    105             new THREE.Vector3(cam.center[0], cam.center[1], cam.center[2])
    106         );
    107         // this.camera.near = 1;
    108         // this.camera.far = 100;
    109 
    110         // 投影矩阵可以直接复制
    111         this.camera.projectionMatrix.fromArray(cam.projectionMatrix);
    112         
    113         this.updateModels(context);
    114 
    115         this.renderer.state.reset();
    116 
    117         context.bindRenderTarget();
    118 
    119         this.renderer.render(this.scene, this.camera);
    120 
    121         // 请求重绘视图。
    122         externalRenderers.requestRender(this.view);
    123 
    124         // cleanup
    125         context.resetWebGLState();
    126     }
    127     
    128     //经纬度坐标转成三维空间坐标
    129     lngLatToXY(view, points) {
    130     
    131         let vector3List; // 顶点数组
    132     
    133         let pointXYs;
    134     
    135     
    136         // 计算顶点
    137         let transform = new THREE.Matrix4(); // 变换矩阵
    138         let transformation = new Array(16);
    139     
    140         // 将经纬度坐标转换为xy值\
    141         let pointXY = webMercatorUtils.lngLatToXY(points[0], points[1]);
    142     
    143         // 先转换高度为0的点
    144         transform.fromArray(
    145             externalRenderers.renderCoordinateTransformAt(
    146                 view,
    147                 [pointXY[0], pointXY[1], points[
    148                     2]], // 坐标在地面上的点[x值, y值, 高度值]
    149                 view.spatialReference,
    150                 transformation
    151             )
    152         );
    153     
    154         pointXYs = pointXY;
    155     
    156         vector3List =
    157             new THREE.Vector3(
    158                 transform.elements[12],
    159                 transform.elements[13],
    160                 transform.elements[14]
    161             )
    162     
    163         return {
    164             vector3List: vector3List,
    165             pointXYs: pointXYs
    166         };
    167     }
    168     
    169     setLight() {
    170         console.log('setLight')
    171         let ambient = new THREE.AmbientLight(0xffffff, 0.7);
    172         this.scene.add(ambient);
    173         let directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);
    174         directionalLight.position.set(100, 300, 200);
    175         this.scene.add(directionalLight);
    176     }
    177     
    178     addModels(context) {
    179         console.log('addModels')
    180     }
    181     
    182     updateModels(context) {
    183         // console.log('updateModels')
    184     }
    185     
    186 }
    View Code

    AreaLayer:源码中mapx.queryTask是封装了arcgis的query查询,这个可以替换掉,我只是要接收返回的rings数组,自行构建静态数据也行

      1 import * as THREE from 'three'
      2 import ExternalRendererLayer from './ExternalRendererLayer.js'
      3 import Graphic from "@arcgis/core/Graphic";
      4 import SpatialReference from '@arcgis/core/geometry/SpatialReference'
      5 import * as externalRenderers from "@arcgis/core/views/3d/externalRenderers"
      6 
      7 import mapx from '@/utils/mapUtils.js';
      8 
      9 export default class AreaLayer extends ExternalRendererLayer {
     10     constructor({
     11         view,
     12         options
     13     }) {
     14         super({
     15             view,
     16             options
     17         })
     18     }
     19     
     20     setup() {
     21         super.setup()
     22         
     23         this.textures = []
     24     }
     25 
     26     addModels(context) {
     27         // super.addModels(context)
     28         // =====================mesh加载=================================//
     29         const url = config.mapservice[1].base_url + config.mapservice[1].jd_url;
     30         // const url = 'http://10.100.0.132:6080/arcgis/rest/services/wuchang_gim/gim_region/MapServer/2';
     31         mapx.queryTask(url, {
     32             where: '1=1',
     33             returnGeometry: true
     34         }).then(featureSet => {
     35             if (featureSet.length > 0) {
     36                 featureSet.forEach(feature => {
     37                     const polygon = feature.geometry;
     38                     const rings = polygon.rings;
     39                     rings.forEach(ring => {
     40                         this._addModel(ring);
     41                     })
     42                 })
     43             }
     44         }).catch(error => {
     45             console.log(error)
     46         })
     47     }
     48     
     49     _addModel(pointList) {
     50         const lineImg = require('../../../../public/static/img/line.png')
     51         let lineTexture = new THREE.TextureLoader().load(lineImg)
     52         lineTexture.wrapS = lineTexture.wrapT = THREE.RepeatWrapping; //每个都重复
     53         lineTexture.repeat.set(1, 1)
     54         lineTexture.needsUpdate = true
     55         
     56         let lineMaterial = new THREE.MeshBasicMaterial({
     57             map: lineTexture,
     58             side: THREE.DoubleSide,
     59             transparent: true
     60         })
     61         
     62         //确定几何体位置
     63         let linePoints = []
     64         // let curvePath = new THREE.CurvePath();
     65         for(let i = 0; i < pointList.length; i++) {
     66             var item = pointList[i];
     67             var renderLinePoints = this.lngLatToXY(this.view, [item[0], item[1], 10]);
     68             var vector3List = renderLinePoints.vector3List;
     69             
     70             linePoints.push(new THREE.Vector3(vector3List.x, vector3List.y, vector3List.z));
     71             
     72             // if(i < pointList.length - 1) {
     73             //     var item1 = pointList[i + 1];
     74             //     var renderLinePoints1 = this.lngLatToXY(this.view, [item1[0], item1[1], 10]);
     75             //     var vector3List1 = renderLinePoints1.vector3List;
     76                 
     77             //     // var item2 = pointList[i + 1];
     78             //     // var renderLinePoints2 = this.lngLatToXY(this.view, [item2[0], item2[1], 10]);
     79             //     // var vector3List2 = renderLinePoints2.vector3List;
     80                 
     81             //     const line = new THREE.LineCurve3(vector3List, vector3List1);
     82             //     // const line = new THREE.QuadraticBezierCurve3(vector3List, vector3List1, vector3List2);
     83             //     curvePath.curves.push(line)
     84             // }
     85         }
     86         
     87         // console.log(curvePath)
     88         
     89         // CatmullRomCurve3创建一条平滑的三维样条曲线
     90         const curvePath = new THREE.CatmullRomCurve3(linePoints) // 曲线路径
     91         
     92         let geometry = new THREE.TubeGeometry(curvePath, 64, 30, 8, true )
     93         let lineMesh = new THREE.Mesh(geometry, lineMaterial);
     94         
     95         this.scene.add(lineMesh);
     96         
     97         this.textures.push(lineTexture);
     98         this.objects.push(lineMesh);
     99     }
    100 
    101 
    102     updateModels(context) {
    103         super.updateModels(context);
    104         
    105         if (this.textures.length > 0) {
    106             this.textures.forEach(texture => {
    107                 if (texture) texture.offset.x -= 0.01;
    108             })
    109         }
    110     }
    111 
    112 }
    View Code

     

    调用案例:MapBuilder是我封装的加载底图的类,各位大佬自己换掉,随便加个底图图层

     1 
     4 
     5 
    41 
    42 
    View Code

     

  • 相关阅读:
    数据结构与算法——栈和队列
    可视化学习:如何生成简单动画让图形动起来
    round函数四舍五入结果不对
    【Python】 使用Apache Tika和Python实现zip、csv、xls等多格式文件文本内容提取
    Java.lang.Class类 getCanonicalName方法有什么功能呢?
    【ElasticSearch学习笔记】一、ES下载、安装、目录结构、root用户权限问题、kibana下载安装
    P17 JLayeredPane层级面板
    分布式ELK+KAFKA日志采集 docker-compose
    Redis中常见数据结构和数据类型
    第81步 时间序列建模实战:Adaboost回归建模
  • 原文地址:https://www.cnblogs.com/loveFlex/p/17667565.html