如何在arcGIS上叠加threejs图层

介绍

最近有两个项目在数据可视化大屏端有比较高的视觉展示要求,为此我们的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轴(蓝色向上轴)旋转朝向(一般情况下不用调整),就能把模型放到正确的位置了。

核心代码

  1. 初始化地图,按照arcGIS文档配置就行了,这里必须注意viewingMode="local",把地图摊开。

    jsx 复制代码
    import 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  } // 设置坐标系,可选
    })
  2. 创建基础图层,arcGIS提供了创建自定义WebGL图层的方法,本质上是创建了一个额外的渲染器,在渲染地图之余做模型渲染,以下结构是固定的。

    jsx 复制代码
    import * 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)
    }
  3. 用户在操作地图的时候需要同步WebGL相机镜头,已达到与地图镜头一致,因此在额外渲染器每帧执行时会执行以下代码,这块内容也是必须的。

    jsx 复制代码
    updateCamera (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()
    }
  4. 实现思路中提到的点坐标转换,arcGIS提供了一个坐标批量转换的方法.toRenderCoordinates,使用时注意查看参数。

    jsx 复制代码
    import * 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
    }
  5. 以下代码是解决"模型体在地图上的位置和朝向",该方法适用于viewingMode='global'即球状模式。arcGIS也提供了额外方法renderCoordinateTransformAt,返回了一个能够调整物体位置、朝向、大小的转置矩阵。

    jsx 复制代码
    import * 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坐标转换的相关文档

    developers.arcgis.com/javascript/...

相关推荐
小月鸭9 小时前
如何理解HTML语义化
前端·html
jump6809 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信9 小时前
我们需要了解的Web Workers
前端
brzhang10 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu10 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花10 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋10 小时前
场景模拟:基础路由配置
前端
六月的可乐10 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程
一 乐11 小时前
智慧党建|党务学习|基于SprinBoot+vue的智慧党建学习平台(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·学习
BBB努力学习程序设计11 小时前
CSS Sprite技术:用“雪碧图”提升网站性能的魔法
前端·html