第03章 - 核心架构与模块设计

第03章:核心架构与模块设计

3.1 CesiumJS 整体架构

3.1.1 分层架构设计

CesiumJS 采用清晰的分层架构设计,从底层渲染引擎到高级应用 API,层次分明:

┌─────────────────────────────────────────────────────────────────┐
│                      应用层 (Application Layer)                  │
│    Viewer │ Widget │ UI Controls │ 业务逻辑                      │
└─────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                    高级 API 层 (High-Level API)                  │
│    Entity │ DataSource │ Imagery │ Terrain │ 3D Tiles           │
└─────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                    核心层 (Core Layer)                           │
│    Scene │ Camera │ Globe │ Primitive │ Geometry                │
└─────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                    渲染层 (Renderer Layer)                       │
│    WebGL Context │ Shader │ Texture │ FrameBuffer │ Buffer      │
└─────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                    基础层 (Foundation Layer)                     │
│    Math │ Geometry │ Coordinate │ Time │ Event │ Promise        │
└─────────────────────────────────────────────────────────────────┘

3.1.2 核心模块组成

CesiumJS 的核心模块及其职责:

模块 职责 主要类
Viewer 应用入口,集成所有功能 Viewer
Scene 场景管理,渲染调度 Scene, Globe, SkyBox
Camera 视角控制 Camera, CameraController
Entity 高级数据表示 Entity, EntityCollection
Primitive 底层渲染 Primitive, GeometryInstance
DataSource 数据源管理 GeoJsonDataSource, CzmlDataSource
Imagery 影像图层 ImageryLayer, ImageryProvider
Terrain 地形数据 TerrainProvider, QuantizedMeshTerrainData
3D Tiles 大规模三维数据 Cesium3DTileset, Cesium3DTile

3.2 Viewer 架构详解

3.2.1 Viewer 组件构成

Viewer 是 CesiumJS 的核心容器,整合了所有主要功能:

┌─────────────────────────────────────────────────────────────────┐
│                         Viewer                                   │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    CesiumWidget                          │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │   │
│  │  │   Scene     │  │   Clock     │  │  Canvas     │      │   │
│  │  │             │  │             │  │  (WebGL)    │      │   │
│  │  └─────────────┘  └─────────────┘  └─────────────┘      │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    UI Widgets                            │   │
│  │  Animation│Timeline│Geocoder│HomeButton│SceneModePicker │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                Data Management                           │   │
│  │  EntityCollection │ DataSourceCollection │ ImageryLayers │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

3.2.2 Viewer 主要属性

const viewer = new Cesium.Viewer('cesiumContainer');

// 核心组件访问
viewer.scene            // Scene 对象
viewer.camera           // Camera 对象
viewer.canvas           // WebGL Canvas
viewer.clock            // Clock 时钟
viewer.cesiumWidget     // 底层 Widget

// 数据集合
viewer.entities         // EntityCollection
viewer.dataSources      // DataSourceCollection
viewer.imageryLayers    // ImageryLayerCollection
viewer.terrainProvider  // TerrainProvider

// UI 控件
viewer.animation        // AnimationViewModel
viewer.timeline         // Timeline
viewer.homeButton       // HomeButton
viewer.geocoder         // Geocoder
viewer.sceneModePicker  // SceneModePicker
viewer.infoBox          // InfoBox
viewer.selectionIndicator // SelectionIndicator

3.2.3 Viewer 配置选项

const viewer = new Cesium.Viewer('cesiumContainer', {
    // ===== 场景配置 =====
    scene3DOnly: false,              // 是否仅 3D 模式
    sceneMode: Cesium.SceneMode.SCENE3D, // 初始场景模式
    
    // ===== 地形配置 =====
    terrainProvider: Cesium.createWorldTerrain({
        requestWaterMask: true,
        requestVertexNormals: true
    }),
    
    // ===== 影像配置 =====
    imageryProvider: new Cesium.IonImageryProvider({ assetId: 2 }),
    baseLayerPicker: true,           // 底图选择器
    
    // ===== 时间配置 =====
    shouldAnimate: true,             // 是否自动播放动画
    clockViewModel: undefined,       // 时钟视图模型
    
    // ===== UI 控件 =====
    animation: true,                 // 动画控件
    timeline: true,                  // 时间轴
    fullscreenButton: true,          // 全屏按钮
    vrButton: false,                 // VR 按钮
    geocoder: true,                  // 地理编码搜索
    homeButton: true,                // 主页按钮
    infoBox: true,                   // 信息框
    sceneModePicker: true,           // 场景模式选择器
    selectionIndicator: true,        // 选择指示器
    navigationHelpButton: true,      // 导航帮助按钮
    navigationInstructionsInitiallyVisible: false,
    
    // ===== 渲染配置 =====
    contextOptions: {
        webgl: {
            alpha: false,
            antialias: true,
            powerPreference: 'high-performance'
        }
    },
    
    // ===== 其他 =====
    targetFrameRate: undefined,      // 目标帧率
    useBrowserRecommendedResolution: true,
    orderIndependentTranslucency: true,
    shadows: false,                  // 阴影
    terrainShadows: Cesium.ShadowMode.RECEIVE_ONLY
});

3.3 Scene 渲染架构

3.3.1 Scene 组件结构

Scene 是渲染引擎的核心,管理所有可渲染内容:

┌─────────────────────────────────────────────────────────────────┐
│                          Scene                                   │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             │
│  │   Globe     │  │  SkyBox     │  │   Sun       │             │
│  │  (地球)     │  │  (天空盒)   │  │  (太阳)     │             │
│  └─────────────┘  └─────────────┘  └─────────────┘             │
│                                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             │
│  │   Moon      │  │SkyAtmosphere│  │    Fog      │             │
│  │  (月亮)     │  │  (大气层)   │  │   (雾)      │             │
│  └─────────────┘  └─────────────┘  └─────────────┘             │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │               PrimitiveCollection                        │   │
│  │     Primitives │ GroundPrimitives │ 3DTiles │ Models     │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                   Camera                                 │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

3.3.2 Scene 渲染流程

// Scene 渲染循环(简化版)
function renderLoop() {
    // 1. 更新时间
    scene.clock.tick();
    
    // 2. 更新相机
    scene.camera.update(frameState);
    
    // 3. 视锥体剔除
    scene.globe.update(frameState);
    scene.primitives.update(frameState);
    
    // 4. 渲染命令排序
    scene.frameState.commandList.sort();
    
    // 5. 执行渲染
    scene.render();
    
    // 6. 请求下一帧
    requestAnimationFrame(renderLoop);
}

3.3.3 Scene 配置与控制

const scene = viewer.scene;

// ===== 渲染配置 =====
scene.fog.enabled = true;                    // 雾效
scene.fog.density = 0.0002;                  // 雾密度

scene.globe.enableLighting = true;           // 光照
scene.globe.dynamicAtmosphereLighting = true; // 动态大气光照
scene.globe.showGroundAtmosphere = true;     // 地面大气

scene.skyAtmosphere.show = true;             // 大气层
scene.sun.show = true;                       // 太阳
scene.moon.show = true;                      // 月亮

// ===== 性能配置 =====
scene.debugShowFramesPerSecond = true;       // 显示帧率
scene.requestRenderMode = true;              // 按需渲染
scene.maximumRenderTimeChange = 0.0;         // 最大渲染时间变化

// ===== 深度测试 =====
scene.globe.depthTestAgainstTerrain = true;  // 地形深度测试

// ===== 场景模式 =====
scene.mode = Cesium.SceneMode.SCENE3D;       // 3D 模式
// Cesium.SceneMode.SCENE2D                   // 2D 模式
// Cesium.SceneMode.COLUMBUS_VIEW             // 哥伦布视图

// ===== 事件监听 =====
scene.preRender.addEventListener(function(scene, time) {
    // 渲染前回调
});

scene.postRender.addEventListener(function(scene, time) {
    // 渲染后回调
});

3.4 坐标系统

3.4.1 坐标系类型

CesiumJS 中使用多种坐标系:

┌─────────────────────────────────────────────────────────────────┐
│                      CesiumJS 坐标系统                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 笛卡尔坐标 (Cartesian3)                                      │
│     - 以地球中心为原点                                           │
│     - X: 指向赤道与本初子午线交点                                 │
│     - Y: 指向赤道与东经90°交点                                   │
│     - Z: 指向北极                                                │
│                                                                  │
│  2. 经纬度坐标 (Cartographic)                                    │
│     - longitude: 经度(弧度)                                    │
│     - latitude: 纬度(弧度)                                     │
│     - height: 高度(米)                                         │
│                                                                  │
│  3. 屏幕坐标 (Cartesian2)                                        │
│     - x: 屏幕水平像素                                            │
│     - y: 屏幕垂直像素                                            │
│                                                                  │
│  4. 度数坐标 (Degrees)                                           │
│     - 便于人类理解的经纬度格式                                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

3.4.2 坐标转换

// ===== 度数 -> 笛卡尔坐标 =====
const cartesian = Cesium.Cartesian3.fromDegrees(116.4, 39.9, 100);
// 或
const cartesianArray = Cesium.Cartesian3.fromDegreesArray([
    116.0, 39.0,
    117.0, 39.0,
    117.0, 40.0
]);

// ===== 笛卡尔坐标 -> 经纬度(弧度)=====
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
console.log(cartographic.longitude); // 弧度
console.log(cartographic.latitude);  // 弧度
console.log(cartographic.height);    // 米

// ===== 弧度 -> 度数 =====
const lonDegrees = Cesium.Math.toDegrees(cartographic.longitude);
const latDegrees = Cesium.Math.toDegrees(cartographic.latitude);

// ===== 度数 -> 弧度 =====
const lonRadians = Cesium.Math.toRadians(116.4);
const latRadians = Cesium.Math.toRadians(39.9);

// ===== 屏幕坐标 -> 笛卡尔坐标 =====
const screenPosition = new Cesium.Cartesian2(400, 300);
const worldPosition = viewer.scene.pickPosition(screenPosition);

// ===== 笛卡尔坐标 -> 屏幕坐标 =====
const screenPos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
    viewer.scene, cartesian
);

// ===== 从经纬度创建 Cartographic =====
const carto = Cesium.Cartographic.fromDegrees(116.4, 39.9, 100);
const carto2 = Cesium.Cartographic.fromRadians(
    Cesium.Math.toRadians(116.4),
    Cesium.Math.toRadians(39.9),
    100
);

3.4.3 坐标工具函数

// 坐标工具类
class CoordinateUtils {
    /**
     * 度数转笛卡尔坐标
     */
    static degreesToCartesian(lon, lat, height = 0) {
        return Cesium.Cartesian3.fromDegrees(lon, lat, height);
    }
    
    /**
     * 笛卡尔坐标转度数
     */
    static cartesianToDegrees(cartesian) {
        const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        return {
            longitude: Cesium.Math.toDegrees(cartographic.longitude),
            latitude: Cesium.Math.toDegrees(cartographic.latitude),
            height: cartographic.height
        };
    }
    
    /**
     * 计算两点间距离
     */
    static distance(point1, point2) {
        return Cesium.Cartesian3.distance(point1, point2);
    }
    
    /**
     * 计算中心点
     */
    static center(positions) {
        return Cesium.BoundingSphere.fromPoints(positions).center;
    }
    
    /**
     * 屏幕坐标转世界坐标
     */
    static screenToWorld(viewer, screenPosition) {
        // 射线拾取地球表面
        const ray = viewer.camera.getPickRay(screenPosition);
        return viewer.scene.globe.pick(ray, viewer.scene);
    }
}

3.5 时间系统

3.5.1 时间类型

// JulianDate - CesiumJS 的核心时间类型
const now = Cesium.JulianDate.now();

// 从 JavaScript Date 创建
const date = new Date('2024-01-01T00:00:00Z');
const julianDate = Cesium.JulianDate.fromDate(date);

// 从 ISO 8601 字符串创建
const isoDate = Cesium.JulianDate.fromIso8601('2024-01-01T12:00:00Z');

// 时间加减
const later = Cesium.JulianDate.addSeconds(now, 3600, new Cesium.JulianDate());
const earlier = Cesium.JulianDate.addDays(now, -1, new Cesium.JulianDate());

// 时间比较
const diff = Cesium.JulianDate.secondsDifference(later, now); // 秒差
const compare = Cesium.JulianDate.compare(now, later); // -1, 0, 1

3.5.2 Clock 时钟系统

const clock = viewer.clock;

// ===== 时钟配置 =====
clock.startTime = Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z');
clock.stopTime = Cesium.JulianDate.fromIso8601('2024-01-02T00:00:00Z');
clock.currentTime = Cesium.JulianDate.fromIso8601('2024-01-01T12:00:00Z');

// 时钟范围
clock.clockRange = Cesium.ClockRange.LOOP_STOP;  // 循环播放
// Cesium.ClockRange.UNBOUNDED    // 无限制
// Cesium.ClockRange.CLAMPED      // 限制在范围内

// 播放速度(倍率)
clock.multiplier = 1;     // 实时
clock.multiplier = 60;    // 60 倍速
clock.multiplier = -1;    // 倒放

// 是否播放
clock.shouldAnimate = true;

// ===== 时钟事件 =====
clock.onTick.addEventListener(function(clock) {
    console.log('当前时间:', clock.currentTime);
});

clock.onStop.addEventListener(function(clock) {
    console.log('时钟停止');
});

3.5.3 Timeline 时间轴

// 时间轴控制
const timeline = viewer.timeline;

// 缩放时间轴
timeline.zoomTo(
    Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z'),
    Cesium.JulianDate.fromIso8601('2024-01-07T00:00:00Z')
);

// 时间轴事件
timeline.addEventListener('settime', function(e) {
    console.log('时间轴时间改变:', e.clock.currentTime);
}, false);

3.6 事件系统

3.6.1 事件类型

// ===== Scene 事件 =====
viewer.scene.preUpdate.addEventListener(function(scene, time) {
    // 更新前
});

viewer.scene.postUpdate.addEventListener(function(scene, time) {
    // 更新后
});

viewer.scene.preRender.addEventListener(function(scene, time) {
    // 渲染前
});

viewer.scene.postRender.addEventListener(function(scene, time) {
    // 渲染后
});

// ===== Camera 事件 =====
viewer.camera.moveStart.addEventListener(function() {
    console.log('相机开始移动');
});

viewer.camera.moveEnd.addEventListener(function() {
    console.log('相机停止移动');
});

viewer.camera.changed.addEventListener(function(percentage) {
    console.log('相机变化:', percentage);
});

// ===== Entity 事件 =====
viewer.entities.collectionChanged.addEventListener(function(collection, added, removed, changed) {
    console.log('实体集合变化');
});

3.6.2 鼠标/触摸事件

// 创建事件处理器
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);

// ===== 鼠标左键点击 =====
handler.setInputAction(function(click) {
    console.log('左键点击:', click.position);
    
    // 拾取实体
    const pickedObject = viewer.scene.pick(click.position);
    if (Cesium.defined(pickedObject)) {
        console.log('选中对象:', pickedObject);
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

// ===== 鼠标移动 =====
handler.setInputAction(function(movement) {
    const position = movement.endPosition;
    console.log('鼠标位置:', position);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

// ===== 鼠标右键点击 =====
handler.setInputAction(function(click) {
    console.log('右键点击:', click.position);
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);

// ===== 鼠标双击 =====
handler.setInputAction(function(click) {
    console.log('双击:', click.position);
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);

// ===== 滚轮 =====
handler.setInputAction(function(delta) {
    console.log('滚轮:', delta);
}, Cesium.ScreenSpaceEventType.WHEEL);

// ===== 组合键 =====
handler.setInputAction(function(click) {
    console.log('Shift + 左键点击');
}, Cesium.ScreenSpaceEventType.LEFT_CLICK, Cesium.KeyboardEventModifier.SHIFT);

// ===== 移除事件 =====
handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);

// ===== 销毁处理器 =====
handler.destroy();

3.6.3 拾取功能

// 拾取场景中的对象
function pickHandler(position) {
    // 拾取第一个对象
    const pickedObject = viewer.scene.pick(position);
    
    if (Cesium.defined(pickedObject)) {
        // Entity
        if (pickedObject.id && pickedObject.id instanceof Cesium.Entity) {
            console.log('选中 Entity:', pickedObject.id.name);
        }
        
        // 3D Tiles
        if (pickedObject.primitive instanceof Cesium.Cesium3DTileset) {
            console.log('选中 3D Tiles');
        }
        
        // Primitive
        if (pickedObject.primitive instanceof Cesium.Primitive) {
            console.log('选中 Primitive');
        }
    }
    
    // 拾取所有对象
    const pickedObjects = viewer.scene.drillPick(position);
    console.log('所有拾取对象:', pickedObjects.length);
    
    // 拾取位置(世界坐标)
    const worldPosition = viewer.scene.pickPosition(position);
    if (Cesium.defined(worldPosition)) {
        const cartographic = Cesium.Cartographic.fromCartesian(worldPosition);
        console.log('拾取位置:', {
            lon: Cesium.Math.toDegrees(cartographic.longitude),
            lat: Cesium.Math.toDegrees(cartographic.latitude),
            height: cartographic.height
        });
    }
}

3.7 模块化结构

3.7.1 npm 包结构

CesiumJS 提供模块化的 npm 包:

cesium (完整包)
├── @cesium/engine (核心引擎)
│   ├── Core/           # 基础工具
│   ├── Renderer/       # 渲染器
│   ├── Scene/          # 场景
│   └── DataSources/    # 数据源
│
└── @cesium/widgets (UI 组件)
    ├── Animation/      # 动画控件
    ├── Timeline/       # 时间轴
    ├── Geocoder/       # 地理编码
    ├── HomeButton/     # 主页按钮
    └── ...

3.7.2 按需导入

// 完整导入
import * as Cesium from 'cesium';

// 按需导入(减小包体积)
import {
    Viewer,
    Cartesian3,
    Color,
    Entity,
    GeoJsonDataSource
} from 'cesium';

// 使用 @cesium/engine(更轻量)
import {
    Scene,
    Camera,
    Primitive,
    GeometryInstance,
    RectangleGeometry
} from '@cesium/engine';

3.7.3 Tree Shaking 优化

// vite.config.js
import { defineConfig } from 'vite';
import cesium from 'vite-plugin-cesium';

export default defineConfig({
    plugins: [cesium()],
    build: {
        rollupOptions: {
            output: {
                manualChunks: {
                    cesium: ['cesium']
                }
            }
        }
    }
});

3.8 扩展机制

3.8.1 Viewer 扩展

// 定义扩展
function myViewerExtension(viewer) {
    // 添加自定义属性
    viewer.myCustomProperty = 'value';
    
    // 添加自定义方法
    viewer.myCustomMethod = function() {
        console.log('自定义方法被调用');
    };
    
    // 返回清理函数
    return function() {
        delete viewer.myCustomProperty;
        delete viewer.myCustomMethod;
    };
}

// 应用扩展
viewer.extend(myViewerExtension);

// 使用扩展
viewer.myCustomMethod();

3.8.2 自定义 DataSource

// 自定义 DataSource
class MyDataSource {
    constructor(name) {
        this._name = name;
        this._entityCollection = new Cesium.EntityCollection();
        this._clock = undefined;
        this._changedEvent = new Cesium.Event();
        this._errorEvent = new Cesium.Event();
        this._loadingEvent = new Cesium.Event();
    }
    
    get name() { return this._name; }
    get entities() { return this._entityCollection; }
    get clock() { return this._clock; }
    get changedEvent() { return this._changedEvent; }
    get errorEvent() { return this._errorEvent; }
    get loadingEvent() { return this._loadingEvent; }
    get isLoading() { return false; }
    
    async load(data) {
        this._loadingEvent.raiseEvent(this, true);
        
        try {
            // 解析数据并创建实体
            for (const item of data) {
                this._entityCollection.add(new Cesium.Entity({
                    name: item.name,
                    position: Cesium.Cartesian3.fromDegrees(item.lon, item.lat)
                }));
            }
            
            this._changedEvent.raiseEvent(this);
        } catch (error) {
            this._errorEvent.raiseEvent(this, error);
        } finally {
            this._loadingEvent.raiseEvent(this, false);
        }
        
        return this;
    }
    
    update(time) {
        // 每帧更新逻辑
        return true;
    }
}

// 使用自定义 DataSource
const myDataSource = new MyDataSource('自定义数据源');
await myDataSource.load([
    { name: '点1', lon: 116.4, lat: 39.9 },
    { name: '点2', lon: 121.5, lat: 31.2 }
]);
viewer.dataSources.add(myDataSource);

3.9 本章小结

本章深入介绍了 CesiumJS 的核心架构:

  1. 分层架构:从基础层到应用层的清晰分离
  2. Viewer:应用入口,整合所有功能组件
  3. Scene:渲染引擎核心,管理所有可渲染内容
  4. 坐标系统:笛卡尔坐标、经纬度、屏幕坐标的转换
  5. 时间系统:JulianDate、Clock、Timeline
  6. 事件系统:场景事件、鼠标事件、拾取功能
  7. 模块化:npm 包结构、按需导入
  8. 扩展机制:Viewer 扩展、自定义 DataSource

在下一章中,我们将详细介绍 Viewer 与场景管理。

3.10 思考与练习

  1. 画出 CesiumJS 的分层架构图,标注各层的主要职责。
  2. 编写一个坐标转换工具类,支持多种坐标格式互转。
  3. 实现一个自定义事件处理器,响应键盘和鼠标组合操作。
  4. 研究 @cesium/engine 和完整 cesium 包的区别。
  5. 尝试创建一个简单的自定义 DataSource。
posted @ 2026-01-08 11:13  我才是银古  阅读(4)  评论(0)    收藏  举报