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

发布时间 2023-08-30 16:20:58作者: Binyy_Wuhan

本文适合对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