数据可视化【原创】vue+arcgis+threejs 实现立体光圈闪烁效果
阅读原文时间:2023年09月05日阅读:4

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

效果图:

素材:

主要思路:

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

子类FlashLayer继承它,并在里面封装了一个excute方法用来执行闪烁,参数包括point和height。先构建一个管道CatmullRomCurve3,在构建环RingGeometry,然后在updateModels里面去更新管道的scale.z,更新环的半径和透明度。

excute方法:

1 excute(point, height) {
2 // let pointList = [
3 // [114.31456780904838, 30.55355011036358, 0],
4 // [114.31456780904838, 30.55355011036358, 2000]
5 // ];
6 let pointList = [
7 [
8 point.longitude,
9 point.latitude,
10 0
11 ],
12 [
13 point.longitude,
14 point.latitude,
15 height
16 ]
17 ]
18
19 let linePoints = [];
20 //确定几何体位置
21 pointList.forEach((item) => {
22 var renderLinePoints = this.lngLatToXY(this.view, [item[0], item[1], item[2]]);
23 linePoints.push(new THREE.Vector3(renderLinePoints.vector3List.x, renderLinePoints
24 .vector3List.y, renderLinePoints.vector3List.z));
25 })
26
27 const lineImg = require('../../../../public/static/img/line.png')
28 let lineTexture = new THREE.TextureLoader().load(lineImg)
29 lineTexture.wrapS = lineTexture.wrapT = THREE.RepeatWrapping; //每个都重复
30 lineTexture.repeat.set(1, 1)
31 lineTexture.needsUpdate = true
32
33 let lineMaterial = new THREE.MeshBasicMaterial({
34 map: lineTexture,
35 side: THREE.DoubleSide,
36 transparent: true,
37 opacity: 1
38 })
39
40 // CatmullRomCurve3创建一条平滑的三维样条曲线
41 let curve = new THREE.CatmullRomCurve3(linePoints) // 曲线路径
42
43 // 创建管道
44 let tubeGeometry = new THREE.TubeGeometry(curve, 1, 16)
45
46 let mesh = new THREE.Mesh(tubeGeometry, lineMaterial);
47 mesh.name = 'FlashLayer_Line';
48 mesh.scale.z = 0;
49 mesh.layers.set(0);
50 this.group.add(mesh);
51
52 //光圈
53 const ringUserData = {
54 width: 300,
55 innerRadius: 0,
56 opacity: 1,
57 opacityFlag: false,
58 per: 10,
59 innerMax: 800,
60 times: 0,
61 line: mesh
62 };
63 const ringGeometry = new THREE.RingGeometry(ringUserData.innerRadius, ringUserData.innerRadius +
64 ringUserData.width, 32);
65 const ringMaterial = new THREE.MeshPhongMaterial({
66 color: COLOR_RING,
67 emissive: COLOR_RING,
68 side: THREE.DoubleSide,
69 flatShading: true,
70 wireframe: false,
71 transparent: true,
72 opacity: ringUserData.opacity
73 });
74 const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
75 const ringPoint = linePoints[1];
76 console.log(ringPoint);
77 ringMesh.position.set(ringPoint.x, ringPoint.y, ringPoint.z / 2 + (Math.random() * ringPoint.z / 4 | 0) );
78 ringMesh.name = 'FlashLayer_Ring';
79 ringMesh.userData = ringUserData;
80 ringMesh.layers.set(0);
81
82 this.group.add(ringMesh);
83 }

updateModels方法(记得一定要销毁哦):

1 updateModels(context) {
2 super.updateModels(context)
3
4 if (this.group.children.length) {
5 let rubbish = [];
6 for (let i = this.group.children.length - 1; i >= 0; i--) {
7 const mesh = this.group.children[i];
8 if (mesh.name === 'FlashLayer_Line') {
9 mesh.material.map.offset.x -= 0.02;
10 mesh.scale.z += 0.03;
11 if(mesh.scale.z >= 1) {
12 mesh.scale.z = 1;
13 }
14 continue;
15 }
16 if (mesh.name === 'FlashLayer_Ring') {
17 const ringUserData = mesh.userData;
18 const per = ringUserData.per;
19 ringUserData.innerRadius += per;
20 ringUserData.opacity -= 1 / (ringUserData.innerMax / per);
21
22 if (ringUserData.innerRadius > ringUserData.innerMax) {
23 ringUserData.innerRadius = 0;
24 ringUserData.opacity = 1;
25 ringUserData.times++;
26
27 if(ringUserData.times === FLASH_TIMES) {
28 rubbish.push(mesh)
29 continue;
30 }
31 }
32
33 // if(ringUserData.opacityFlag) {
34 // ringUserData.opacity += 1 / 800 / 5;
35 // } else {
36 // ringUserData.opacity -= 1 / 800 / 5;
37 // }
38
39 // if(ringUserData.opacity >= 1) {
40 // ringUserData.opacity = 1;
41 // ringUserData.opacityFlag = false;
42 // }
43 // if(ringUserData.opacity <= 0) { 44 // ringUserData.opacity = 0; 45 // ringUserData.opacityFlag = true; 46 // } 47 48 const ringGeometry = new THREE.RingGeometry(ringUserData.innerRadius, ringUserData.innerRadius + 49 ringUserData.width, 32); 50 const ringMaterial = new THREE.MeshPhongMaterial({ 51 color: COLOR_RING, 52 emissive: COLOR_RING, 53 side: THREE.DoubleSide, 54 flatShading: true, 55 wireframe: false, 56 transparent: true, 57 opacity: ringUserData.opacity 58 }); 59 mesh.geometry.dispose(); 60 mesh.material.dispose(); 61 mesh.geometry = ringGeometry; 62 mesh.material = ringMaterial; 63 mesh.rotation.z += 1; 64 65 continue; 66 } 67 } 68 69 if(rubbish.length) { 70 for(let i = 0; i < rubbish.length; i++) { 71 let rubbishMesh = rubbish[i]; 72 let line = rubbishMesh.userData.line; 73 this.group.remove(line); 74 line.material.dispose(); 75 line.geometry.dispose(); 76 // line.dispose(); 77 line = null; 78 this.group.remove(rubbishMesh); 79 rubbishMesh.material.dispose(); 80 rubbishMesh.geometry.dispose(); 81 // rubbishMesh.dispose(); 82 rubbishMesh = null; 83 } 84 rubbish = null; 85 } 86 87 // if (mesh.scale.z <=0 ){ 88 // this.animateFlag = true; 89 // } 90 91 // if(mesh.scale.z >=1 ) {
92 // this.animateFlag = false;
93 // }
94
95 // if(this.animateFlag) {
96 // mesh.scale.z += 0.01;
97 // } else {
98 // mesh.scale.z -= 0.01;
99 // }
100 }
101 }

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 }

FlashLayer:

1 import mapx from '@/utils/mapUtils.js';
2 import Polygon from "@arcgis/core/geometry/Polygon";
3 import Point from "@arcgis/core/geometry/Point";
4 import * as THREE from 'three'
5 import ExternalRendererLayer from './ExternalRendererLayer.js'
6 import Graphic from "@arcgis/core/Graphic";
7 import SpatialReference from '@arcgis/core/geometry/SpatialReference'
8 import * as externalRenderers from "@arcgis/core/views/3d/externalRenderers"
9
10 const COLOR_RING = 0xff0000;
11 const FLASH_TIMES = 2;
12
13 export default class FlashLayer extends ExternalRendererLayer {
14 constructor({
15 view,
16 options
17 }) {
18 super({
19 view,
20 options
21 })
22 }
23
24 setup() {
25 this.group = new THREE.Group();
26 // this.animateFlag = false;
27 }
28
29 addModels(context) {
30 super.addModels(context);
31
32 this.scene.add(this.group);
33 this.objects.push(this.group);
34 }
35
36 excute(point, height) {
37 // let pointList = [
38 // [114.31456780904838, 30.55355011036358, 0],
39 // [114.31456780904838, 30.55355011036358, 2000]
40 // ];
41 let pointList = [
42 [
43 point.longitude,
44 point.latitude,
45 0
46 ],
47 [
48 point.longitude,
49 point.latitude,
50 height
51 ]
52 ]
53
54 let linePoints = [];
55 //确定几何体位置
56 pointList.forEach((item) => {
57 var renderLinePoints = this.lngLatToXY(this.view, [item[0], item[1], item[2]]);
58 linePoints.push(new THREE.Vector3(renderLinePoints.vector3List.x, renderLinePoints
59 .vector3List.y, renderLinePoints.vector3List.z));
60 })
61
62 const lineImg = require('../../../../public/static/img/line.png')
63 let lineTexture = new THREE.TextureLoader().load(lineImg)
64 lineTexture.wrapS = lineTexture.wrapT = THREE.RepeatWrapping; //每个都重复
65 lineTexture.repeat.set(1, 1)
66 lineTexture.needsUpdate = true
67
68 let lineMaterial = new THREE.MeshBasicMaterial({
69 map: lineTexture,
70 side: THREE.DoubleSide,
71 transparent: true,
72 opacity: 1
73 })
74
75 // CatmullRomCurve3创建一条平滑的三维样条曲线
76 let curve = new THREE.CatmullRomCurve3(linePoints) // 曲线路径
77
78 // 创建管道
79 let tubeGeometry = new THREE.TubeGeometry(curve, 1, 16)
80
81 let mesh = new THREE.Mesh(tubeGeometry, lineMaterial);
82 mesh.name = 'FlashLayer_Line';
83 mesh.scale.z = 0;
84 mesh.layers.set(0);
85 this.group.add(mesh);
86
87 //光圈
88 const ringUserData = {
89 width: 300,
90 innerRadius: 0,
91 opacity: 1,
92 opacityFlag: false,
93 per: 10,
94 innerMax: 800,
95 times: 0,
96 line: mesh
97 };
98 const ringGeometry = new THREE.RingGeometry(ringUserData.innerRadius, ringUserData.innerRadius +
99 ringUserData.width, 32);
100 const ringMaterial = new THREE.MeshPhongMaterial({
101 color: COLOR_RING,
102 emissive: COLOR_RING,
103 side: THREE.DoubleSide,
104 flatShading: true,
105 wireframe: false,
106 transparent: true,
107 opacity: ringUserData.opacity
108 });
109 const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
110 const ringPoint = linePoints[1];
111 console.log(ringPoint);
112 ringMesh.position.set(ringPoint.x, ringPoint.y, ringPoint.z / 2 + (Math.random() * ringPoint.z / 4 | 0) );
113 ringMesh.name = 'FlashLayer_Ring';
114 ringMesh.userData = ringUserData;
115 ringMesh.layers.set(0);
116
117 this.group.add(ringMesh);
118 }
119
120 updateModels(context) {
121 super.updateModels(context)
122
123 if (this.group.children.length) {
124 let rubbish = [];
125 for (let i = this.group.children.length - 1; i >= 0; i--) {
126 const mesh = this.group.children[i];
127 if (mesh.name === 'FlashLayer_Line') {
128 mesh.material.map.offset.x -= 0.02;
129 mesh.scale.z += 0.03;
130 if(mesh.scale.z >= 1) {
131 mesh.scale.z = 1;
132 }
133 continue;
134 }
135 if (mesh.name === 'FlashLayer_Ring') {
136 const ringUserData = mesh.userData;
137 const per = ringUserData.per;
138 ringUserData.innerRadius += per;
139 ringUserData.opacity -= 1 / (ringUserData.innerMax / per);
140
141 if (ringUserData.innerRadius > ringUserData.innerMax) {
142 ringUserData.innerRadius = 0;
143 ringUserData.opacity = 1;
144 ringUserData.times++;
145
146 if(ringUserData.times === FLASH_TIMES) {
147 rubbish.push(mesh)
148 continue;
149 }
150 }
151
152 // if(ringUserData.opacityFlag) {
153 // ringUserData.opacity += 1 / 800 / 5;
154 // } else {
155 // ringUserData.opacity -= 1 / 800 / 5;
156 // }
157
158 // if(ringUserData.opacity >= 1) {
159 // ringUserData.opacity = 1;
160 // ringUserData.opacityFlag = false;
161 // }
162 // if(ringUserData.opacity <= 0) { 163 // ringUserData.opacity = 0; 164 // ringUserData.opacityFlag = true; 165 // } 166 167 const ringGeometry = new THREE.RingGeometry(ringUserData.innerRadius, ringUserData.innerRadius + 168 ringUserData.width, 32); 169 const ringMaterial = new THREE.MeshPhongMaterial({ 170 color: COLOR_RING, 171 emissive: COLOR_RING, 172 side: THREE.DoubleSide, 173 flatShading: true, 174 wireframe: false, 175 transparent: true, 176 opacity: ringUserData.opacity 177 }); 178 mesh.geometry.dispose(); 179 mesh.material.dispose(); 180 mesh.geometry = ringGeometry; 181 mesh.material = ringMaterial; 182 mesh.rotation.z += 1; 183 184 continue; 185 } 186 } 187 188 if(rubbish.length) { 189 for(let i = 0; i < rubbish.length; i++) { 190 let rubbishMesh = rubbish[i]; 191 let line = rubbishMesh.userData.line; 192 this.group.remove(line); 193 line.material.dispose(); 194 line.geometry.dispose(); 195 // line.dispose(); 196 line = null; 197 this.group.remove(rubbishMesh); 198 rubbishMesh.material.dispose(); 199 rubbishMesh.geometry.dispose(); 200 // rubbishMesh.dispose(); 201 rubbishMesh = null; 202 } 203 rubbish = null; 204 } 205 206 // if (mesh.scale.z <=0 ){ 207 // this.animateFlag = true; 208 // } 209 210 // if(mesh.scale.z >=1 ) {
211 // this.animateFlag = false;
212 // }
213
214 // if(this.animateFlag) {
215 // mesh.scale.z += 0.01;
216 // } else {
217 // mesh.scale.z -= 0.01;
218 // }
219 }
220 }
221
222 transparentObject(geometry, material) {
223 var obj = new THREE.Object3D();
224 var mesh = new THREE.Mesh(geometry, material);
225 mesh.material.side = THREE.BackSide; // back faces
226 mesh.renderOrder = 0;
227 obj.add(mesh);
228
229 var mesh = new THREE.Mesh(geometry, material.clone());
230 mesh.material.side = THREE.FrontSide; // front faces
231 mesh.renderOrder = 1;
232 obj.add(mesh);
233 return obj
234 }
235
236 }

调用案例:点击地图显示光圈闪烁,MapBuilder是我封装的加载底图的类,各位大佬自己换掉,随便加个底图图层

1
4
5
43
44

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章