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

相关推荐
程序员爱技术8 分钟前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会1 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
衣乌安、1 小时前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜1 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师1 小时前
CSS的三个重点
前端·css
耶啵奶膘3 小时前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^4 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie5 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic5 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿6 小时前
webWorker基本用法
前端·javascript·vue.js