外观
地形
约 3733 字大约 12 分钟
2023-09-08
地形是三维场景中的重要三维效果,能看到山峰的高低起伏效果,是三维场景的"骨骼", 如果想要看到真实感,地形数据(DEM)不可或缺。
Mars3D支持流式的、可视化的全球高程投影地形地势、水形数据,包括海洋、湖泊、河流、山峰、峡谷和其他能够被三维展示出来的且效果比二维好的地形数据。像图层数据一样, Mars3D引擎会从一个服务器上请求流式地形数据,仅请求那些基于当前相机能看到的需要绘制的图层上的数据。
目前一个三维场景只支持一个地形服务图层。

1. DEM地形数据介绍
DEM数据就是 数字高程模型,就是描述每个点位的高程数据而已,没有其他附加信息,更不会包含影像图,单独展示是看不出什么名堂的。DEM数据在ArcGIS中打开只能看到是灰度图。
1.1.terrain格式介绍
通常地形是*.tif格式,我们平台中需要处理转换为terrain瓦片格式才能使用。 terrain地形也是瓦片地图金字塔模型结构组成。 
terrain瓦片的根目录有个layer.json的地形服务入口文件,来描述地形服务的基本信息和结构,如:http://data.mars3d.cn/terrain/layer.json
terrain有2种格式:
Heightmap-1.0 :是Cesium早期使用的地形切片格式,每个瓦片存储65*65个高度值,通过Cesium API进行解析、构网、渲染、查询等。优点是切片简单,存储小,缺点是精度不高等。heightmap官方文档
Quantized-Math-1.0 : 是Cesium现在及未来使用的地形切片格式,会慢慢的替代Heightmap-1.0格式,因此,我们应该优先掌握该格式。quantized-mesh官方文档
tif转terrain: 地形数据处理教程
2. 加载地形
地形数据集是巨大的,通常都是GB或者TB级别。在普通3D引擎中,使用底层图形API去高效实现地形数据的可视化需要做很多事情。幸好Mars3D已经完成了这个体力活,而我们只需要写几行代码。地形数据单独是没法展示出来看的,只是一堆高程数据而已。结合影像图来展示,效果最佳。
2.1. 快速开始(初始化 new Map 时传入)
在构造Map时传入terrain参数说明
const map = new mars3d.Map('mars3dContainer', {
terrain: {
// type:'xyz',//默认未传入时代表 'xyz'
url: 'http://data.mars3d.cn/terrain',
show: true
},
})
// 关闭或开启地形,只需要修改下面属性
map.hasTerrain = false2.2. 通过修改 map.terrainProvider 属性控制地形。
在创建完map后,通过 mars3d.LayerUtil.createTerrainProvider 创建地形服务对象,并赋值给 map.terrainProvider。
// 设置新的地形服务
map.terrainProvider = mars3d.LayerUtil.createTerrainProvider({
type: 'arcgis',
url: 'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer',
})2.3 通过图层方式管理地形(多地形切换)
上面两种方式都是"一个球只用一个地形",如果项目需要在界面上让用户切换地形(例如"无地形 / 天地图地形 / 本地精细地形"),推荐使用地形图层 mars3d.layer.TerrainLayer,它和普通图层一样注册到图层管理里:
// 方式3:以图层的形式加载(一般用于图层管理中,多个地形的切换)
const terrainLayer = new mars3d.layer.TerrainLayer({
url: "http://data.mars3d.cn/terrain"
})
map.addLayer(terrainLayer)如果使用平台自带的底图/图层切换控件,也可以直接把地形服务列进 baseLayerPicker 的候选列表:
const map = new mars3d.Map("mars3dContainer", {
terrain: {
url: "http://data.mars3d.cn/terrain",
show: true
}
// control: {
// baseLayerPicker: true,
// terrainProviderViewModels: getTerrainProviderViewModelsArr() // 自定义地形可选数组
// }
})切换地形后,已经做的地形分析(开挖、压平、抬升、等高线等)会失效,需要监听地形变化事件重建:
map.on(mars3d.EventType.terrainChange, function () {
terrainClip.reload() // 重建开挖等分析效果
})2.4 常用地形服务清单
| type | 说明 | 配置示例 |
|---|---|---|
xyz(默认) | 标准 terrain 切片服务,平台处理后的地形数据 | { url: "http://data.mars3d.cn/terrain" } |
arcgis | ArcGIS 地形服务(ImageServer) | { type: "arcgis", url: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer" } |
ion | Cesium 官方在线地形 | { type: "ion", requestWaterMask: true, requestVertexNormals: true } |
tdt | 天地图地形服务 | { type: "tdt", url: "https://t{s}.tianditu.gov.cn/mapservice/swdx", key: "你的key", subdomains: "01234567" } |
none | 不使用地形 | map.terrainProvider = mars3d.LayerUtil.getNoTerrainProvider() |
// 无地形
map.terrainProvider = mars3d.LayerUtil.getNoTerrainProvider()
// 标准 terrain 切片
map.terrainProvider = mars3d.LayerUtil.createTerrainProvider({
url: "http://data.mars3d.cn/terrain"
})
// 天地图地形(也可以直接 new provider)
map.terrainProvider = new mars3d.provider.TdtTerrainProvider({
url: "https://t{s}.tianditu.gov.cn/mapservice/swdx",
key: mars3d.Token.tianditu, // 平台内置 token,超量后请自行申请替换
subdomains: "01234567"
})注意:
ion、arcgis等国外在线服务在国内网络环境下可能访问超时或被屏蔽,正式项目建议使用本地/自建地形服务(见第 7 节)。
2.5 地形加载事件与调试技巧
// 地形服务加载完成 / 失败
map.on(mars3d.EventType.terrainLoadSuccess, function (event) {
console.log("地形服务加载完成", event)
})
map.on(mars3d.EventType.terrainLoadError, function (event) {
console.log("地形服务加载失败", event)
})
// 地形切换
map.on(mars3d.EventType.terrainChange, function (event) {
console.log("地形已切换", event)
})调试时可以打印当前点击位置对应的地形瓦片 xyz 值,方便排查地形服务缺失的层级:
map.on(mars3d.EventType.click, function (event) {
const point = mars3d.LngLatPoint.fromCartesian(event.cartesian)
const tilingScheme = map.terrainProvider.tilingScheme
const level = map.terrainProvider._availability._maximumLevel
const xy = tilingScheme.positionToTileXY(point.toCartographic(), level)
const reverseY = tilingScheme.getNumberOfYTilesAtLevel(level) - xy.y - 1 // 地形是 TMS 服务
console.log(`当前坐标:${point.lng},${point.lat},地形瓦片 xyz:${xy.x},${reverseY},${level}`)
})查看地形三角网(网格)可以直观判断地形是否生效、精度是否足够:
map.scene.globe._surface.tileProvider._debug.wireframe = true // 显示地形三角网3. 深度检测
默认是可以看到地下的物体,如果需要看不到地形下面的矢量对象或模型,那么可以开启深度检测来达到需求。
// Enable depth testing so things behind the terrain disappear.
viewer.scene.globe.depthTestAgainstTerrain = true;4. 运行效果
新窗口查看
5. 地形夸张与高度取值
5.1 地形夸张
当地形起伏不明显(如平原地区)时,可以把地形高度按倍数放大,让起伏更直观:
// v1.116+ 版本
map.scene.verticalExaggeration = 10 // 地形夸张程度,默认 1.0
// v1.115 及之前版本
// map.scene.globe.terrainExaggeration = 10运行效果
新窗口查看
注意:地形夸张只改变"看起来的高度",不会改变地形数据的真实高程。因地形夸张后地表会被抬高,加载在它上面的矢量(尤其是通过高度取样放置的对象)也需要相应处理,否则会出现"对象陷进地形里"的现象。验收类项目(如量算、坡度)建议不要夸张。
5.2 从地形上取高度
三维里"对象贴不贴地"取决于高度值,常用以下几种取值方式:
// 1) 取某个经纬度处的地形高度
const height = map.getHeight({ lng: 117.21, lat: 31.8 })
console.log("地表高度", height)
// 2) 用屏幕点/笛卡尔坐标取地表点(常用于点击拾取)
const position = mars3d.PointUtil.getSurfacePosition(map.scene, cartesian)
// 3) 把一批坐标的高度统一设为 0(即"后续按地形实时取高",常用于分析区域)
positions = mars3d.PointUtil.setPositionsHeight(positions, 0)
// 4) 在原有高度上整体抬升(如通视分析抬高 1.5 米相当于人的身高)
const center = mars3d.PointUtil.addPositionsHeight(position, 1.5)矢量图形则通过样式参数控制贴地:
const graphic = new mars3d.graphic.PolylineEntity({
positions: positions,
style: {
color: "#ffff00",
width: 3,
clampToGround: true // 是否贴地形
}
})深度检测(
viewer.scene.globe.depthTestAgainstTerrain = true)与贴地是两个独立的事情:贴地决定对象"挂"在地形表面,深度检测决定被地形挡住的物体是否可见,两者通常搭配使用。
6. 地形分析
平台把常用的地形分析都封装成了 thing 对象,需要地形已加载(map.hasTerrain = true)才能生效:
| 功能 | 类名 | 说明 | 功能示例 |
|---|---|---|---|
| 地形开挖 | mars3d.thing.TerrainClip | 按区域把地形挖开,露出地下对象 | 查看 |
| 地形开挖(planes) | mars3d.thing.TerrainPlanClip | 基于剖切面实现的平面裁剪式开挖 | 查看 |
| 地形压平 | mars3d.thing.TerrainFlat | 把区域地形压到指定高度 | 查看 |
| 地形抬升 | mars3d.thing.TerrainUplift | 把区域地形整体抬高(填方) | 查看 |
| 等高线 | mars3d.thing.ContourLine | 按高差绘制等高线并分级着色 | 查看 |
| 坡度坡向 | mars3d.thing.Slope | 计算坡度、坡向并可视化 | 查看 |
| 淹没分析 | mars3d.thing.FloodByMaterial、FloodByGraphic | 模拟水位上涨淹没地形 | 查看 |
| 最短路径 | mars3d.thing.ShortestPath | 地形表面两点间最短通行路径 | 查看 |
这里给出一个"地形开挖"的完整例子,其余功能的详细参数与完整代码见Thing管理及分析功能:
const terrainClip = new mars3d.thing.TerrainClip({
stylePit: {
diffHeight: 900, // 开挖深度
image: "https://data.mars3d.cn/img/textures/poly-stone.jpg", // 井壁贴图
imageBottom: "https://data.mars3d.cn/img/textures/poly-soil.jpg", // 井底贴图
splitNum: 80 // 井边界插值数
}
})
map.addThing(terrainClip)
// 添加开挖区域
terrainClip.addArea([
[116.334222, 30.899171, 645.46],
[116.370874, 30.899171, 645.46],
[116.370874, 30.944509, 645.46],
[116.334222, 30.944509, 645.46]
])
// 地形切换后需要重建
map.on(mars3d.EventType.terrainChange, function () {
terrainClip.reload()
})运行效果
新窗口查看
7. 离线与私有化地形
内网、涉密、弱网环境下不能访问公网地形服务,需要自己准备并发布地形服务。
7.1 数据准备:从 DEM 到 terrain 服务
- 准备 DEM 原始数据(常见为
*.tif),注意坐标系统一为 WGS84 经纬度(或明确记录坐标系,便于前端配置crs)。 - 把
tif转成平台可直接加载的terrain瓦片(Cesium 的 heightmap / quantized-mesh 格式),处理流程见教程地形数据处理(tif 转 terrain)。 - 处理完成后目录中会得到
layer.json与分级目录,layer.json是地形服务的入口文件,前端需要访问它就是"地形服务地址"。
terrain/ ← 地形服务根目录(url 就填到这里)
├── layer.json ← 服务入口文件
├── 0/0/0.terrain
├── 1/...
└── ...7.2 发布与加载
把整个 terrain 目录放到 Web 服务器(如 Nginx)的静态目录下发布即可,前端地址指向目录:
// 内网发布后的加载方式,与在线服务写法完全一致
const map = new mars3d.Map("mars3dContainer", {
terrain: {
url: "http://192.168.1.100:8080/terrain", // 内网地形服务地址
show: true
}
})Nginx 静态发布与 MIME 配置(保证 .terrain 等文件能正常下载)见发布服务。
7.3 内网环境还需要离线哪些内容
只离线地形往往不够,一个完整的内网三维系统通常还需要:
| 资源 | 说明 | 参考 |
|---|---|---|
| 底图影像/电子地图 | 用 tif 切片或下载在线地图瓦片后本地发布 | 影像图切片处理、在线地图的下载和使用 |
| 地形 | 上文 7.1、7.2 | 地形数据处理 |
| 三维模型 | 3DTiles / glTF 本地化,注意 tileset.json 里引用的是相对路径 | 模型数据处理 |
| 前端依赖 | mars3d.js、mars3d-cesium、字体、图标、地图库,避免 CDN 引用 | 见安装与使用 |
| 服务接口 | 平台默认的 data.mars3d.cn、天地图、POI 查询等服务需替换为内网服务 | 见下方提示 |
重要:平台示例与文档中大量使用
https://data.mars3d.cn/...的演示数据与mars3d.Token内置 token,这些在纯内网环境下不可用,必须替换为本地资源。离线部署常见问题(跨域、token、瓦片 404 等)可以参考内网离线相关问题。
7.4 离线后的自检清单
- 浏览器直接访问
http://内网地址/terrain/layer.json,能返回 JSON 即服务正常。 - 控制台无跨域(CORS)报错;若前端与瓦片不同域,需要服务端配置 CORS 或做反向代理。
- 地图上开启"地形三角网"(见 2.5)确认地形确实生效。
- 随手量算一个点的高程,与 DEM 数据(如 GIS 工具中)比对,确认高程量纲与基准一致。
8. 地形常见问题
8.1 地形已加载,但看不出起伏?
- 视角问题:平原地形(高差几十米)在几万米视高下几乎看不出起伏,先缩放到 km 级查看,或临时开启地形夸张(见 5.1)。
- 地形没真正生效:用
map.hasTerrain判断,或打开地形三角网(见 2.5)看是否出现网格。 - 地形数据本身精度低:低精度地形(如全局 90m 数据)在小范围项目里表现就是"平的",需要局部高精度 DEM。
console.log("当前是否有地形:", map.hasTerrain)8.2 只有部分区域有地形,或出现 404?
一般是地形服务缺少某些层级或服务未覆盖分析区域:F12 查看网络面板,找出 404 的瓦片 URL(可从 2.5 的调试代码得到对应的 z/x/y),回到数据处理环节补齐该层级;跨域导致的失败则需服务端配置 CORS。
8.3 地下的管线/模型被地形挡住看不到?
这是正常的深度关系。两种做法:
// 做法1:开启深度检测,让被遮挡的对象"消失"(更真实)
map.scene.globe.depthTestAgainstTerrain = true
// 做法2:让地表半透明(地下模式),可以同时看到地表与地下
// 见 thing/analysis/underground 示例反过来,如果希望地下的对象始终可见,则要关闭深度检测(
false),此时贴地对象可能穿透地形显示,属于预期行为。
8.4 地形与影像/模型对不上位置?
常见原因是三者的坐标系或偏移不一致:影像可能是 EPSG:4490,模型可能是偏移坐标,地形是 WGS84。请统一确认:
- 瓦片图层配置
crs(EPSG:4326/EPSG:4490/EPSG:3857); - 数据图层配置
chinaCRS(GCJ02/BAIDU/WGS84)。
详见坐标系。
8.5 关闭地形后矢量对象位置变了?
同一份坐标在有地形/无地形时,若对象使用了 clampToGround 或高度为 0,显示位置会不同(一个贴地形表面、一个贴在椭球面)。做"有地形/无地形切换"的项目,建议统一按需重新取高或重新 setStyle,不要在两种状态下混用同一份高度值。
8.6 地形加载太慢/太卡?
- 只在一个球上使用一个地形服务,避免叠加;
- 内网部署时确认服务器开启了静态资源压缩(gzip)与缓存;
- 高精度地形文件较大,可按项目实际范围只处理需要的区域,不要全量加载;
- 地表透明(地下模式)、地形开挖等会增加渲染开销,按需开启。
