在高德地图上实现山地地形

2,288 阅读4分钟

在高德地图上实现山地地形

前言

在GIS应用中,有时候需要模拟展示一些地形效果,特别是山地区域。我们可以使用多种方式来创建山地地形模型,其中最常用的方法是使用数字高程模型(DEM),它是一种用于描述地形高度的数字模型。通过DEM,可以获取地形的高程和坡度等信息,并将其转换为三维地形模型,本文浅显地介绍了一种实现方法。

Honeycam_2023-05-04_16-45-01.gif

需求说明

  1. 使用高度图作为原始数据,在地图中生成对应的地形
  2. 地形区域为矩形,可指定中心、宽高、单位高度
  3. 支持更换地形的纹理,可以使用影像地图、等高线图、高度图等

名词解释

高度图:也称高程图(Heightmap)、高程模型或灰度图,是一种用于描述地形高度的二维图像,常用于游戏和虚拟现实应用中的地形建模。高度图通常是一个灰度图像,其中每个像素的灰度值表示该点的高度。较暗的像素通常表示较低的高度,而较亮的像素则表示较高的高度。通过将这些高度值映射到三维网格上,可以创建出具有自然地形的山脉、河流、峡谷等地貌特征的三维地形模型。

DEM(Digital Elevation Model,数字高程模型)数据是用于描述地形高度的数字模型,数据形式可以是文本格式ASCII Grid,图像格式GeoTIFF,二进制格式SDTS DEM

实现思路

1.创建一个平面几何体Plane,创建时配置好Plane的位置,尺寸,宽高方向的分段数。分段数决定了山地地形层最终的顶点数量,顶点越多图层越平滑,性能损耗也随着增加 Untitled.png

2.使用高度图Heightmap作为原始数据,调整Plane上每个顶点的z轴高度,Heightmap上的像素点和Plane上的顶点有一定的映射关系,每个顶点对应Heightmap上具有相同位置关系的像素点P,P越接近白色,则顶点高度越高 %E6%9C%AA%E6%A0%87%E9%A2%98-1.jpg

3.贴纹理,将平面所在区域的卫星影像图(或其他任意图片) %E6%9C%AA%E6%A0%87%E9%A2%98-2.jpg

4.贴法线图增加凹凸感,法线图可以使用纹理图直接在Photoshop生成 %E6%9C%AA%E6%A0%87%E9%A2%98-2.5.jpg

5.增加一些场景,比如卫星图层、风车发电器什么的,看起来比较像那么回事。 Honeycam_2023-05-04_16-34-15.gif

代码实现

  1. 加载高度图,处理DEM数据
// 加载图片
loadTerrainImg () {
    return new Promise((resolve) => {
      const img = new Image()
      img.src = this._conf.terrainTexture
      img.onload = () => {
        this._terrainImg = img
        resolve(img)
      }
    })
  }

// 获取高度图中的高度属性转为Js对象
getTerrainData () {
    const img = this._terrainImg
    const { width, height } = img
    const canvas = document.createElement('canvas')
    canvas.width = width
    canvas.height = height
    const ctx = canvas.getContext('2d')

    // 将图片绘制到画布上
    ctx.drawImage(img, 0, 0, width, height)
    // 获取画布上的图像像素矩阵
    const { data } = ctx.getImageData(0, 0, width, height)

    const res = []
    /**
     * 数据获取规则:先宽后高
     */
    for (let j = 0; j < height; j++) {
      res.push([])
      for (let i = 0; i < width; i++) {
        const index = i + j * width
				// 每个像素点信息为r、g、b、a,且r=g=b,取r的值即可
        res[j].push(data[4 * index])
      }
      // 宽度加1,以覆盖所有顶点
      res[j].push(res[j][res[j].length - 1])
    }
    // 高度加1,以覆盖所有顶点
    res.push([...res[res.length - 1]])
    // console.log(res)
    return res
  }
  1. 创建几何体Plane,并根据高度图数据调整Plane为地形几何体
createPlaneGeometry () {
    // 创建几何面
    const { width, height, widthSegments, heightSegments } = this._conf.style
    const geometry = new THREE.PlaneGeometry(width, height, widthSegments, heightSegments)
    // 获取高度图数据
    const widthHeightArr = this.getTerrainData()

    // 调整几何面上每个顶点的高度,高度值来自terrainData
    for (let j = 0; j <= heightSegments; j++) {
      for (let i = 0; i <= widthSegments; i++) {
        const _i = Math.floor(i / widthSegments * this._terrainImg.width)
        const _j = Math.floor(j / heightSegments * this._terrainImg.height)
        geometry.attributes.position.array[(i + j * (widthSegments + 1)) * 3 + 2] = widthHeightArr[_i][_j] * this._conf.unitHeight
      }
    }
    return geometry
  }
  1. 贴纹理,创建模型,此时图层创建完毕
createModel () {
    // 几何体
    const geometry = this.createPlaneGeometry()
    // 纹理材质
    const texture = new THREE.TextureLoader().load(this._conf.mapTexture)
    const normal = new THREE.TextureLoader().load(this._conf.normalTexture)
    const material = new THREE.MeshStandardMaterial({
      map: texture, //纹理贴图
      normalMap: normal, // 法线贴图,用于模拟凹凸感
      side: THREE.DoubleSide,
      blending: THREE.NormalBlending
    })

    const plane = new THREE.Mesh(geometry, material)
    this.scene.add(plane)
  }
  1. 添加光照,这里只加了一个平行光
layer.on('complete', ({ scene }) => {
  // 平行光
  const directLight = new THREE.DirectionalLight('#ffffff', 0.6)
  directLight.position.set(1, -1, 1)
  
  scene.add(directLight)
})

注意点

高度图与地形面Plane顶点的对应关系,可以不是一对一的,但高度图和地形面的宽高比必须一致。比如我使用了一张宽高分别为501px的图片保存地形高度数据,那么就一共有501501=251001个高度信息,Plane在宽高方向上的分段数分别为20,那么一共有2121=441个顶点,每个顶点只要找到高度图在对应参考位置的高度就行了。 %E6%9C%AA%E6%A0%87%E9%A2%98-5.jpg

还可以做哪些改进

本文的方法适用于在地形叠加更多模型的场景。如果将地图可视范围放大到省份、国家级别的尺度,还要兼顾地图拖动、缩放、旋转时同步更新,如果还使用创建几何体的方式去实现的话其实是不方便的。

山地只要“看上去”有高低错落的感觉就行了,在着色器中完全可以模拟出来,不需要建立真正高度地形,那么我们完全给一个平面赋予这样一种自定义材质,将当前的DEM数据传入着色器模拟出山地效果,文末链接有具体的示例介绍。 Untitled 1.png

高德地图Loca的3D热力图应该就是这样实现的。 Honeycam_2023-05-05_11-37-46.gif

相关链接

常用的gltf模型下载源

sketchfab.com/

世界地形高度图

tangrams.github.io/heightmappe…

Three.js重建真实地形

blog.csdn.net/shebao3333/…