介绍
最近有两个项目在数据可视化大屏端有比较高的视觉展示要求,为此我们的UI团队也做了一些效果图,经过几轮的调整终于得到合作方的肯定,后续的技术实现任务理所当然地落在前端开发人员头上。
如图所示的行政区域立体块效果,原本使用我们之前开发的基于高德地图的数据可视化图层组件gl-layers是能够支持展示需要的,然而由于合作方提供的底图,是基于arcGIS引擎且带专用坐标系的地图切片服务,所以需要解决gl-layers的arcGIS兼容问题。那么今天我们着重分享如何在arcGIS上叠加自定义的webGL图层。
工具说明
保持版本稳定特别重要,因为arcGIS有个特点就是断崖式更新,别说大版本的升级,单是小版本的迭代经常也是有各种暗雷,比如会直接把某个类或者某组方法给废了,开发的时候必须仔细查看文档,好在arcGIS文档足够详细,英文过了4级的基本都能看懂。
工具 | 版本 | 说明 |
---|---|---|
arcGIS | 4.22 | 整个可视化场景底图,选择3D底图,local地图摊开)模式 |
three.js | 0.157 | webGL的3D引擎,老朋友了 |
实现思路
我们实现的思路很直接,就是在地图上面再叠加一层three.js场景,把场景上的网格体放置到它地图上应该放置的位置。如果把地球考虑为球体会比较复杂,为了降低难度,我们先把三维地球摊开为平面地图,在平面地图上处理位置关系。
在webGL的世界里,场景里的大部分内容都是由网格体Mesh构成的,而网格体Mesh则由基本的三角面片构成,三角面片则由确定的3个空间点坐标连线构成,因此空间点的位置其实就决定了内容的位置。所以核心代码之一就是解决网格体顶点坐标值转换的问题。
本文的WebGL图层使用three.js实现,我们知道three.js是有自己的空间坐标系统的,在arcGIS上叠加WebGL图层要解决的核心问题就是把three.js场景中的内容位置对应到地图上的实际位置。因此,对于现成的模型,我们只要把模型自身的原点"对齐"到地图上的经纬度位置,再注意z轴(蓝色向上轴)旋转朝向(一般情况下不用调整),就能把模型放到正确的位置了。
核心代码
-
初始化地图,按照arcGIS文档配置就行了,这里必须注意viewingMode="local",把地图摊开。
jsximport Map from '@arcgis/core/Map' import SceneView from '@arcgis/core/views/SceneView' map = new Map({ basemap: 'satellite', // 卫星影像 }) view = new SceneView({ map, viewingMode: 'local', // 展开平面模式,必须! container: container.value, //DOM容器 camera: {...}, spatialReference: { wkid: 3857 } // 设置坐标系,可选 })
-
创建基础图层,arcGIS提供了创建自定义WebGL图层的方法,本质上是创建了一个额外的渲染器,在渲染地图之余做模型渲染,以下结构是固定的。
jsximport * as externalRenderers from '@arcgis/core/views/3d/externalRenderers' /** * @description 创建独立图层(渲染器) * @private * @return {AMap.CustomLayer} */ async initLayer () { const t = this const { view } = t const myExternalRenderer = { setup: function (context) { // 安装代码,执行一次 t.initThree(context) }, render: function (context) { // 每帧执行 t.updateCamera(context) }, dispose (context) { // 移除时执行 } } externalRenderers.add(view, myExternalRenderer) }
-
用户在操作地图的时候需要同步WebGL相机镜头,已达到与地图镜头一致,因此在额外渲染器每帧执行时会执行以下代码,这块内容也是必须的。
jsxupdateCamera (context) { const { view } = this // 同步镜头 const cam = context.camera this.camera.position.set(cam.eye[0], cam.eye[1], cam.eye[2]) this.camera.up.set(cam.up[0], cam.up[1], cam.up[2]) this.camera.lookAt(new THREE.Vector3(cam.center[0], cam.center[1], cam.center[2])) this.camera.projectionMatrix.fromArray(cam.projectionMatrix) // 绘制场景 this.renderer.resetState() // 必要!重新绑定renderer context.bindRenderTarget() this.renderer.render(this.scene, this.camera) // 请求重绘视图。 externalRenderers.requestRender(view) // 清除WebGL状态 context.resetWebGLState() }
-
实现思路中提到的点坐标转换,arcGIS提供了一个坐标批量转换的方法.toRenderCoordinates,使用时注意查看参数。
jsximport * as webMercatorUtils from '@arcgis/core/geometry/support/webMercatorUtils' /** * 将经纬度坐标数组转换为THREE空间坐标值 * @param {Array} lngLats 坐标数组 * @param {sceneView} view 当前视图 * @return {Array} */ lngLatToXyz (lngLats, view) { // 经纬度转墨卡托平面坐标 const originPos = lngLats.map(([lng, lat, altitude]) => { const [x, y] = webMercatorUtils.lngLatToXY(lng, lat) return [x, y, altitude || 0] }).flat() // 转换结果(一维数组) const dist = externalRenderers.toRenderCoordinates( view, originPos, 0, view.center.spatialReference, new Float32Array(originPos.length), 0, originPos.length / 3 ) // 重新转为二维数组 const res = [] for (let i = 0; i < dist.length; i += 3) { res.push([dist[i], dist[i + 1], dist[i + 2]]) } return res }
-
以下代码是解决"模型体在地图上的位置和朝向",该方法适用于viewingMode='global'即球状模式。arcGIS也提供了额外方法renderCoordinateTransformAt,返回了一个能够调整物体位置、朝向、大小的转置矩阵。
jsximport * as webMercatorUtils from '@arcgis/core/geometry/support/webMercatorUtils' createCube () { const { scene, view } = this if (this._center) { const SIZE = 400 const geometry = new THREE.BoxGeometry(0.2 * SIZE, 0.2 * SIZE, 1 * SIZE) const material = new THREE.MeshStandardMaterial({ color: 0xFF0000, transparent: false }) const cube = new THREE.Mesh(geometry, material) // 1.输入位置 [经度, 纬度, 高程] const [longitude, latitude] = this._center const { spatialReference } = view.center // 2.转为墨卡托平面坐标系xy const [x, y] = webMercatorUtils.lngLatToXY(longitude, latitude) const entryPos = [x, y, SIZE / 2] // 3.获取转置矩阵 const arr = externalRenderers.renderCoordinateTransformAt( view, entryPos, spatialReference, new Float64Array(16) ) const matrix = new THREE.Matrix4().fromArray(arr) // 乘以转置矩阵 cube.applyMatrix4(matrix) scene.add(cube) } }
总结
以上就是关于如何在arcGIS上叠加自定义WebGL图层的简单分享,最关键的原理和核心代码弄懂了,其实会发现WebGL图层接入到各种主流地图引擎其实大同小异,你也可以写一套兼容各种引擎的WebGL图层组件,关于如何把图层封装好以及提供各种图层该有的基本方法,这块内容等下一次再分享吧。
相关链接
arcGIS自定义WebGL图层示例
developers.arcgis.com/javascript/...
arcGIS坐标转换的相关文档