一、现成的源码调试文件
源码目录结构详解中已经介绍过,Apps文件夹下的文件是基于 Source 目录下的源码直接创建出来的现成的页面程序。所以,可以直接使用Apps里的示例进行ceisum源码调试。
例如,CesiumViewer示例,Apps\CesiumViewer\index.html为入口文件,运行结果如下图所示。
二、加载地球
为了更简单的学习,简化了Apps\CesiumViewer\index.html和Apps\CesiumViewer\CesiumViewer.js两个文件,实现调用源码去加载地球的功能。如果源码部署在了IIS服务器上,修改代码后直接在浏览器刷新或强制刷新,则即时生效。
Apps\CesiumViewer\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<title>Cesium Viewer</title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="CesiumViewer.css" media="screen" />
</head>
<body style="background: #000">
<-- 定义地图容器 -->
<div id="cesiumContainer" class="fullWindow"></div>
<-- 引入脚本文件 -->
<script src="CesiumViewer.js" type="module"></script>
</body>
</html>
在源码项目构建打包(运行 build 指令)之后,会出现Source 目录的入口索引文件,文件名是 Cesium.js,以供按需使用 Source源码目录下面对应的功能模块。
Apps\CesiumViewer\CesiumViewer.js
如果是加载地球和默认的基础部件的话,需调用Viewer 功能模块,如下所示。
window.CESIUM_BASE_URL = "../../Source/";
import { Viewer } from "../../Source/Cesium.js";
function main() {
// 最高端的食材,往往只需要最简单的烹饪。加载一个cesium地球,仅仅只需要一行代码。
let viewer = new Viewer("cesiumContainer");
}
main();
如果只是加载一个干净的地球,不加载地图部件儿,只需调用CesiumWidget功能模块,如下所示。
window.CESIUM_BASE_URL = "../../Source/";
import { CesiumWidget } from "../../Source/Cesium.js";
function main() {
const cesiumWidget = new CesiumWidget(document.getElementById("cesiumContainer"));
}
main();
三、源码分析——Viewer.js
从Cesium.js文件中,可以找到Viewer功能模块代码位于./Widgets/Viewer/Viewer.js
export { default as Viewer } from './Widgets/Viewer/Viewer.js';
打开Viewer.js文件后发现有两千多行代码,不要慌,一步一步来,直接Ctrl+F搜索 function Viewer进行定位。仔细研读之后,发现Viewer也是一个地球的壳子。在此先简单解读一下Viewer.js脚本。
大致过程就是:
- 获取dom容器 →
- 判断options参数,若不存在则 freeze 冻结赋值空对象 →
- 使用CesiumWidget创建地球 →
- 初始化地图小部件(Selection Indicator、Info Box、Main Toolbar、Geocoder、HomeButton、SceneModePicker、BaseLayerPicker、Navigation Help Button、Animation、Timeline、Fullscreen、VR等等) →
- 最后,将以上初始化的对象,全部注册注册为当前Viewer实例的属性,并将其中一些对象例如dataSourceCollection等公共函数API和私有函数API一并注册到Viewer的原型链上(Object.defineProperties)附上Ceisum API地址→
- 另外Cesium还默认为cesiumWidget注册了屏幕操作事件的点击、双击事件,方便初始化完成后能通过点击来拾取场景中的Entity
需要注意,使用CesiumWidget创建地球之前,源码中还有很多判断与赋值,这是因为 使用cesiumWidget方法创建地球的options参数中,有几项参数是根据new Viewer(container,options)中传入的container和options参数来进行二次判断筛选来进行赋值的,主要包括:
判断是否使用BaseLayerPicker部件 → 嵌套div,创建地图div容器和版权div容器 → 获取时钟模型 → 判断是否仅使用3d场景,
所以,官方的代码写的逻辑,一定要仔细去理解,不要被乱象迷失 而 轻言放弃!
function Viewer(container, options) {
// 判断是否传入domid,如果没有传值,则抛出错误,defined方法:判断参数是否为undifined或null,如果不是则返回true。
if (!defined(container)) {
throw new DeveloperError("container is required.");
}
// 根据domid获取DOM元素,使用getElement方法返回dom元素【document.getElementById()】。
container = getElement(container);
// 判断是否传入options,如果为空,则使用预设值:defaultValue.EMPTY_OBJECT:{}
// defaultValue方法:判断第一个参数如果不存在,则把第二个参数作为它的值返回,如果存在,那就返回它本身。
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
// 判断options是否存在globe属性、baseLayerPicker属性,且判断值不为false
// 如果都不存在,则createBaseLayerPicker为true,即使用BaseLayerPicker控件
const createBaseLayerPicker = (!defined(options.globe) || options.globe !== false) && (!defined(options.baseLayerPicker) || options.baseLayerPicker !== false);
// 如果不使用BaseLayerPicker控件,但是指定了selectedImageryProviderViewModel参数,则抛出无效参数的错误
if (!createBaseLayerPicker && defined(options.selectedImageryProviderViewModel)) {
throw new DeveloperError("options.selectedImageryProviderViewModel is not available when not using the BaseLayerPicker widget. Either specify options.imageryProvider instead or set options.baseLayerPicker to true.");
}
// 如果不使用BaseLayerPicker控件,但是指定了selectedTerrainProviderViewModel参数,则抛出无效参数的错误
if (!createBaseLayerPicker && defined(options.selectedTerrainProviderViewModel)) {
throw new DeveloperError("options.selectedTerrainProviderViewModel is not available when not using the BaseLayerPicker widget. Either specify options.terrainProvider instead or set options.baseLayerPicker to true.");
}
// 为了避免有this指向错误问题,定义了that属性
const that = this;
// 在我们创建的id为cesiumContainer容器div里面嵌套一个class为cesium-viewer的div
const viewerContainer = document.createElement("div");
viewerContainer.className = "cesium-viewer";
container.appendChild(viewerContainer);
// 在class为cesium-viewer的div里再嵌套一个class为cesium-viewer-cesiumWidgetContainer的div,
// 专门存放地球的div容器,作为Cesium widget container
const cesiumWidgetContainer = document.createElement("div");
cesiumWidgetContainer.className = "cesium-viewer-cesiumWidgetContainer";
viewerContainer.appendChild(cesiumWidgetContainer);
// 在class为cesium-viewer的div里再嵌套一个class为cesium-viewer-bottom的div,用于显示地球底部的版权注释信息
const bottomContainer = document.createElement("div");
bottomContainer.className = "cesium-viewer-bottom";
viewerContainer.appendChild(bottomContainer);
// 判断options.scene3DOnly参数是否存在,如果没有则默认为false,即是否仅使用3d场景
const scene3DOnly = defaultValue(options.scene3DOnly, false);
let clockViewModel;
let clock;
let destroyClockViewModel = false;
// 判断options.clockViewModel是否存在,若存在则使用传入的时钟模型,否则使用系统默认的时钟模型
if (defined(options.clockViewModel)) {
clockViewModel = options.clockViewModel;
clock = clockViewModel.clock;
} else {
clock = new Clock();
clockViewModel = new ClockViewModel(clock);
destroyClockViewModel = true;
}
// 判断options.shouldAnimate是否存在,若存在则将时钟的同名属性设为同样的值。
if (defined(options.shouldAnimate)) {
clock.shouldAnimate = options.shouldAnimate;
}
// 调用CesiumWidget模块,创建地球
const cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
imageryProvider:
createBaseLayerPicker || defined(options.imageryProvider)
? false
: undefined,
clock: clock,
creditContainer: defined(options.creditContainer)
? options.creditContainer
: bottomContainer,
scene3DOnly: scene3DOnly,
//......省略一些options属性值
});
//最后,将以上初始化的对象,全部注册注册为当前Viewer实例的属性,并将其中一些对象例如dataSourceCollection的一些事件一并注册到Viewer的原型上。
// 另外Cesium还默认为cesiumWidget注册了屏幕操作事件的点击、双击事件,方便初始化完成后能通过点击来拾取场景中的Entity
}