效果图
在很多情况下,我们需要在三维场景中显示真实世界地理位置的建筑物,例如制作城市规划演示或者导航应用程序。在本文中,我们将介绍如何使用 Three.js 将 GeoJSON 数据渲染为一组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…