如何在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/...

相关推荐
f89790707034 分钟前
layui动态表格出现 横竖间隔线
前端·javascript·layui
鱼跃鹰飞41 分钟前
Leecode热题100-295.数据流中的中位数
java·服务器·开发语言·前端·算法·leetcode·面试
二十雨辰1 小时前
[uni-app]小兔鲜-04推荐+分类+详情
前端·javascript·uni-app
霸王蟹2 小时前
Vue3 项目中为啥不需要根标签了?
前端·javascript·vue.js·笔记·学习
小白求学12 小时前
CSS计数器
前端·css
Anita_Sun2 小时前
🌈 Git 全攻略 - Git 的初始设置 ✨
前端
lucifer3112 小时前
深入解析 React 组件封装 —— 从业务需求到性能优化
前端·react.js
等什么君!3 小时前
复习HTML(进阶)
前端·html
儒雅的烤地瓜3 小时前
JS | 如何解决ajax无法后退的问题?
前端·javascript·ajax·pushstate·popstate事件·replacestate
觉醒法师3 小时前
Vue3+TS项目 - ref和useTemplateRef获取组件实例
开发语言·前端·javascript