数字孪生系统之GeoJson生成建筑

1,031 阅读2分钟

效果图

geojson-to-building.png

在很多情况下,我们需要在三维场景中显示真实世界地理位置的建筑物,例如制作城市规划演示或者导航应用程序。在本文中,我们将介绍如何使用 Three.jsGeoJSON 数据渲染为一组3D建筑物。

GeoJSON简介

GeoJSON是一种用于编码地理空间数据结构的开放标准格式,基于JSON。它定义了几种基本的地理数据类型,如点、线、面和几何集合,并提供了相应的JSON对象来表示这些数据类型。

一个简单的GeoJSON数据示例如下:

{
  "type": "Feature",
  "properties": {
    "gml_id": "layer680",
    "objectid": 9456704,
    "height": 6.0,
    "shape_leng": 0.00065453249221400001,
    "shape_area": 2.0398117e-8
  },
  "geometry": {
    "type": "MultiPolygon",
    "coordinates": [
      [
        [
          [120.22386148, 30.2333918],
          [120.22407273, 30.23327273],
          [120.22402227, 30.23320461],
          [120.22381103, 30.23332369],
          [120.22386148, 30.2333918]
        ]
      ]
    ]
  }
}

在上面的例子中,它描述了一个具有经纬度坐标和属性名称的点要素。GeoJSON也支持更复杂的几何形状,如多边形和多线等。本文将会重点关注MultiPolygon

加载GeoJSON

fetchData(callback: (data: GeoJson) => void) {
    fetch('/geojson/xx.geojson')
      .then((res) => res.json())
      .then((data) => {
        callback(data)
      })
      .catch((err) => {
        console.log(err)
      })
  }

经纬度转三维世界坐标

getD3geoMercator() {
    const d3geoMercator = geoMercator()
      .center([120.22422224, 30.20212491])
      .scale(6000000)
      .translate([0, 0])
    return d3geoMercator
}

创建建筑纹理

setMaterial() {
    this.material = new THREE.MeshLambertMaterial({
      color: 0xffffff
    })

    this.material.onBeforeCompile = (shader) => {
      shader.vertexShader = shader.vertexShader.replace(
        'void main() {',
        `
        varying vec3 vPosition;
        varying vec2 vUv;
        void main() {
          vUv = uv;
          vPosition = position;
        `
      )

      shader.fragmentShader = shader.fragmentShader.replace(
        'void main() {',
        `
        varying vec3 vPosition;
        varying vec2 vUv;
        void main() {
        `
      )

      const output = /* glsl */ `
        #include <dithering_fragment>
        vec3 gradient = mix(vec3(1.0, 123.0/255.0, 0.0), vec3(1.0, 166.0/255.0, 74.0/255.0), 0.5);
        // 光照颜色基础上叠加渐变色
        outgoingLight = outgoingLight * gradient;
        gl_FragColor = vec4(outgoingLight, 1.0);
      `
      shader.fragmentShader = shader.fragmentShader.replace('#include <dithering_fragment>', output)
    }
}

完整代码如下

import * as THREE from 'three'
import { geoMercator, type GeoProjection } from 'd3-geo'
import Experience from '../..'
import { Debug } from '../../utils/Debug'
import type { GeoJson } from './types'

export default class GeoJsonToBuilding {
  experience: Experience
  debug: Debug
  deBugParams!: {
    radius: number
    height: number
    color1: THREE.Color
    color2: THREE.Color
    repeat: THREE.Vector2
    speed: number
  }
  group!: THREE.Group<THREE.Object3DEventMap>
  d3geoMercator: GeoProjection
  material!: THREE.MeshLambertMaterial

  constructor() {
    this.experience = new Experience()
    this.debug = this.experience.debug

    this.deBugParams = {
      radius: 1,
      height: 1,
      color1: new THREE.Color(0xfaa64a),
      color2: new THREE.Color(0xffffff),
      repeat: new THREE.Vector2(3, 1),
      speed: 0.05
    }

    this.d3geoMercator = this.getD3geoMercator()

    this.setGroup()
    this.setMaterial()
    this.fetchData((data) => {
      this.handleCreateBuilding(data)
    })
  }

  fetchData(callback: (data: GeoJson) => void) {
    fetch('/geojson/xx.geojson')
      .then((res) => res.json())
      .then((data) => {
        callback(data)
      })
      .catch((err) => {
        console.log(err)
      })
  }

  setGroup() {
    this.group = new THREE.Group()
    this.experience.scene.add(this.group)
  }

  handleCreateBuilding(data: GeoJson) {
    data.features.forEach((feature) => {
      if (
        feature.geometry &&
        feature.geometry.type === 'MultiPolygon' &&
        feature.geometry.coordinates[0][0]
      ) {
        const height = feature.properties.height
        const coordinates = feature.geometry.coordinates[0][0]

        const mesh = this.createBuildingMesh(coordinates, height)
        this.group.add(mesh)
      }
    })
    this.group.rotateX(-Math.PI / 2)
    this.experience.scene.add(this.group)
  }

  createBuildingMesh(points: Array<[number, number]>, height: number) {
    const shapePoints = points.map((point) => {
      const [x, y] = this.d3geoMercator(point)!
      return new THREE.Vector2(x, y)
    })

    const geometry = new THREE.ExtrudeGeometry(new THREE.Shape(shapePoints), {
      depth: height
    })

    return new THREE.Mesh(geometry, this.material)
  }

  getD3geoMercator() {
    const d3geoMercator = geoMercator()
      .center([120.22422224, 30.20212491])
      .scale(6000000)
      .translate([0, 0])
    return d3geoMercator
  }

  setMaterial() {
    this.material = new THREE.MeshLambertMaterial({
      color: 0xffffff
    })

    this.material.onBeforeCompile = (shader) => {
      shader.vertexShader = shader.vertexShader.replace(
        'void main() {',
        `
        varying vec3 vPosition;
        varying vec2 vUv;
        void main() {
          vUv = uv;
          vPosition = position;
        `
      )

      shader.fragmentShader = shader.fragmentShader.replace(
        'void main() {',
        `
        varying vec3 vPosition;
        varying vec2 vUv;
        void main() {
        `
      )

      const output = /* glsl */ `
        #include <dithering_fragment>
        vec3 gradient = mix(vec3(1.0, 123.0/255.0, 0.0), vec3(1.0, 166.0/255.0, 74.0/255.0), 0.5);
        // 光照颜色基础上叠加渐变色
        outgoingLight = outgoingLight * gradient;
        gl_FragColor = vec4(outgoingLight, 1.0);
      `
      shader.fragmentShader = shader.fragmentShader.replace('#include <dithering_fragment>', output)
    }
  }

  setDebug() {}

  update() {
    // this.mesh.material.uniforms.uTime.value += this.deBugParams.speed
  }
}

体验地址:http://120.77.245.158/three-examples/index / www.stao.fun/three-examp…

Three-Demo2.png