Cesium正式支持海底地球了,Cesium World Bathymetry 上手试用指南
最近刷到 Cesium ion 上出现了一套新的地形数据 Cesium World Bathymetry,之前使用 Cesium 做三维地球开发的时候,默认使用的地形数据是 Cesium World Terrain,那它们之间有什么不同呢?小编于是写了个 Demo,结果发现 Cesium World Bathymetry 这套数据集更偏向海底地形,比如海沟、珊瑚礁、近岸水深起伏等,今天小编就给大家做个介绍。
什么是 Cesium World Bathymetry
Cesium World Bathymetry 是一套全球覆盖的水深与地形瓦片,quantized-mesh 1.0格式,把多源水深与近岸地形集成到了一张全球底图。底库主要来自 GEBCO 2023,再叠上各地更高分辨率的水深成果,并补了内陆地形。
官方示例中的应用方向包括油气勘探、无人潜航器导航、海上风电场管理、气候相关分析等;对 GISer 来说,更常见的是海岸带三维展示、海底地貌科普,以及把自有 DEM 叠到这套全球底图上做对照。
基础数据信息:
官方给出的大致精度如下:
- 瑞士湖泊:约 1 m、2 m、3 m
- 美国东岸、美属岛屿,以及阿拉斯加、俄勒冈、加州部分区域:约 3 m、10 m
- 墨西哥湾北部:约 12 m
- 大堡礁:约 30 m
- 美国西海岸:约 90 m
- 全球其余区域:约 450 m
CesiumJS 上手示例
登录 Cesium ion,把 Cesium World Bathymetry 加到 My Assets。对应 Asset ID 是 2426648。较新的 CesiumJS 也可直接用 Cesium.Terrain.fromWorldBathymetry(),不必手写 ID。
下面这份示例中小编主要做了三处改动,一是加载 World Bathymetry,并请求顶点法线;二是关掉雾与地面大气、打开方向光,让海底起伏更清楚;三是用高程色带加等高线,并垂直夸张 3 倍,镜头飞到台湾西岸,把代码中的YOUR_ION_ACCESS_TOKEN 换成自己的 token 后运行即可,运行效果如图:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>Cesium World Bathymetry Demo</title>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.142/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.142/Build/Cesium/Widgets/widgets.css" rel="stylesheet" />
<style>
html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
Cesium.Ion.defaultAccessToken = "YOUR_ION_ACCESS_TOKEN";
const viewer = new Cesium.Viewer("cesiumContainer", {
terrain: Cesium.Terrain.fromWorldBathymetry({
requestVertexNormals: true,
}),
timeline: false,
animation: false,
baseLayerPicker: false,
geocoder: false,
});
const scene = viewer.scene;
const globe = scene.globe;
const camera = scene.camera;
// 水深可视化时,雾与地面大气容易糊掉起伏
scene.fog.enabled = false;
globe.showGroundAtmosphere = false;
globe.enableLighting = true;
globe.maximumScreenSpaceError = 1.0;
scene.verticalExaggeration = 3.0;
scene.light = new Cesium.DirectionalLight({
direction: new Cesium.Cartesian3(1, 0, 0),
});
const scratchNormal = new Cesium.Cartesian3();
scene.preRender.addEventListener(function () {
const surfaceNormal = globe.ellipsoid.geodeticSurfaceNormal(
camera.positionWC,
scratchNormal
);
Cesium.Cartesian3.negate(surfaceNormal, surfaceNormal);
scene.light.direction = Cesium.Cartesian3.normalize(
Cesium.Cartesian3.add(surfaceNormal, camera.rightWC, surfaceNormal),
scene.light.direction
);
});
// 高程色带 + 等高线(色阶参考 cmocean deep)
const minHeight = -10000.0;
const maxHeight = 2000.0;
const contourLineSpacing = 500.0;
const range = maxHeight - minHeight;
const d = (height) => (height - minHeight) / range;
function getColorRamp() {
const ramp = document.createElement("canvas");
ramp.width = 100;
ramp.height = 15;
const ctx = ramp.getContext("2d");
const grd = ctx.createLinearGradient(0, 0, 100, 0);
grd.addColorStop(d(maxHeight), "#B79E6C");
grd.addColorStop(d(100.0), "#FBFFEE");
grd.addColorStop(d(0.0), "#F9FCCA");
grd.addColorStop(d(-500.0), "#BDE7AD");
grd.addColorStop(d(-1000.0), "#81D2A3");
grd.addColorStop(d(-1500.0), "#5AB7A4");
grd.addColorStop(d(-2000.0), "#4C9AA0");
grd.addColorStop(d(-2500.0), "#437D9A");
grd.addColorStop(d(-4000.0), "#3E6194");
grd.addColorStop(d(-5000.0), "#424380");
grd.addColorStop(d(-8000.0), "#392D52");
grd.addColorStop(d(minHeight), "#291C2F");
ctx.fillStyle = grd;
ctx.fillRect(0, 0, ramp.width, ramp.height);
return ramp;
}
function getElevationContourMaterial() {
return new Cesium.Material({
fabric: {
type: "ElevationColorContour",
materials: {
contourMaterial: { type: "ElevationContour" },
elevationRampMaterial: { type: "ElevationRamp" },
},
components: {
diffuse:
"(1.0 - contourMaterial.alpha) * elevationRampMaterial.diffuse + contourMaterial.alpha * contourMaterial.diffuse",
alpha: "max(contourMaterial.alpha, elevationRampMaterial.alpha)",
},
},
translucent: false,
});
}
function updateGlobeMaterial() {
const material = getElevationContourMaterial();
const exaggeration = scene.verticalExaggeration;
let shadingUniforms = material.materials.elevationRampMaterial.uniforms;
shadingUniforms.image = getColorRamp();
shadingUniforms.minimumHeight = minHeight * exaggeration;
shadingUniforms.maximumHeight = maxHeight * exaggeration;
shadingUniforms = material.materials.contourMaterial.uniforms;
shadingUniforms.width = 1.0;
shadingUniforms.spacing = contourLineSpacing * exaggeration;
shadingUniforms.color = Cesium.Color.BLACK.withAlpha(0.5);
globe.material = material;
}
updateGlobeMaterial();
// 飞到台湾西岸,便于观察台湾海峡水深起伏
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(119.6, 24.0, 180000.0),
orientation: {
heading: Cesium.Math.toRadians(90.0),
pitch: Cesium.Math.toRadians(-45.0),
roll: 0.0,
},
duration: 2.5,
});
</script>
</body>
</html>
如果只想最小接入,也可以写成官方文档里的两行。
viewer.scene.setTerrain(
new Cesium.Terrain(Cesium.CesiumTerrainProvider.fromIonAssetId(2426648))
);
如果需要更详细的说明,可参考官方博客,比如影像底图选择、垂直夸张、等高线密度等都可以进行调整,另外目前的数据中大堡礁、马里亚纳海沟、菲律宾近海等分辨率相对高一点,可以用来做 Demo 演示。
对比 World Terrain
通过上述 Demo 可以看出,Cesium World Terrain 和 World Bathymetry 侧重点不一,一个关注陆地,一个关注海洋。如果目前的应用只聚焦在陆地,建议使用 Cesium World Terrain;如果偏向海洋,建议使用 World Bathymetry。当然也可以根据用户视角位置自动切换。需要注意的是,默认卫星影像在开阔洋面往往偏暗,水深起伏不好辨认,可参考小编的方式用色带把高程差表现出来,或者使用更亮的影像底图。
总结
之前使用 Cesium 做三维地球,海底区域经常是一块“平面蓝”,如果需要展示海底地形就需要自行切片水深 DEM 数据,数据处理和托管都需要一定成本。现在 Cesium 把 World Bathymetry 放进 ion 全球内容库之后,水下地形也能和陆地三维共用同一套接入方式,海岸带展示、海上风电示意、海底地貌这类需求会方便很多,对搞海洋GIS的小伙伴来说是一项不错的数据集。
目前仅有少数区域有高精度数据,后续官方也会更新,有兴趣的可以关注一下。有没有小伙伴做过类似场景?欢迎评论区留言分享。
参考
- Cesium World Bathymetry 产品页:https://cesium.com/platform/cesium-ion/content/cesium-world-bathymetry/
- Introducing Cesium World Bathymetry:https://cesium.com/blog/2024/01/23/introducing-cesium-world-bathymetry/
- Working with Cesium World Bathymetry in CesiumJS:https://cesium.com/blog/2024/01/29/cesium-world-bathymetry-in-cesiumjs/
相关阅读
声明
1.本文所分享的所有需要用户下载使用的内容(包括但不限于软件、数据、图片)来自于网络或者麻辣GIS粉丝自行分享,版权归该下载资源的合法拥有者所有,如有侵权请第一时间联系本站删除。
2.下载内容仅限个人学习使用,请切勿用作商用等其他用途,否则后果自负。