地理参考系
类型
类 + Scene 配置项
概念
GeoReference 将坐标参考系、大地基准面和轴约定绑定到一个固定的局部原点。转换链路如下:
地理坐标 (lng, lat, alt)
-> 基准面归一化到 WGS84
-> 局部切平面(float64 米制)
-> 围绕局部原点重新中心化
-> ENU -> three.js 轴映射(东:+X,北:-Z,上:+Y)
-> THREE.Vector3(float32)
局部原点保证了场景坐标始终很小。Web Mercator 的绝对坐标约为 1.3e7 米,直接使用 three.js 的 float32 坐标在城市场景中会产生明显抖动;重新中心化后,所有物体都位于距 (0, 0, 0) 几公里以内。
每个 Scene 都存在一个 GeoReference。不传入任何 GIS 配置时,它使用以 (0, 0) 为中心的 WGS84/等距圆柱默认值,对普通的非地理场景是完全无副作用的空操作,因此该功能是完全可选的。
默认用法
下面的每个示例都可以用 WebGPU(默认)或 WebGL 渲染器查看 —— 切换标签页进行对比。
- WebGPU
- WebGL
import { useRef, useEffect } from 'react'
import * as THREE from 'three'
import { Scene } from 'react-three-lite'
import type { GeoPoint, SceneComponents } from 'react-three-lite'
const ORIGIN: GeoPoint = { lng: 116.397, lat: 39.909 }
const PLACES: { point: GeoPoint; color: number }[] = [
{ point: ORIGIN, color: 0xffd166 },
{ point: { lng: 116.4005, lat: 39.909 }, color: 0x4fc3f7 },
{ point: { lng: 116.397, lat: 39.9117 }, color: 0x81c784 },
{ point: { lng: 116.3935, lat: 39.9063 }, color: 0xe57373 }
]
function App() {
const objectsRef = useRef<THREE.Object3D[]>([])
// The built-in grid is 20 m by default; a city-scale scene customises
// it through gridHelper options, and Scene owns its disposal.
const grid = {
size: 1200,
divisions: 24,
colorCenterLine: 0x4a6fa5,
colorGrid: 0x2f3e5c
}
const handleCreated = (scene: THREE.Scene, components: SceneComponents) => {
const { camera } = components
// A raised south-side viewpoint looks north over the landmarks; the scene
// places the camera from local metres and it looks back at the origin.
scene.setPosition(camera, { x: 0, y: 500, z: 700 })
camera.lookAt(0, 0, 0)
PLACES.forEach(({ point, color }) => {
const radius = 35
const geometry = new THREE.SphereGeometry(radius, 24, 16)
const material = new THREE.MeshBasicMaterial({ color })
const marker = new THREE.Mesh(geometry, material)
// 参考系已绑定在 scene 上,由 scene 自己完成投影。
scene.setPosition(marker, point)
// Rest the sphere on the ground plane instead of burying its lower half.
marker.position.y = radius
scene.add(marker)
objectsRef.current.push(marker)
})
}
useEffect(() => {
return () => {
objectsRef.current.forEach((object) => {
object.removeFromParent()
const mesh = object as THREE.Mesh
mesh.geometry?.dispose()
;(mesh.material as THREE.Material | undefined)?.dispose()
})
objectsRef.current = []
}
}, [])
return (
<Scene
origin={ORIGIN}
gridHelper={grid}
onCreated={handleCreated}
bgColor="rgb(40, 42, 54)"
style={{ marginTop: '10px', width: '100%', height: '300px' }}
/>
)
}
import { useRef, useEffect } from 'react'
import * as THREE from 'three'
import { Scene } from 'react-three-lite'
import type { GeoPoint, SceneComponents } from 'react-three-lite'
const ORIGIN: GeoPoint = { lng: 116.397, lat: 39.909 }
const PLACES: { point: GeoPoint; color: number }[] = [
{ point: ORIGIN, color: 0xffd166 },
{ point: { lng: 116.4005, lat: 39.909 }, color: 0x4fc3f7 }
]
function App() {
const objectsRef = useRef<THREE.Object3D[]>([])
const handleCreated = (scene: THREE.Scene, components: SceneComponents) => {
const { camera } = components
// A raised south-side viewpoint looks north over the landmarks; the scene
// places the camera from local metres and it looks back at the origin.
scene.setPosition(camera, { x: 0, y: 500, z: 700 })
camera.lookAt(0, 0, 0)
PLACES.forEach(({ point, color }) => {
const radius = 35
const geometry = new THREE.SphereGeometry(radius, 24, 16)
const material = new THREE.MeshBasicMaterial({ color })
const marker = new THREE.Mesh(geometry, material)
// 参考系已绑定在 scene 上,由 scene 自己完成投影。
scene.setPosition(marker, point)
// Rest the sphere on the ground plane instead of burying its lower half.
marker.position.y = radius
scene.add(marker)
objectsRef.current.push(marker)
})
}
useEffect(() => {
return () => {
objectsRef.current.forEach((object) => {
object.removeFromParent()
const mesh = object as THREE.Mesh
mesh.geometry?.dispose()
;(mesh.material as THREE.Material | undefined)?.dispose()
})
objectsRef.current = []
}
}, [])
return (
<Scene
rendererType="webgl"
origin={ORIGIN}
onCreated={handleCreated}
bgColor="rgb(40, 42, 54)"
style={{ marginTop: '10px', width: '100%', height: '300px' }}
/>
)
}
Scene 配置项
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| origin | GeoPoint | { lng: 0, lat: 0, alt: 0 } | 局部切平面原点,所有场景坐标都相对于它 |
| crs | 'EPSG:4326' | 'EPSG:3857' | 'EPSG:4326' | 平面模型:等距圆柱切平面或重新中心化的 Web Mercator |
| datum | 'WGS84' | 'GCJ02' | 'BD09' | 'WGS84' | 传入场景的坐标所使用的基准面 |
| axes | AxesMapping | { east: 'x', north: '-z', up: 'y' } | 将 ENU 基映射到带符号的 three.js 轴 |
| geo | GeoReference | — | 直接传入预先构建的参考系,替代 origin/crs/datum/axes |
Scene 构建出的参考系可在 onCreated(scene, components) 回调中通过
components.geo 获取,也可通过 useScene().geo 获取。它同时会绑定到 scene
自身,因此大多数代码完全不需要显式提到 geo。
物体定位
每个 Scene 都会把自己的 GeoReference 绑定到 scene 实例上,并安装一个统一的
定位方法 scene.setPosition(object, position, datum?)。由于 scene 已经持有参考系,
定位时只需给出坐标本身,无需再把参考系传来传去;mesh、group 和 camera 都是
THREE.Object3D,所以同一个调用写法可以定位任意对象。
position 接受所有常用形态(ScenePosition):
| 坐标形态 | 含义 |
|---|---|
{ lng, lat, alt? } | 地理点,经 scene 参考系投影 |
[lng, lat] | 地理点元组,高度默认 0 |
[lng, lat, alt] | 地理点元组 |
THREE.Vector3 | 局部米坐标,原样使用、不投影 |
{ x, y, z? } | 局部米坐标对象,z 默认 0 |
const handleCreated = (scene: THREE.Scene) => {
// 下面四种都是地理坐标,统一经由 scene 自身的参考系投影。
scene.setPosition(mesh, { lng: 116.397, lat: 39.909 })
scene.setPosition(mesh, { lng: 116.397, lat: 39.909, alt: 35 })
scene.setPosition(mesh, [116.397, 39.909])
scene.setPosition(mesh, [116.397, 39.909, 35])
// GCJ02/BD09 输入可按次传入 datum 覆盖场景的 datum。
scene.setPosition(mesh, [116.403, 39.915], 'GCJ02')
// 相机与 mesh 使用完全相同的调用。
scene.setPosition(camera, [116.397428, 39.90923, 120])
// 局部米坐标原样使用,不做投影。
scene.setPosition(helper, new THREE.Vector3(10, 0, -5))
scene.setPosition(helper, { x: 10, y: 0, z: -5 })
}
方法会返回传入的对象,支持链式调用。自行 new THREE.Scene() 创建的场景没有该
方法;可调用一次 bindSceneGeo(scene, geo) 安装,或通过 getSceneGeo(scene)
读取参考系。如果场景已安装方法但没有绑定参考系,地理元组/对象会回退为本地坐标,
把 lng/lat/alt 直接写到 x/y/z,因此该调用在任何场景下都是安全的。
| 辅助函数 | 签名 | 描述 |
|---|---|---|
| scene.setPosition | (object: Object3D, position: ScenePosition, datum?: DatumType) => Object3D | 定位任意对象;地理输入自动投影 ,局部米坐标原样使用 |
| scene.getGeo | () => GeoReference | undefined | 读取绑定在 scene 上的参考系 |
| bindSceneGeo | (scene: Scene, geo?: GeoReference) => Scene | 在 scene 上安装定位方法,并可选绑定参考系 |
| getSceneGeo | (scene: Scene) => GeoReference | undefined | scene.getGeo() 的函数形式 |
GeoReference 方法
| 名称 | 参数 | 描述 |
|---|---|---|
| constructor | (origin?: GeoPoint, options?: GeoReferenceOptions) => GeoReference | 构建参考系及其切平面 |
| toLocal | (p: GeoPoint, datum?: DatumType, target?: Vector3) => Vector3 | 地理坐标 -> 局部米制坐标 |
| toGeo | (v: Vector3, datum?: DatumType, target?: GeoPoint) => GeoPoint | 局部米制坐标 -> 地理坐标 |
| setPosition | (object: Object3D, p: GeoPoint, datum?: DatumType) => Object3D | 将已有物体放置到指定地理位置 |
| distance | (a: GeoPoint, b: GeoPoint, datum?: DatumType) => number | 平面距离(米) |
import { GeoReference } from 'react-three-lite'
import type { GeoPoint } from 'react-three-lite'
const origin: GeoPoint = { lng: 116.397, lat: 39.909 }
const geo = new GeoReference(origin, { crs: 'EPSG:4326', datum: 'WGS84' })
// 相对于原点的局部米制坐标(内部使用 float64 计算)。
const local = geo.toLocal({ lng: 116.4005, lat: 39.909 })
// 反向转换;混合多种数据源时可按次传入 datum。
const back = geo.toGeo(local)
// GCJ02(高德/腾讯)输入会被自动归一化。
const fromMars = geo.toLocal({ lng: 116.403, lat: 39.915 }, 'GCJ02')
相机取景
fitCameraToPoints 是一个与坐标系无关的统一取景点。它通过检查每个点的