目录
[一、准备工作:申请天地图 Key](#一、准备工作:申请天地图 Key)
[3.1 页面基础与 Cesium 环境引入](#3.1 页面基础与 Cesium 环境引入)
[3.2 Viewer 初始化与相机交互参数配置](#3.2 Viewer 初始化与相机交互参数配置)
[3.3 天地图影像、国界瓦片图层加载](#3.3 天地图影像、国界瓦片图层加载)
[3.4 📌重点:天地图地形瓦片加载](#3.4 📌重点:天地图地形瓦片加载)
[3.5 三维地名 WTFS 服务接入](#3.5 三维地名 WTFS 服务接入)
[3.6 📌重点:地球自转功能实现](#3.6 📌重点:地球自转功能实现)
前言
对于 WebGIS 的初学者来说,搭建一个可以运行的三维数字地球,是非常有成就感的入门实践。很多同学刚接触 Cesium 的时候,第一想法就是做出一个可以旋转、带有真实地形起伏的地球,但是直接使用官方示例,大多对接的是国外地图服务,网络访问不稳定,对国内开发者并不友好。

天地图作为国家地理信息公共服务平台,提供了免费可供开发者调用的影像、国界、高程地形、三维地名等全套三维 GIS 服务。同时官方提供了适配好的 Cesium 扩展库,不用我们从零去做投影转换、瓦片格式适配的底层工作。本文就是一套完整可复现的实战案例,从零搭建三维地球,加载天地图地形瓦片还原真实地表高低起伏,并且通过相机接口实现地球自动自转动画效果。非常适合 GIS 新手拿来练习学习,完整 HTML 示例,只需要替换自己申请的天地图 Key,借助简单的 Web 服务即可直接运行看到效果。
注意:示例全部引用天地图官方 CDN 提供 Cesium 以及扩展插件,不需要本地下载、编译部署 Cesium 库,降低新手的上手门槛。
一、准备工作:申请天地图 Key
·访问天地图开发者控制台申请开发 Key:具体在天地图中注册并且生成访问key的方法在之前的博客中已经介绍过,需要的朋友可以查阅之前的博文。
- 注册并登录天地图开发者控制台;
- 创建应用,应用类型选择浏览器端 Key;
- 将代码中
token = '你申请的天地图key'替换成自己获取到的 tk 密钥。
⚠️新手特别注意:浏览器端 Key 支持配置域名白名单,如果没有配置白名单,本地直接双击打开 html 文件(file 本地协议访问)会出现 403 鉴权报错。本地调试推荐使用 VS Code 的 Live Server 插件、Nginx 等简易 Web 服务运行项目,避免跨域与鉴权问题。

二、完整代码展示
<!DOCTYPE html>
<html lang="en">
<head>
<title>天地图三维服务接入</title>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/demo/sanwei/static/cesium/Cesium.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/Cesium_ext_min.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/long.min.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/bytebuffer.min.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/protobuf.min.js"></script>
<link rel="stylesheet" cesium="true" href="https://api.tianditu.gov.cn/cdn/demo/sanwei/static/cesium/Widgets/widgets.css">
<style>
html, body{
margin: 0;
padding: 0;
overflow: hidden;
}
#cesiumContainer {
width: 100%;
height: 100%;
}
#cesiumContainer .cesium-viewer-bottom{
display: none;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
var token = '你申请的天地图key';
// 服务域名
var tdtUrl = 'https://t{s}.tianditu.gov.cn/';
// 服务负载子域
var subdomains=['0','1','2','3','4','5','6','7'];
// cesium 初始化
var viewer = new Cesium.Map('cesiumContainer', {
shouldAnimate: true, //是否允许动画
selectionIndicator: false,
baseLayerPicker: false,
fullscreenButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
timeline: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
showRenderLoopErrors: false,
shadows: false,
});
// 抗锯齿
viewer.scene.fxaa = true;
viewer.scene.postProcessStages.fxaa.enabled=false;
// 水雾特效
viewer.scene.globe.showGroundAtmosphere = true;
// 设置最大俯仰角,[-90,0]区间内,默认为-30,单位弧度
viewer.scene.screenSpaceCameraController.constrainedPitch = Cesium.Math.toRadians(-20);
viewer.scene.screenSpaceCameraController.autoResetHeadingPitch = false;
viewer.scene.screenSpaceCameraController.inertiaZoom = 0.5;
viewer.scene.screenSpaceCameraController.minimumZoomDistance = 50;
viewer.scene.screenSpaceCameraController.maximumZoomDistance = 20000000;
viewer.scene.screenSpaceCameraController.zoomEventTypes = [
Cesium.CameraEventType.RIGHT_DRAG,
Cesium.CameraEventType.WHEEL,
Cesium.CameraEventType.PINCH,
];
viewer.scene.screenSpaceCameraController.tiltEventTypes = [
Cesium.CameraEventType.MIDDLE_DRAG,
Cesium.CameraEventType.PINCH,
{
eventType: Cesium.CameraEventType.LEFT_DRAG,
modifier: Cesium.KeyboardEventModifier.CTRL,
},
{
eventType: Cesium.CameraEventType.RIGHT_DRAG,
modifier: Cesium.KeyboardEventModifier.CTRL,
},
];
// 取消默认的双击事件
viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
// 叠加影像服务
var imgMap = new Cesium.UrlTemplateImageryProvider({
url: tdtUrl + 'DataServer?T=img_w&x={x}&y={y}&l={z}&tk=' + token,
subdomains: subdomains,
tilingScheme : new Cesium.WebMercatorTilingScheme(),
maximumLevel : 18
});
viewer.imageryLayers.addImageryProvider(imgMap);
// 叠加国界服务
var iboMap = new Cesium.UrlTemplateImageryProvider({
url: tdtUrl + 'DataServer?T=ibo_w&x={x}&y={y}&l={z}&tk=' + token,
subdomains: subdomains,
tilingScheme : new Cesium.WebMercatorTilingScheme(),
maximumLevel : 10
});
viewer.imageryLayers.addImageryProvider(iboMap);
// -------------------【重点:加载天地图地形瓦片】-------------------
var terrainUrls = new Array();
for (var i = 0; i < subdomains.length; i++){
var url = tdtUrl.replace('{s}', subdomains[i]) + 'mapservice/swdx?T=elv_c&tk=' + token;
terrainUrls.push(url);
}
var provider = new Cesium.GeoTerrainProvider({
urls: terrainUrls
});
viewer.terrainProvider = provider;
// 将三维球定位到中国
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(103.84, 31.15, 17850000),
orientation: {
heading : Cesium.Math.toRadians(348.4202942851978),
pitch : Cesium.Math.toRadians(-89.74026687972041),
roll : Cesium.Math.toRadians(0)
},
complete:function callback() {
// 定位完成之后的回调函数
}
});
// 叠加三维地名服务
var wtfs = new Cesium.GeoWTFS({
viewer,
subdomains:subdomains,
metadata:{
boundBox: {
minX: -180,
minY: -90,
maxX: 180,
maxY: 90
},
minLevel: 1,
maxLevel: 20
},
depthTestOptimization: true,
dTOElevation: 15000,
dTOPitch: Cesium.Math.toRadians(-70),
aotuCollide: true, //是否开启避让
collisionPadding: [5, 10, 8, 5], //开启避让时,标注碰撞增加内边距,上、右、下、左
serverFirstStyle: true, //服务端样式优先
labelGraphics: {
font:"28px sans-serif",
fontSize: 28,
fillColor:Cesium.Color.WHITE,
scale: 0.5,
outlineColor:Cesium.Color.BLACK,
outlineWidth: 2,
style:Cesium.LabelStyle.FILL_AND_OUTLINE,
showBackground:false,
backgroundColor:Cesium.Color.RED,
backgroundPadding:new Cesium.Cartesian2(10, 10),
horizontalOrigin:Cesium.HorizontalOrigin.LEFT,
verticalOrigin:Cesium.VerticalOrigin.TOP,
eyeOffset:Cesium.Cartesian3.ZERO,
pixelOffset: new Cesium.Cartesian2(5, 5),
disableDepthTestDistance:undefined
},
billboardGraphics: {
horizontalOrigin:Cesium.HorizontalOrigin.CENTER,
verticalOrigin:Cesium.VerticalOrigin.CENTER,
eyeOffset:Cesium.Cartesian3.ZERO,
pixelOffset:Cesium.Cartesian2.ZERO,
alignedAxis:Cesium.Cartesian3.ZERO,
color:Cesium.Color.WHITE,
rotation:0,
scale:1,
width:18,
height:18,
disableDepthTestDistance:undefined
}
});
wtfs.getTileUrl = function(){
return tdtUrl + 'mapservice/GetTiles?lxys={z},{x},{y}&VERSION=1.0.0&tk='+ token;
}
wtfs.getIcoUrl = function(){
return tdtUrl + 'mapservice/GetIcon?id={id}&tk='+ token;
}
wtfs.initTDT([{"x":6,"y":1,"level":2,"boundBox":{"minX":90,"minY":0,"maxX":135,"maxY":45}},{"x":7,"y":1,"level":2,"boundBox":{"minX":135,"minY":0,"maxX":180,"maxY":45}},{"x":6,"y":0,"level":2,"boundBox":{"minX":90,"minY":45,"maxX":135,"maxY":90}},{"x":7,"y":0,"level":2,"boundBox":{"minX":135,"minY":45,"maxX":180,"maxY":90}},{"x":5,"y":1,"level":2,"boundBox":{"minX":45,"minY":0,"maxX":90,"maxY":45}},{"x":4,"y":1,"level":2,"boundBox":{"minX":0,"minY":0,"maxX":45,"maxY":45}},{"x":5,"y":0,"level":2,"boundBox":{"minX":45,"minY":45,"maxX":90,"maxY":90}},{"x":4,"y":0,"level":2,"boundBox":{"minX":0,"minY":45,"maxX":90,"maxY":90}},{"x":6,"y":2,"level":2,"boundBox":{"minX":90,"minY":-45,"maxX":135,"maxY":0}},{"x":6,"y":3,"level":2,"boundBox":{"minX":90,"minY":-90,"maxX":135,"maxY":-45}},{"x":7,"y":2,"level":2,"boundBox":{"minX":135,"minY":-45,"maxX":180,"maxY":0}},{"x":5,"y":2,"level":2,"boundBox":{"minX":45,"minY":-45,"maxX":90,"maxY":0}},{"x":4,"y":2,"level":2,"boundBox":{"minX":0,"minY":-45,"maxX":45,"maxY":0}},{"x":3,"y":1,"level":2,"boundBox":{"minX":-45,"minY":0,"maxX":0,"maxY":45}},{"x":3,"y":0,"level":2,"boundBox":{"minX":-45,"minY":45,"maxX":0,"maxY":90}},{"x":2,"y":0,"level":2,"boundBox":{"minX":-90,"minY":45,"maxX":-45,"maxY":90}},{"x":0,"y":1,"level":2,"boundBox":{"minX":-180,"minY":0,"maxX":-135,"maxY":45}},{"x":1,"y":0,"level":2,"boundBox":{"minX":-135,"minY":45,"maxX":-90,"maxY":90}},{"x":0,"y":0,"level":2,"boundBox":{"minX":-180,"minY":45,"maxX":-135,"maxY":90}}]);
// -------------------【重点:实现地球自转】-------------------
// 开启时钟动画
viewer.clock.shouldAnimate = true;
// 每帧旋转弧度,数值越大转速越快
const angle = 0.001;
// 时钟每帧更新回调
viewer.clock.onTick.addEventListener(() => {
// 相机绕世界Z轴旋转,模拟地球自转
// UNIT_Z:地心垂直向上Z轴;angle:旋转弧度,正值逆时针,负值顺时针
viewer.scene.camera.rotate(Cesium.Cartesian3.UNIT_Z, angle);
});
</script>
</body>
</html>
三、核心模块解析
3.1 页面基础与 Cesium 环境引入
很多刚接触 Cesium 的新手,第一步就会被本地部署 Cesium 库难住,需要下载庞大的资源包、处理静态资源路径。在这个案例中,我们直接全部引用天地图官方 CDN 资源,无需本地部署 Cesium。除了基础的 Cesium.js,还必须引入天地图的扩展库Cesium_ext_min.js。天地图封装的地形提供者GeoTerrainProvider、三维地名GeoWTFS全部都在这个扩展 JS 里面,如果缺失该脚本,代码直接会报对象未定义的错误。
javascript
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/demo/sanwei/static/cesium/Cesium.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/Cesium_ext_min.js"></script>
页面 CSS 部分把 html、body 设置为宽高 100% 并且隐藏滚动条,保证三维容器占满浏览器整个视口;同时隐藏 Cesium 默认底部版权水印 DOM,得到一个干净的三维画布。

3.2 Viewer 初始化与相机交互参数配置
这是天地图二次封装的对象,内部做了很多适配天地图服务的逻辑,如果直接替换成 Viewer,部分扩展功能会失效。初始化的时候关闭了 Cesium 自带的大量 UI 控件,包括图层选择器、全屏按钮、搜索地理编码器、时间轴、帮助提示等等,目的是得到纯净的三维地球效果。
同时我们针对相机交互做了细致的约束:限制最大俯仰角度、缩放的最近和最远距离,自定义拖拽、滚轮、手势的事件类型,禁用双击放大。对于新手来说,读懂这一部分参数,可以理解 Cesium 相机控制的底层逻辑,后续做自定义漫游的时候会受益匪浅。
javascript
// cesium 初始化
var viewer = new Cesium.Map('cesiumContainer', {
shouldAnimate: true, //是否允许动画
selectionIndicator: false,
baseLayerPicker: false,
fullscreenButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
timeline: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
showRenderLoopErrors: false,
shadows: false,
});
3.3 天地图影像、国界瓦片图层加载
WebGIS 三维地图的画面,本质上是由无数张分级的瓦片图片拼接而成。
img_w代表天地图卫星影像瓦片服务,提供真实卫星拍摄的地表影像;ibo_w为国界注记瓦片,叠加在影像上层,用来渲染国家边界线。
UrlTemplateImageryProvider是 Cesium 通用的瓦片影像加载类,配置 subdomains 子域名数组可以实现浏览器多请求并发,加快瓦片加载速度;tilingScheme 指定 Web 墨卡托投影,maximumLevel 设置瓦片最大请求层级。最后调用addImageryProvider把图层添加到场景图层集合当中。新手可以在这里尝试调整图层顺序,体会多图层叠加渲染的逻辑。

3.4 📌重点:天地图地形瓦片加载
地形加载是本案例的核心难点,也是很多新手踩坑重灾区。很多同学运行代码之后,看到影像正常加载,但是地球始终是光滑的球面,看不到高山、盆地的起伏,问题几乎都出在这里。
天地图的高程瓦片拥有自己独特的数据格式,不能使用 Cesium 原生的CesiumTerrainProvider,必须使用天地图扩展的GeoTerrainProvider 。天地图地形服务接口地址为mapservice/swdx?T=elv_c&tk=token,我们循环遍历子域数组生成多域名请求地址数组,传入 GeoTerrainProvider,最后赋值给viewer.terrainProvider,场景就会启用高程数据渲染真实地形。

小实验:新手可以注释掉
viewer.terrainProvider = provider;这一行,刷新页面对比前后效果,直观感受地形服务开启和关闭的差异。
3.5 三维地名 WTFS 服务接入
GeoWTFS是天地图提供的三维地名标注服务,可以在三维球体上渲染全球城市地名标签,支持标签避让、字体样式、描边、偏移量等大量自定义配置。getTileUrl用来定义地名瓦片请求地址;getIcoUrl获取地名配套图标资源;wtfs.initTDT()传入全球瓦片范围配置,完成地名服务初始化。新手可以修改 labelGraphics 里面字体大小、颜色、描边宽度,观察页面地名标签的变化,练习三维标签样式调整。

3.6 📌重点:地球自转功能实现
网上有不少地球自转的示例,部分方案是修改球体本身的旋转,这种方式会造成地形、标签、实体全部发生扭曲,体验很差。
本案例采用业界更推荐的实现思路:地理数据球体本身保持静止,移动相机的位置来模拟自转效果。
- 开启 clock 时钟动画
viewer.clock.shouldAnimate = true; - 定义每帧旋转弧度 angle,数值越大转速越快,正数逆时针旋转,负数顺时针旋转;
- 监听
viewer.clock.onTick事件,该事件会在引擎每一帧渲染前触发,调用camera.rotate()让相机绕地心 Z 轴Cartesian3.UNIT_Z持续旋转。
代码如下:
javascript
// 1. 开启时钟动画(可选,但建议开启)
viewer.clock.shouldAnimate = true;
// 2. 定义每帧旋转的弧度,数值越大转得越快
// 例如,这里设置每帧旋转 0.001 弧度
const angle = 0.001;
// 3. 监听 clock.onTick 事件
viewer.clock.onTick.addEventListener(() => {
// 让相机绕 Z 轴(垂直轴)旋转
// 参数1: 旋转轴 (Cartesian3.UNIT_Z 表示垂直向上)
// 参数2: 旋转角度(弧度),正值为逆时针,负值为顺时针[reference:3][reference:4]
viewer.scene.camera.rotate(Cesium.Cartesian3.UNIT_Z, angle);
});
💡新手拓展练习:可以定义按钮,点击移除 onTick 监听,实现 "开启自转 / 停止自转" 的交互;修改 angle 数值,体验不同旋转速度。
四、新手入门实操提示
- 直接复制完整 HTML 代码,不要遗漏任何 CDN 的 script 标签,缺失脚本会直接报错;
- 优先使用 Live Server、Nginx 等 Web 服务运行,拒绝双击 html 用 file 协议打开;
- 密钥申请完成后记得配置域名白名单,本地调试可以配置localhost;
- 调试过程善用浏览器 F12 开发者工具:Console 控制台看 JS 报错,Network 网络面板查看瓦片、地形请求是否成功;
- 不要直接大段修改代码,建议小改动、刷新看效果,理解每一段代码的作用之后,再进行二次开发。
完整的视频效果如下:
基于天地图三维Cesium地球自转实践
这个案例覆盖了密钥鉴权、瓦片图层、地形加载、相机动画、三维标注多个 WebGIS 高频知识点,非常适合作为 Cesium 入门练手项目。大家可以基于这份 Demo 做二次修改,慢慢扩展自己的功能。
五、常见踩坑说明
- 页面空白,瓦片不加载
- 检查 token 密钥是否正确;浏览器 F12 看网络请求 403,大概率 key 域名白名单没有配置。
- 不能直接 file 打开 html,需要部署 web 服务访问。
- 地形没有起伏,全是平面
- 确认使用的是
Cesium.GeoTerrainProvider,不要用原生 TerrainProvider; - 网络面板检查地形瓦片请求是否正常返回。
- 确认使用的是
- 地球自转不生效
- 确认
viewer.clock.shouldAnimate=true; - 不要把 rotate 放到 render 循环,推荐使用 clock.onTick。
- 确认
- 三维地名不显示
- WTFS 服务依赖天地图扩展 JS,确认 CDN 脚本加载成功。
六、总结与拓展方向
本篇实战案例基于天地图官方 Cesium 三维扩展库,完整实现了卫星影像图层、国界线图层、天地图高程地形、全球三维地名,并且通过相机旋转实现流畅的地球自转动画。整套代码降低了新手的学习成本,不需要搭建复杂工程,一个 HTML 文件就可以跑起来完整的三维地球。对于国内开发者来说,天地图服务网络访问稳定,摆脱对国外地图服务依赖。学会基础运行之后,大家还可以继续拓展更多功能:添加点位标记、弹窗信息、轨迹漫游动画、做自转启停开关、调速滑块、叠加自定义业务矢量图层等。希望本篇文章能够帮助更多 WebGIS 新手快速入门 Cesium 三维开发。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。