資料視覺化【原創】vue+arcgis+threejs 實現流光邊界線效果

2023-08-30 18:00:57

本文適合對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