数据可视化【原创】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 }
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 }
调用案例:MapBuilder是我封装的加载底图的类,各位大佬自己换掉,随便加个底图图层

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 AreaLayer from './core/AreaLayer-flow-tube2.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 this.areaLayer = new AreaLayer({ view: v }); 34 this.areaLayer.apply(); 35 } 36 }); 37 } 38 } 39 }; 40 </script> 41 42 <style lang="scss" scoped> 43 .root { 44 position: absolute; 45 width: 100%; 46 height: 100%; 47 #map { 48 width: 100%; 49 height: 100%; 50 outline: none; 51 // background-color: $color-grey; 52 // background-color: black; 53 } 54 } 55 56 ::v-deep { 57 .esri-ui-top-left { 58 left: 410px; 59 top: 40px; 60 } 61 h2.esri-widget__heading { 62 font-size: 12px; 63 } 64 .esri-view-width-xlarge .esri-popup__main-container { 65 width: 300px; 66 } 67 .esri-view .esri-view-surface--inset-outline:focus::after { 68 outline: auto 0px Highlight !important; 69 outline: auto 0px -webkit-focus-ring-color !important; 70 } 71 } 72 </style>
作者: Binyy
出处: https://www.cnblogs.com/loveFlex
城市:wuhan
微信:momakeyy
详细源码请移步,记得点个星星噢~ https://gitee.com/binyylovesino/lilo-ui 欢迎各路大佬指导、提问~
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 如有问题, 可邮件(408460486@qq.com)或者微信咨询.
posted on 2023-08-30 16:15 Binyy_Wuhan 阅读(1678) 评论(3) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义