cesium超速入门总结

由于工作原因,做了一些有关 cesium 的三维地图开发。基于我所用到的功能,于此做一个入门总结,并记录下曾踩下的坑。初学者可依照以下整个完整流程,复现功能内容。做此记录时,cesium 版本为 ^1.64.0

注:这里虽然利用了 npmyarn 来快速安装 cesium ,但实际的demo只需直接引用文件,不需要打包


测试用demo路径结构描述

1
2
3
4
cesium-demo
├─node_modules/
├─index.html
└─package.json

准备工作

开发环境

建议使用 VSCode 进行开发,并安装插件 Live Server,用于启动此 index.html 文件

安装cesium

1
2
3
4
# 在 cesium-demo 目录下输入如下命令
npm install --save cesium
# 或者
yarn add cesium

index.html文件初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Cesium-test</title>
<script src="./node_modules/cesium/Build/Cesium/Cesium.js"></script>
<style>
@import url("./node_modules/cesium/Build/Cesium/Widgets/widgets.css");
html, body, #cesiumContainer {
width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
const viewer = new Cesium.Viewer('cesiumContainer'); // 这里直接传入的也可以是获取到的cesiumContainer的dom元素对象
</script>
</body>
</html>

初始界面预览

cesium地球

常用初始化配置

以下内容不会给出完整代码,但会给出完整核心代码片段,读者对照初始化 index.html 自行增添或者更改即可运行,若出现误差,可尝试把 cesium 版本调整到 ^1.64.0。以下 js 代码均直接在 script 标签全局命名空间中出现。

隐藏界面小控件

通过初试配置,将界面上默认显示的控件隐藏,以便进行自定义

1
2
3
4
5
6
7
8
9
10
const viewer = new Cesium.Viewer('cesiumContainer', {
animation: false, // 隐藏界面左下角控制动画的面板
baseLayerPicker: false, // 隐藏界面左上角地图底图的切换按钮
fullscreenButton: false, // 隐藏界面右下角全屏按钮
geocoder: false, // 隐藏界面右上角搜索按钮
homeButton: false, // 隐藏界面右上角初始化地球位置按钮
sceneModePicker: false, // 隐藏界面右上角视角切换按钮
timeline: false, // 隐藏正下方时间线
navigationHelpButton: false, // 隐藏右上角帮助按钮
});

仅仅是如此设置,底部还剩一段图片加文字信息,这里可以通过 css 手动隐藏。在 style 中添加如下代码。

1
2
3
.cesium-viewer-bottom {
display: none;
}

如此,一个干净完整的地球便出现了
去掉控件的地球

去掉光圈

如果有人觉得地球外层的光圈太扎眼,可以通过以下方式去除

1
2
3
4
const viewer = new Cesium.Viewer('cesiumContainer', {
// ...
skyAtmosphere: false,
});

效果如下
没有光晕的地球

聚焦框和信息框的隐藏

默认情况下,如果左键双击 cesium 地图上的实体内容,camera 视角会自动聚焦过去,并显示自带的聚焦框以及实体的信息框,如下
带有信息框和聚焦框
默认不显示聚焦框和信息框

1
2
3
4
5
const viewer = new Cesium.Viewer('cesiumContainer', {
// ...
selectionIndicator: false, // 隐藏聚焦框
infoBox: false, // 隐藏信息框
});

取消默认的左键双击实体效果,便于自定义其他交互

1
viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);

地形加载

初始化时默认加载地形

1
2
3
4
5
6
7
const viewer = new Cesium.Viewer('cesiumContainer', {
// ...
terrainProvider: Cesium.createWorldTerrain({
requestWaterMask: true,
requestVertexNormals: true
})
});

后期手动添加地形

1
2
3
4
viewer.terrainProvider = Cesium.createWorldTerrain({
requestWaterMask: true,
requestVertexNormals: true
});

:此方法加载地形时,加载的是 cesium 自己发布的地形服务。一旦 cesium 官方版本更新,则会导致地形加载失败(默认加载的底图也是如此)。浏览器控制台会报Token过期的错,但是实际上就算你更改了 Cesium.Ion.defaultAccessToken 的值仍然不能解决此问题。正确操作是,升级项目的 cesium 版本即可。最好的方法是,发布自己的地形以及底图服务,并加载。这样就不会受到 cesium 版本更新的影响。

底图服务加载

imageryProvider 除了下面示例的 ArcGisMapServerImageryProvider 以外,还有分别用于加载其他不同框架底图服务的构造函数,具体内容,可在官方文档中搜索 imageryProvider 查询。
初始化默认加载其他底图

1
2
3
4
5
6
const viewer = new Cesium.Viewer('cesiumContainer', {
// ...
imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
url: 'https://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer'
})
});

后期手动添加底图

1
2
3
viewer.imageryLayers.addImageryProvider(new Cesium.ArcGisMapServerImageryProvider({
url: 'https://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer'
}));

效果展示
黑暗
:此坑与地形等同

视角转换

1
2
3
4
5
6
7
8
9
10
11
12
viewer.scene.camera.flyTo({
destination: new Cesium.Cartesian3.fromDegrees(106.49673, 29.61736, 1500), // 经纬度,高度
orientation: {
heading: 0,
pitch: -0.52458664812464143,
roll: 2.0174972803488345e-11
},
duration: 2,
complete: () => {
// 飞行结束后执行逻辑
}
});

camera 相关的所有操作在此处查看

较为常用功能

以下内容,涉及到具体私人发布使用的服务部分,只给出最为核心的代码片段,如何实现。需要读者自行发布或寻找服务进行测试

geojson的加载使用

1
2
3
4
5
6
7
8
9
10
11
const jsonUrl = '...'; // 你的 geojson 文件路径,可以是本地相对路径
const mapJSON = Cesium.GeoJsonDataSource.load(jsonUrl, {
// 对 geojson 文件的初始配置,可在 https://cesium.com/docs/cesiumjs-ref-doc/GeoJsonDataSource.html 查看详情
});
viewer.dataSources.add(mapJSON); // 将其加载到地图上去
mapJSON.then(entities => {
// entities 是该 geojson 内部所有实体的数组,可对实体进行属性变更的操作
entities.forEach(entity => {
entity.show = false; // 隐藏该实体
});
});

针对高精度倾斜摄影加载及显示效果优化

倾斜摄影数据以 3dtile 的形式加载出来,通常情况下,只需要有 url 就足够了。以下参数是由于倾斜摄影数据总数据太大,清晰度过高,从而调节参数进行性能优化。这不是一个通用的解决方案,但可以以此为参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const tileSetPromise = new Cesium.Cesium3DTileset({
url: '...', // 你的倾斜摄影服务发布地址
skipLevelOfDetail: true,
baseScreenSpaceError: 1024,
maximumScreenSpaceError: 256, // 数值加大,能让最终成像变模糊
skipScreenSpaceErrorFactor: 16,
skipLevels: 1,
immediatelyLoadDesiredLevelOfDetail: false,
loadSiblings: true, // 如果为true则不会在已加载完概况房屋后,自动从中心开始超清化房屋
cullWithChildrenBounds: true,
cullRequestsWhileMoving: true,
cullRequestsWhileMovingMultiplier: 10, // 值越小能够更快的剔除
preloadWhenHidden: true,
preferLeaves: true,
maximumMemoryUsage: 128, // 内存分配变小有利于倾斜摄影数据回收,提升性能体验
progressiveResolutionHeightFraction: 0.5, // 数值偏于0能够让初始加载变得模糊
dynamicScreenSpaceErrorDensity: 0.1, // 数值加大,能让周边加载变快
dynamicScreenSpaceErrorFactor: 1, // 不知道起了什么作用没,反正放着吧先
dynamicScreenSpaceError: true // 根据测试,有了这个后,会在真正的全屏加载完之后才清晰化房屋
});
tileSetPromise.readyPromise.then(tileSet => {
this.scene.primitives.add(tileSet); // 将倾斜摄影实体加载到地图上
this.changeHeight(tileSet, 310); // 将此 tileSet 提高 310 米
});

// 可通过此函数,来修改 tileSet 的高度
function changeHeight(tileSet, height) {
height = Number(height);
if (isNaN(height)) { return; }
const cartographic = Cesium.Cartographic.fromCartesian(tileSet.boundingSphere.center);
const surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, cartographic.height);
const offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, height);
const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
tileSet.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
}

限制camera高度的上下限

1
2
viewer.scene.screenSpaceCameraController.maximumZoomDistance = 65000; // 相机高度的最大值设定为 65000 米
viewer.scene.screenSpaceCameraController.minimumZoomDistance = 1000; // 相机高度的最小值设定为 1000 米

事件绑定

1
2
3
4
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(movement => {
// movement.position 是一个 Cartesian2 对象,可以通过它拿到所点击的屏幕坐标
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); // 监听左键点击

通过Cartesian2获得经纬度的方式

1
2
3
4
5
6
7
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(movement => {
const cartesian = this.scene.globe.pick(this.camera.getPickRay(movement.position), this.scene); // 获取空间坐标
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const lon = Cesium.Math.toDegrees(cartographic.longitude); // 经度
const lat = Cesium.Math.toDegrees(cartographic.latitude); // 纬度
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); // 监听左键点击

地图二三维转化

1
2
3
4
// 地图转化为三维立体状态
viewer.scene.morphTo3D(0);
// 地图转化为二维平面状态
viewer.scene.morphTo2D(0);
好饿,想吃两个小饼饼(# ̄y▽ ̄)╭
0%