数据可视化【原创】vue+arcgis+threejs 实现立体光圈闪烁效果
本文适合对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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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是我封装的加载底图的类,各位大佬自己换掉,随便加个底图图层
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <template> 2 <div class="root"><div id="map" ref="rootmap"></div></div> 3 </template> 4 5 <script> 6 import MapBuilder from './core/MapBuilder.js'; 7 import FlashLayer from './core/FlashLayer.js'; 8 9 export default { 10 name: 'base-map', 11 data() { 12 return { 13 map: null 14 }; 15 }, 16 computed: {}, 17 created() { 18 this.inited = false; 19 this.view = null; 20 }, 21 mounted() { 22 this.setup(); 23 }, 24 methods: { 25 setup() { 26 let mb = new MapBuilder({ 27 id: 'map', 28 complete: (m, v, b) => { 29 this.map = m; 30 this.inited = true; 31 this.view = v; 32 33 v.on('click', event => { 34 console.log(event); 35 this.flashLayer.excute(event.mapPoint, 2000); 36 }); 37 } 38 }); 39 } 40 } 41 }; 42 </script> 43 44 <style lang="scss" scoped> 45 .root { 46 position: absolute; 47 width: 100%; 48 height: 100%; 49 #map { 50 width: 100%; 51 height: 100%; 52 outline: none; 53 // background-color: $color-grey; 54 // background-color: black; 55 } 56 } 57 58 ::v-deep { 59 .esri-ui-top-left { 60 left: 410px; 61 top: 40px; 62 } 63 h2.esri-widget__heading { 64 font-size: 12px; 65 } 66 .esri-view-width-xlarge .esri-popup__main-container { 67 width: 300px; 68 } 69 .esri-view .esri-view-surface--inset-outline:focus::after { 70 outline: auto 0px Highlight !important; 71 outline: auto 0px -webkit-focus-ring-color !important; 72 } 73 } 74 </style>
作者: Binyy
出处: https://www.cnblogs.com/loveFlex
城市:wuhan
微信:momakeyy
详细源码请移步,记得点个星星噢~ https://gitee.com/binyylovesino/lilo-ui 欢迎各路大佬指导、提问~
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 如有问题, 可邮件(408460486@qq.com)或者微信咨询.
posted on 2023-09-05 16:32 Binyy_Wuhan 阅读(886) 评论(1) 编辑 收藏 举报