Cesium 权威指南:全面遍历与展示 Cesium 操作

Cesium 是一个开源的 JavaScript 库,用于在网页浏览器中创建高性能的 3D 地球和地图应用。它支持丰富的地理空间数据可视化,广泛应用于地理信息系统(GIS)、仿真、数据分析等领域。无论您是前端开发者、GIS 专业人士,还是对 3D 地图感兴趣的爱好者,学习 Cesium 都能帮助您构建交互式、动态的地理空间应用。本指南将全面介绍 Cesium 的安装、配置、基础操作、高级功能、与 PostGIS 的集成、案例分析及项目实践,帮助您系统掌握并应用 Cesium。

目录

  1. 什么是 Cesium?
  2. Cesium 的安装与配置
  3. Cesium 基础概念
  4. 创建第一个 Cesium 应用
  5. 加载与展示空间数据
  6. 与 PostGIS 集成
  7. 常用功能与操作
  8. 高级功能与扩展
  9. 案例分析
  10. 项目实践:构建一个 3D 城市可视化应用
  11. 常见问题与解决方法
  12. 进一步学习资源

什么是 Cesium?

Cesium 是一个开源的 JavaScript 库,专注于创建高性能的 3D 地球和地图应用。它利用 WebGL 技术,在浏览器中渲染逼真的地理空间数据,支持全球范围内的地理信息展示和交互。Cesium 的主要特点包括:

  • 高性能 3D 渲染:利用 WebGL 实现流畅的 3D 场景。
  • 丰富的数据支持:支持多种地理空间数据格式,如 GeoJSON、KML、CZML 等。
  • 时间动态效果:支持时间线,适用于展示动态数据。
  • 插件与扩展:拥有丰富的插件生态,方便功能扩展。
  • 跨平台:基于浏览器,兼容各种操作系统和设备。

应用场景:

  • 城市规划与管理
  • 灾害监测与应急响应
  • 航空航天仿真
  • 数据可视化与分析
  • 教育与科研

Cesium 的安装与配置

准备开发环境

在开始使用 Cesium 之前,需要准备一个基本的前端开发环境,包括:

  • 文本编辑器或集成开发环境(IDE):如 Visual Studio Code、WebStorm 等。
  • Web 服务器:用于托管和访问 Cesium 应用,开发阶段可以使用简易的本地服务器,如 http-server
  • 包管理工具:如 npm(Node.js)或 yarn,用于管理项目依赖。

安装 Node.js 和 npm

Cesium 的开发通常依赖于 Node.js 环境。如果尚未安装 Node.js 和 npm,可以按照以下步骤进行安装:

  1. 下载 Node.js:访问 Node.js 官方网站 下载适用于您操作系统的安装包并安装。

  2. 验证安装

    node -v
    npm -v
    

    以上命令应分别返回 Node.js 和 npm 的版本号。

创建项目目录

在合适的位置创建一个新的项目目录,并进入该目录:

mkdir cesium-project
cd cesium-project

初始化项目

使用 npm 初始化项目,并安装 Cesium 作为依赖:

npm init -y
npm install cesium

配置 Cesium

由于 Cesium 依赖于一些静态资源(如图像、模型等),需要进行相应的配置。以下以使用 Webpack 构建项目为例:

安装 Webpack 及相关依赖
npm install webpack webpack-cli webpack-dev-server --save-dev
npm install html-webpack-plugin copy-webpack-plugin --save-dev
npm install style-loader css-loader --save-dev
创建 Webpack 配置文件

在项目根目录下创建 webpack.config.js 文件,并添加以下内容:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
    publicPath: '',
  },
  mode: 'development',
  devServer: {
    static: './dist',
    compress: true,
    port: 8080,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html',
    }),
    new CopyWebpackPlugin({
      patterns: [
        { from: 'node_modules/cesium/Build/Cesium/Workers', to: 'Workers' },
        { from: 'node_modules/cesium/Build/Cesium/Assets', to: 'Assets' },
        { from: 'node_modules/cesium/Build/Cesium/Widgets', to: 'Widgets' },
      ],
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  resolve: {
    alias: {
      cesium: path.resolve(__dirname, 'node_modules/cesium/Build/Cesium/Cesium.js'),
    },
  },
};

解释:

  • Entry Point: 指定入口文件为 ./src/index.js
  • Output: 输出文件名为 bundle.js,输出路径为 dist 目录,clean: true 表示在每次构建前清空 dist 目录。
  • Mode: 设置为 development,适合开发阶段。
  • DevServer: 配置开发服务器,监听 dist 目录,启用压缩,端口号为 8080
  • Plugins:
    • HtmlWebpackPlugin: 自动生成 HTML 文件,基于 src/index.html 模板。
    • CopyWebpackPlugin: 复制 Cesium 的 Workers、Assets 和 Widgets 目录到 dist 目录。
  • Module Rules: 处理 CSS 文件,使用 style-loadercss-loader
  • Resolve Alias: 设置 cesium 别名,指向 Cesium 的主文件。
创建项目结构

src 目录下创建 index.jsindex.html 文件:

mkdir src
touch src/index.js src/index.html
配置 HTML 模板

src/index.html 中添加基本的 HTML 结构:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Cesium 应用</title>
  <link href="https://cesium.com/downloads/cesiumjs/releases/1.93/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
  <style>
    #cesiumContainer {
      width: 100%;
      height: 100vh;
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
  </style>
</head>
<body>
  <div id="cesiumContainer"></div>
</body>
</html>

解释:

  • 引入 Cesium Widgets 的 CSS 样式。
  • 定义一个全屏的容器 cesiumContainer 用于展示 Cesium 视图。
配置 JavaScript 入口

src/index.js 中初始化 Cesium:

import Cesium from 'cesium/Cesium';
import 'cesium/Widgets/widgets.css';

// Cesium 配置
window.CESIUM_BASE_URL = './';

// 创建 Viewer 实例
const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain(),
});

解释:

  • Import Cesium: 导入 Cesium 库和其 CSS 样式。
  • CESIUM_BASE_URL: 设置 Cesium 静态资源的基础路径为当前目录。
  • Viewer: 创建 Cesium 的核心视图对象,加载世界地形。

运行项目

package.json 中添加以下脚本:

"scripts": {
  "start": "webpack serve --open",
  "build": "webpack"
}

解释:

  • start: 启动 Webpack 开发服务器,并在浏览器中自动打开应用。
  • build: 进行项目构建,生成生产环境代码。

启动开发服务器:

npm start

浏览器将自动打开 http://localhost:8080/,您应能看到 Cesium 的 3D 地球界面。

Cesium 基础概念

Viewer

Viewer 是 Cesium 中的核心组件,用于创建和管理 3D 地球视图。通过配置 Viewer,可以添加图层、控件、数据源等。

示例:

const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain(),
});

参数解释:

  • 'cesiumContainer': HTML 容器的 ID,用于渲染 Cesium 视图。
  • terrainProvider: 指定地形数据提供者,这里使用 Cesium 提供的全球地形数据。

Entities

Entity 是 Cesium 中用于表示地理对象的抽象概念,可以是点、线、面、模型等。Entities 可以包含位置、样式、属性等信息。

示例:

viewer.entities.add({
  name: '北京',
  position: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042),
  point: {
    pixelSize: 10,
    color: Cesium.Color.RED,
  },
});

解释:

  • name: 实体名称。
  • position: 实体位置,使用经纬度转换为笛卡尔坐标。
  • point: 点的样式,包括像素大小和颜色。

Data Sources

Data Sources 用于加载和管理外部数据,如 GeoJSON、KML、CZML 等。Cesium 提供了多种数据源加载器,方便集成各种空间数据。

示例:

const geoJsonPromise = Cesium.GeoJsonDataSource.load('path/to/your.geojson');
viewer.dataSources.add(geoJsonPromise);

解释:

  • GeoJsonDataSource.load: 加载 GeoJSON 数据。
  • viewer.dataSources.add: 将加载的数据源添加到 Viewer 中。

Camera

Camera 控制 Cesium 视图的观察角度和位置。通过操作摄像机,可以实现缩放、旋转、平移等视图变换。

示例:

viewer.camera.flyTo({
  destination: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 1000000),
});

解释:

  • flyTo: 让摄像机飞行到指定位置。
  • destination: 目标位置,使用经纬度和高度转换为笛卡尔坐标。

Primitives

Primitives 是 Cesium 中更底层的渲染对象,用于实现更高性能或更复杂的渲染需求。相比 Entities,Primitives 提供了更高的灵活性和控制,但也需要更复杂的编程。

示例:

const primitive = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
  url: 'path/to/tileset.json',
}));

解释:

  • Cesium3DTileset: 加载 3D Tiles 数据集。
  • viewer.scene.primitives.add: 将 Primitive 添加到场景中。

Cesium Widget

Cesium 提供了多种内置的 Widget,用于增强用户界面和交互功能,如时间线、导航控件、信息框等。

示例:

const viewer = new Cesium.Viewer('cesiumContainer', {
  timeline: true,
  animation: true,
});

解释:

  • timeline: 显示时间轴控件。
  • animation: 显示动画控制器。

创建第一个 Cesium 应用

以下步骤将带您创建一个简单的 Cesium 应用,展示基本的 3D 地球视图和一个标注点。

设置项目结构

假设已经按照前述的安装步骤创建了项目目录,并配置了 Webpack。

修改 index.js 添加标注点

src/index.js 中添加一个实体,用于标注北京的位置。

import Cesium from 'cesium/Cesium';
import 'cesium/Widgets/widgets.css';

// Cesium 配置
window.CESIUM_BASE_URL = './';

// 创建 Viewer 实例
const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain(),
});

// 添加实体
viewer.entities.add({
  name: '北京',
  position: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042),
  point: {
    pixelSize: 10,
    color: Cesium.Color.RED,
  },
  label: {
    text: '北京',
    font: '14pt sans-serif',
    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
    outlineWidth: 2,
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
    pixelOffset: new Cesium.Cartesian2(0, -15),
  },
});

// 飞行到实体位置
viewer.zoomTo(viewer.entities);

解释:

  • Entities.add: 添加一个名为“北京”的实体,位置为北京的经纬度,显示为红色点和标签。
  • viewer.zoomTo: 自动飞行到添加的实体位置。

运行项目

在项目根目录下运行:

npm start

浏览器将打开 http://localhost:8080/,您将看到 3D 地球,标注有北京的位置。

加载与展示空间数据

Cesium 支持多种空间数据格式,以下介绍几种常用的数据加载方式。

加载 GeoJSON 数据

GeoJSON 是一种常用的地理空间数据格式,适用于表示点、线、面等几何对象。

示例:

假设有一个 cities.geojson 文件,内容如下:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": { "name": "上海", "population": 24240000 },
      "geometry": {
        "type": "Point",
        "coordinates": [121.4737, 31.2304]
      }
    },
    {
      "type": "Feature",
      "properties": { "name": "广州", "population": 13500000 },
      "geometry": {
        "type": "Point",
        "coordinates": [113.2644, 23.1291]
      }
    }
  ]
}

加载 GeoJSON 数据:

Cesium.GeoJsonDataSource.load('path/to/cities.geojson').then(function(dataSource) {
  viewer.dataSources.add(dataSource);
  viewer.zoomTo(dataSource);
});

解释:

  • GeoJsonDataSource.load: 加载 GeoJSON 数据。
  • viewer.dataSources.add: 将加载的数据源添加到 Viewer 中。
  • viewer.zoomTo: 自动飞行到数据源范围。

加载 KML 数据

KML 是一种用于表达地理注记和可视化的 XML 格式。

示例:

Cesium.KmlDataSource.load('path/to/your.kml').then(function(dataSource) {
  viewer.dataSources.add(dataSource);
  viewer.zoomTo(dataSource);
});

解释:

  • KmlDataSource.load: 加载 KML 数据。
  • viewer.dataSources.add: 将加载的数据源添加到 Viewer 中。
  • viewer.zoomTo: 自动飞行到数据源范围。

加载 CZML 数据

CZML 是 Cesium 专用的 JSON 格式,用于描述动态的时间变化数据。

示例:

const czmlData = [
  {
    "id": "document",
    "name": "CZML Point",
    "version": "1.0"
  },
  {
    "id": "point1",
    "name": "动态点",
    "position": {
      "cartographicDegrees": [116.4074, 39.9042, 1000000]
    },
    "point": {
      "pixelSize": 10,
      "color": {
        "rgba": [255, 0, 0, 255]
      }
    }
  }
];

const czmlDataSource = new Cesium.CzmlDataSource();
viewer.dataSources.add(czmlDataSource);
czmlDataSource.load(czmlData);
viewer.zoomTo(czmlDataSource);

解释:

  • CzmlDataSource: 创建 CZML 数据源实例。
  • czmlDataSource.load: 加载 CZML 数据。
  • viewer.dataSources.add: 将 CZML 数据源添加到 Viewer 中。
  • viewer.zoomTo: 自动飞行到数据源范围。

加载 3D Tiles

3D Tiles 是 Cesium 用于高效传输和渲染大规模 3D 地理空间数据的格式,适用于建筑物、地形等复杂模型。

示例:

const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
  url: 'path/to/tileset.json'
}));

tileset.readyPromise.then(function(tileset) {
  viewer.zoomTo(tileset);
}).otherwise(function(error) {
  console.error(error);
});

解释:

  • Cesium3DTileset: 创建 3D Tiles 数据集实例。
  • viewer.scene.primitives.add: 将 3D Tiles 添加到场景中。
  • tileset.readyPromise: 等待数据集加载完成后自动飞行到数据集位置。

与 PostGIS 集成

PostGIS 是 PostgreSQL 的空间扩展,用于存储和管理地理空间数据。将 PostGIS 与 Cesium 集成,可以实现动态数据的可视化和交互。以下介绍一种常见的集成方式:通过后端 API 从 PostGIS 获取数据,并在 Cesium 中加载和展示。

后端 API 开发

假设使用 Node.js 和 Express 搭建后端 API,从 PostGIS 数据库中查询数据并以 GeoJSON 格式返回。

安装依赖

在项目目录下安装 Express 和 pg(PostgreSQL 客户端):

npm install express pg cors
创建后端服务器

在项目根目录下创建 server.js 文件,并添加以下内容:

const express = require('express');
const { Pool } = require('pg');
const cors = require('cors');
const app = express();
const port = 3001;

// 配置 PostgreSQL 连接
const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'gis_db',
  password: 'your_password',
  port: 5432,
});

app.use(cors());

// API 路由:获取城市数据
app.get('/api/cities', async (req, res) => {
  try {
    const result = await pool.query(`
      SELECT name, population, ST_AsGeoJSON(geom) AS geojson
      FROM cities
    `);
    const features = result.rows.map(row => ({
      type: 'Feature',
      properties: {
        name: row.name,
        population: row.population,
      },
      geometry: JSON.parse(row.geojson),
    }));
    res.json({
      type: 'FeatureCollection',
      features: features,
    });
  } catch (err) {
    console.error(err);
    res.status(500).send('Server Error');
  }
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/`);
});

解释:

  • Express: 用于搭建后端服务器。
  • pg: PostgreSQL 客户端,用于连接和查询 PostGIS 数据库。
  • cors: 中间件,解决跨域资源共享(CORS)问题。
  • /api/cities: 定义一个 API 路由,查询 cities 表中的数据,并以 GeoJSON 格式返回。
运行后端服务器

在终端中运行:

node server.js

服务器将启动并监听 http://localhost:3001/

前端 Cesium 加载 API 数据

在前端 Cesium 应用中,通过 Fetch API 调用后端 API 获取 GeoJSON 数据,并加载到 Cesium 中。

修改 index.js 添加数据加载
import Cesium from 'cesium/Cesium';
import 'cesium/Widgets/widgets.css';

// Cesium 配置
window.CESIUM_BASE_URL = './';

// 创建 Viewer 实例
const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain(),
});

// 从后端 API 获取城市数据并加载
fetch('http://localhost:3001/api/cities')
  .then(response => response.json())
  .then(geoJson => {
    Cesium.GeoJsonDataSource.load(geoJson).then(dataSource => {
      viewer.dataSources.add(dataSource);
      viewer.zoomTo(dataSource);

      // 添加点击事件
      const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
      handler.setInputAction(function(click) {
        const pickedObject = viewer.scene.pick(click.position);
        if (Cesium.defined(pickedObject) && pickedObject.id) {
          const name = pickedObject.id.name;
          const population = pickedObject.id.population;
          // 显示信息窗口
          viewer.selectedEntity = pickedObject.id;
          // 可以进一步定制信息窗口内容
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    });
  })
  .catch(error => {
    console.error('Error fetching GeoJSON data:', error);
  });

解释:

  • Fetch API: 通过 HTTP 请求获取后端 API 提供的 GeoJSON 数据。
  • GeoJsonDataSource.load: 加载 GeoJSON 数据并创建数据源。
  • viewer.dataSources.add: 将数据源添加到 Viewer 中。
  • viewer.zoomTo: 自动飞行到数据源范围。
  • ScreenSpaceEventHandler: 监听鼠标点击事件,获取点击的实体并显示信息窗口。

常用功能与操作

Cesium 提供了丰富的功能和操作接口,以下按照不同的功能维度,详细展示 Cesium 中的常见操作。

添加标注和信息窗口

在 Cesium 中,可以为实体添加标注和信息窗口,以提供更多信息和交互功能。

示例:

viewer.entities.add({
  name: '上海',
  position: Cesium.Cartesian3.fromDegrees(121.4737, 31.2304),
  point: {
    pixelSize: 10,
    color: Cesium.Color.BLUE,
  },
  label: {
    text: '上海',
    font: '14pt sans-serif',
    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
    outlineWidth: 2,
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
    pixelOffset: new Cesium.Cartesian2(0, -15),
  },
  description: '<h3>上海</h3><p>人口: 24,240,000</p>',
});

解释:

  • position: 实体位置,使用经纬度转换为笛卡尔坐标。
  • point: 点的样式,包括像素大小和颜色。
  • label: 标签的样式和位置。
  • description: 信息窗口内容,支持 HTML 格式。

效果:

在 Cesium 视图中添加一个蓝色点和标签“上海”,点击该实体时会弹出信息窗口,显示城市名称和人口信息。

添加路径和轨迹

Cesium 支持绘制路径和轨迹,适用于展示移动对象的运动轨迹。

示例:

const polyline = viewer.entities.add({
  name: '飞行路径',
  polyline: {
    positions: Cesium.Cartesian3.fromDegreesArray([
      116.4074, 39.9042, // 北京
      121.4737, 31.2304, // 上海
      113.2644, 23.1291  // 广州
    ]),
    width: 5,
    material: Cesium.Color.YELLOW,
  },
});

解释:

  • polyline.positions: 使用经纬度数组转换为笛卡尔坐标,定义路径点。
  • width: 线条宽度。
  • material: 线条颜色和样式。

效果:

在 Cesium 视图中绘制一条黄色线条,连接北京、上海和广州。

添加模型

Cesium 支持加载和展示 3D 模型,如 glTF 格式的模型。

示例:

const model = viewer.entities.add({
  name: 'Cesium 模型',
  position: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 1000),
  model: {
    uri: './path_to_model.glb',  // 3D 模型文件
    minimumPixelSize: 128,
  },
});

解释:

  • model.uri: 模型文件路径,支持 glTF、glb 等格式。
  • minimumPixelSize: 模型在屏幕上的最小像素大小,确保在远距离时仍可见。

效果:

在指定位置加载并展示一个 3D 模型,随着视角的变化,模型会动态旋转和缩放。

视图控制操作

定位到特定位置

通过摄像机控制,可以将视图定位到特定的地理位置。

示例:

viewer.camera.flyTo({
  destination: Cesium.Cartesian3.fromDegrees(121.4737, 31.2304, 1000000), // 上海
  orientation: {
    heading: Cesium.Math.toRadians(0.0),
    pitch: Cesium.Math.toRadians(-45.0),
    roll: 0.0
  },
});

解释:

  • destination: 目标位置,包含经纬度和高度。
  • orientation: 摄像机的朝向,包括航向(heading)、俯仰(pitch)和滚转(roll)。

效果:

摄像机会平滑飞行到上海位置,并调整视角以最佳展示该区域。

设置相机角度

可以直接控制摄像机的朝向和视角。

示例:

viewer.camera.lookAt(
  Cesium.Cartesian3.fromDegrees(121.4737, 31.2304),
  new Cesium.Cartesian3(0.0, 0.0, 1000)
);

解释:

  • lookAt: 将摄像机对准指定位置,并设置相对偏移量。
  • Cartesian3: 定义相对位置的偏移量。

效果:

摄像机将对准上海位置,并以指定的偏移量调整视角。

时间与动态数据

Cesium 提供了强大的时间控制功能,可以动态展示随时间变化的数据。

设置时间轴

时间轴用于控制和展示时间动态效果。

示例:

viewer.clock.startTime = Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z');
viewer.clock.currentTime = Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z');
viewer.clock.stopTime = Cesium.JulianDate.fromIso8601('2024-12-31T23:59:59Z');
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 循环播放
viewer.clock.multiplier = 1000; // 时间流速
viewer.timeline.zoomTo(viewer.clock.startTime, viewer.clock.stopTime);

解释:

  • startTime: 开始时间。
  • currentTime: 当前时间。
  • stopTime: 结束时间。
  • clockRange: 时间范围行为,LOOP_STOP 表示循环播放。
  • multiplier: 时间流速,1 为实时,1000 表示加速 1000 倍。
  • timeline.zoomTo: 设置时间轴的显示范围。

效果:

在 Cesium 视图中显示时间轴,时间会按设定的流速自动推进,并循环播放。

使用 CZML 进行动态数据展示

CZML 是 Cesium 的一种数据格式,用于描述时间序列数据。可以通过 CZML 动态地添加和显示对象。

示例:

const czmlData = [
  {
    "id": "document",
    "name": "CZML Path",
    "version": "1.0"
  },
  {
    "id": "point1",
    "name": "动态点",
    "position": {
      "cartographicDegrees": [116.4074, 39.9042, 1000000]
    },
    "point": {
      "pixelSize": 10,
      "color": {
        "rgba": [255, 0, 0, 255]
      }
    },
    "availability": "2024-01-01T00:00:00Z/2024-12-31T23:59:59Z"
  }
];

const czmlDataSource = new Cesium.CzmlDataSource();
viewer.dataSources.add(czmlDataSource);
czmlDataSource.load(czmlData);
viewer.zoomTo(czmlDataSource);

解释:

  • CZML 数据结构: 包含文档信息和实体定义。
  • position.cartographicDegrees: 定义实体的经纬度和高度。
  • availability: 定义实体的可用时间范围。

效果:

在 Cesium 视图中动态展示一个红色点,随着时间的变化,点的位置和属性可以随之更新。

空间数据与图层

加载 GeoJSON 数据

示例:

const geoJsonPromise = Cesium.GeoJsonDataSource.load('./path_to_geojson_file.geojson');
geoJsonPromise.then(function(dataSource) {
  viewer.dataSources.add(dataSource);
  viewer.zoomTo(dataSource);
});

解释:

  • GeoJsonDataSource.load: 加载 GeoJSON 文件。
  • viewer.dataSources.add: 将 GeoJSON 数据源添加到 Viewer 中。
  • viewer.zoomTo: 自动飞行到数据源范围。
加载 KML 数据

示例:

const kmlDataSource = new Cesium.KmlDataSource();
viewer.dataSources.add(kmlDataSource);
kmlDataSource.load('./path_to_kml_file.kml');

解释:

  • KmlDataSource: 创建 KML 数据源实例。
  • viewer.dataSources.add: 将 KML 数据源添加到 Viewer 中。
  • kmlDataSource.load: 加载 KML 文件。

添加图层控制

切换地图图层

可以通过移除和添加不同的影像图层,实现地图图层的切换。

示例:

// 移除所有影像图层
viewer.imageryLayers.removeAll();

// 添加 OpenStreetMap 图层
viewer.imageryLayers.addImageryProvider(Cesium.createOpenStreetMapImageryProvider());

// 添加 Bing Maps 图层
viewer.imageryLayers.addImageryProvider(new Cesium.BingMapsImageryProvider({
  url : 'https://dev.virtualearth.net',
  key : 'Your Bing Maps Key',
  mapStyle : Cesium.BingMapsStyle.AERIAL
}));

解释:

  • removeAll: 移除所有现有的影像图层。
  • createOpenStreetMapImageryProvider: 创建 OpenStreetMap 影像图层。
  • BingMapsImageryProvider: 创建 Bing Maps 影像图层,需要提供 Bing Maps API Key。
切换地形图层

通过更改 terrainProvider,可以切换不同的地形数据源。

示例:

// 切换到 Cesium World Terrain
viewer.terrainProvider = Cesium.createWorldTerrain();

// 切换到无地形
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();

解释:

  • createWorldTerrain: 加载全球地形数据。
  • EllipsoidTerrainProvider: 使用椭球体地形,表示无地形。

用户交互操作

监听鼠标点击事件

通过 ScreenSpaceEventHandler,可以监听鼠标点击事件,获取点击位置的相关信息。

示例:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function(click) {
  const pickedObject = viewer.scene.pick(click.position);
  if (Cesium.defined(pickedObject) && pickedObject.id) {
    const name = pickedObject.id.name;
    const population = pickedObject.id.population;
    // 显示信息窗口
    viewer.selectedEntity = pickedObject.id;
    // 可以进一步定制信息窗口内容
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

解释:

  • ScreenSpaceEventHandler: 创建事件处理器,监听特定类型的事件。
  • setInputAction: 设置事件的处理函数。
  • pickedObject: 获取被点击的对象。
  • viewer.selectedEntity: 选中实体,自动显示信息窗口。

自定义样式与外观

可以通过自定义实体的样式,改变其在 Cesium 中的外观。

示例:

viewer.entities.add({
  name: '自定义标注',
  position: Cesium.Cartesian3.fromDegrees(120.1551, 30.2741),
  billboard: {
    image: './path_to_image.png',
    scale: 0.5,
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
    horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
  },
  label: {
    text: '杭州',
    font: '16px sans-serif',
    fillColor: Cesium.Color.WHITE,
    outlineColor: Cesium.Color.BLACK,
    outlineWidth: 3,
    verticalOrigin: Cesium.VerticalOrigin.TOP,
    pixelOffset: new Cesium.Cartesian2(0, 15),
  }
});

解释:

  • billboard: 添加自定义图标,指定图像路径和样式属性。
  • label: 添加文本标签,设置字体、颜色、边框等样式。
  • pixelOffset: 设置标签的像素偏移量,调整位置。

效果:

在 Cesium 视图中添加一个自定义图标和标签,展示杭州的位置和名称。

加载 3D Tiles 数据

3D Tiles 是 Cesium 用于高效传输和渲染大规模 3D 地理空间数据的格式,适用于建筑物、地形等复杂模型。

示例:

const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
  url: './path_to_3d_tiles/tileset.json'
}));

tileset.readyPromise.then(function(tileset) {
  viewer.zoomTo(tileset);
}).otherwise(function(error) {
  console.error(error);
});

解释:

  • Cesium3DTileset: 创建 3D Tiles 数据集实例。
  • scene.primitives.add: 将 3D Tiles 添加到场景中。
  • readyPromise: 等待数据集加载完成后自动飞行到数据集位置。

效果:

在 Cesium 视图中加载并展示 3D Tiles 数据集,如城市建筑物模型。

高级功能与扩展

Cesium 提供了许多高级功能和扩展,适用于更复杂的应用场景和需求。以下介绍一些常用的高级功能与扩展操作。

粒子系统

Cesium 支持粒子系统,用于创建烟雾、火焰、雨雪等动态效果。

示例:

const particleSystem = viewer.scene.primitives.add(new Cesium.ParticleSystem({
  image: './path_to_particle_image.png',
  startColor: Cesium.Color.WHITE.withAlpha(0.7),
  endColor: Cesium.Color.WHITE.withAlpha(0.0),
  startScale: 1.0,
  endScale: 4.0,
  minimumParticleLife: 2.0,
  maximumParticleLife: 5.0,
  minimumSpeed: 1.0,
  maximumSpeed: 3.0,
  imageSize: new Cesium.Cartesian2(20, 20),
  emissionRate: 1000,
  lifetime: 30.0,
  emitter: new Cesium.CircleEmitter(5.0)
}));

解释:

  • image: 粒子图像路径。
  • startColorendColor: 粒子的起始和结束颜色。
  • startScaleendScale: 粒子的起始和结束缩放比例。
  • minimumParticleLifemaximumParticleLife: 粒子的最小和最大寿命。
  • minimumSpeedmaximumSpeed: 粒子的最小和最大速度。
  • imageSize: 粒子图像大小。
  • emissionRate: 每秒发射的粒子数量。
  • lifetime: 粒子系统的寿命。
  • emitter: 粒子发射器,这里使用圆形发射器。

效果:

在 Cesium 视图中创建一个烟雾效果,粒子从圆形区域内发射,逐渐扩散和消失。

自定义渲染效果

通过自定义渲染效果,可以实现更复杂的视觉效果,如光影、特殊材质等。

示例:

使用着色器材质为实体添加动态效果。

const shaderMaterial = new Cesium.Material({
  fabric: {
    type: 'Image',
    uniforms: {
      image: './path_to_custom_shader_image.png',
    },
    sources: {
      image: `
        uniform sampler2D image;
        czm_material czm_getMaterial(czm_materialInput materialInput)
        {
          czm_material material = czm_getDefaultMaterial(materialInput);
          material.diffuse = texture(image, materialInput.st).rgb;
          material.alpha = texture(image, materialInput.st).a;
          return material;
        }
      `,
    },
  },
});

viewer.entities.add({
  name: '自定义材质实体',
  position: Cesium.Cartesian3.fromDegrees(120.1551, 30.2741),
  box: {
    dimensions: new Cesium.Cartesian3(100000.0, 100000.0, 100000.0),
    material: shaderMaterial,
  },
});

解释:

  • Cesium.Material: 创建自定义材质,使用 GLSL 着色器代码定义材质效果。
  • box.material: 将自定义材质应用到实体的盒子形状上。

效果:

在 Cesium 视图中添加一个带有自定义着色器效果的盒子实体,展示独特的视觉效果。

集成第三方库

Cesium 可以与其他 JavaScript 库集成,扩展功能和交互性。例如,与 D3.js 集成,实现数据驱动的可视化效果。

示例:

import * as d3 from 'd3';

// 创建 D3 相关的数据
const data = [
  { longitude: 116.4074, latitude: 39.9042, value: 10 },
  { longitude: 121.4737, latitude: 31.2304, value: 20 },
  { longitude: 113.2644, latitude: 23.1291, value: 30 },
];

// 使用 D3 生成颜色比例尺
const colorScale = d3.scaleSequential(d3.interpolateViridis)
  .domain([0, d3.max(data, d => d.value)]);

// 添加实体并应用 D3 颜色
data.forEach(d => {
  viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(d.longitude, d.latitude),
    point: {
      pixelSize: d.value,
      color: Cesium.Color.fromCssColorString(colorScale(d.value)),
    },
    label: {
      text: `${d.value}`,
      font: '12px sans-serif',
      fillColor: Cesium.Color.WHITE,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      pixelOffset: new Cesium.Cartesian2(0, -15),
    },
  });
});

解释:

  • D3.js: 用于数据驱动的可视化和颜色比例尺生成。
  • colorScale: 使用 D3 的颜色比例尺,根据数据值生成颜色。
  • Entities.add: 根据数据动态添加实体,并应用 D3 生成的颜色。

效果:

在 Cesium 视图中根据数据值添加多个点,点的大小和颜色由 D3.js 动态生成,实现数据驱动的可视化效果。

性能优化

在处理大规模数据和复杂场景时,性能优化至关重要。以下是一些常用的性能优化技巧:

  • 使用 Primitives 代替 Entities:Primitives 更高效,适用于渲染大量简单对象。
  • 批量添加数据:尽量一次性加载和添加数据,减少渲染开销。
  • 使用 3D Tiles:用于高效传输和渲染大规模 3D 数据。
  • 启用硬件加速:确保浏览器支持并启用 WebGL 硬件加速。
  • 减少渲染细节:通过 LOD(Level of Detail)技术,减少远距离对象的渲染细节。
  • 优化着色器和材质:使用高效的着色器代码,避免不必要的计算。

示例:

// 使用 Primitives 添加大量点
const positions = [];
const colors = [];
const pointSize = 5;

for (let i = 0; i < 10000; i++) {
  const longitude = Math.random() * 360 - 180;
  const latitude = Math.random() * 180 - 90;
  positions.push(Cesium.Cartesian3.fromDegrees(longitude, latitude));

  colors.push(Cesium.Color.fromRandom({ alpha: 1.0 }));
}

const pointPrimitiveCollection = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection());

positions.forEach((position, index) => {
  pointPrimitiveCollection.add({
    position: position,
    color: colors[index],
    pixelSize: pointSize,
  });
});

解释:

  • PointPrimitiveCollection: 使用 Primitives 集合添加大量点,提高渲染性能。
  • fromRandom: 为每个点生成随机颜色。
  • pixelSize: 设置点的像素大小。

效果:

在 Cesium 视图中高效地渲染 10,000 个随机分布的彩色点,保持流畅的渲染性能。

自定义控件

Cesium 提供了多种内置控件,但您也可以创建自定义控件,增强用户交互体验。

示例:

// 创建自定义按钮
const toolbar = document.createElement('div');
toolbar.style.position = 'absolute';
toolbar.style.top = '10px';
toolbar.style.left = '10px';
toolbar.style.backgroundColor = 'rgba(42, 42, 42, 0.8)';
toolbar.style.padding = '10px';
toolbar.style.borderRadius = '5px';
toolbar.style.zIndex = 1;

const flyButton = document.createElement('button');
flyButton.textContent = '飞行到上海';
flyButton.style.cursor = 'pointer';
flyButton.onclick = () => {
  viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(121.4737, 31.2304, 1000000),
  });
};

toolbar.appendChild(flyButton);
document.body.appendChild(toolbar);

解释:

  • DOM 操作: 创建一个自定义工具栏和按钮,添加到页面中。
  • 按钮事件: 点击按钮时,摄像机会飞行到上海位置。

效果:

在 Cesium 视图的左上角添加一个按钮,点击按钮后,摄像机飞行到上海位置。

案例分析

通过具体案例,深入理解 Cesium 的应用和功能。

案例 1:展示多个城市的位置和信息

需求:

在 Cesium 中展示多个城市的位置,并在点击时显示城市的详细信息。

实现步骤:

  1. 准备 GeoJSON 数据

    创建一个 cities.geojson 文件,包含多个城市的地理位置和属性信息。

    {
      "type": "FeatureCollection",
      "features": [
        {
          "type": "Feature",
          "properties": { "name": "北京", "population": 21540000 },
          "geometry": {
            "type": "Point",
            "coordinates": [116.4074, 39.9042]
          }
        },
        {
          "type": "Feature",
          "properties": { "name": "上海", "population": 24240000 },
          "geometry": {
            "type": "Point",
            "coordinates": [121.4737, 31.2304]
          }
        },
        {
          "type": "Feature",
          "properties": { "name": "广州", "population": 13500000 },
          "geometry": {
            "type": "Point",
            "coordinates": [113.2644, 23.1291]
          }
        }
      ]
    }
    
  2. 加载 GeoJSON 数据

    index.js 中加载 GeoJSON 数据,并添加到 Cesium Viewer。

    fetch('http://localhost:3001/api/cities')
      .then(response => response.json())
      .then(geoJson => {
        Cesium.GeoJsonDataSource.load(geoJson).then(dataSource => {
          viewer.dataSources.add(dataSource);
          viewer.zoomTo(dataSource);
    
          // 添加点击事件
          const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
          handler.setInputAction(function(click) {
            const pickedObject = viewer.scene.pick(click.position);
            if (Cesium.defined(pickedObject) && pickedObject.id) {
              const name = pickedObject.id.name;
              const population = pickedObject.id.population;
              // 显示信息窗口
              viewer.selectedEntity = pickedObject.id;
            }
          }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
        });
      })
      .catch(error => {
        console.error('Error fetching GeoJSON data:', error);
      });
    

效果:

在 Cesium 视图中展示北京、上海、广州三个城市的位置,点击任意城市点会弹出包含城市名称和人口的提示框。

案例 2:路径动画

需求:

展示一条飞行路径,并让相机沿路径飞行。

实现步骤:

  1. 定义路径

    使用多段经纬度坐标定义飞行路径。

    const pathPositions = Cesium.Cartesian3.fromDegreesArray([
      116.4074, 39.9042, // 北京
      121.4737, 31.2304, // 上海
      113.2644, 23.1291  // 广州
    ]);
    
    const pathEntity = viewer.entities.add({
      polyline: {
        positions: pathPositions,
        width: 5,
        material: Cesium.Color.BLUE,
      }
    });
    
  2. 实现相机飞行

    使用 camera.flyTo 方法沿路径飞行。

    viewer.camera.flyTo({
      destination: pathPositions[0],
      duration: 2,
      complete: () => {
        viewer.camera.flyTo({
          destination: pathPositions[1],
          duration: 2,
          complete: () => {
            viewer.camera.flyTo({
              destination: pathPositions[2],
              duration: 2,
            });
          }
        });
      }
    });
    

效果:

相机会依次飞行到北京、上海、广州,沿途展示路径线。

项目实践:构建一个 3D 城市可视化应用

通过一个实际项目,综合应用所学的 Cesium 知识,构建一个简单的 3D 城市可视化应用。

项目背景

构建一个展示多个城市的 3D 可视化应用,包含以下功能:

  • 展示城市的位置和人口信息。
  • 点击城市点查看详细信息。
  • 展示城市间的飞行路径。
  • 动态展示城市人口变化。

步骤详解

设计数据库(PostGIS)

确保您的 PostGIS 数据库中有 cities 表,包含城市名称、人口、地理位置等信息。

示例表结构:

CREATE TABLE cities (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    population INTEGER,
    geom GEOGRAPHY(Point, 4326)
);

插入示例数据:

INSERT INTO cities (name, population, geom)
VALUES
('北京', 21540000, ST_GeogFromText('SRID=4326;POINT(116.4074 39.9042)')),
('上海', 24240000, ST_GeogFromText('SRID=4326;POINT(121.4737 31.2304)')),
('广州', 13500000, ST_GeogFromText('SRID=4326;POINT(113.2644 23.1291)'));
开发后端 API

确保后端 API 能够从 PostGIS 数据库中获取城市数据,并以 GeoJSON 格式返回。参考前述的 与 PostGIS 集成 部分。

开发前端 Cesium 应用
设置项目

按照前述的 Cesium 的安装与配置 步骤,创建并配置 Cesium 项目。

加载城市数据

index.js 中加载后端 API 提供的城市数据,并展示在 Cesium 中。

import Cesium from 'cesium/Cesium';
import 'cesium/Widgets/widgets.css';

// Cesium 配置
window.CESIUM_BASE_URL = './';

// 创建 Viewer 实例
const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain(),
});

// 从后端 API 获取城市数据并加载
fetch('http://localhost:3001/api/cities')
  .then(response => response.json())
  .then(geoJson => {
    Cesium.GeoJsonDataSource.load(geoJson).then(dataSource => {
      viewer.dataSources.add(dataSource);
      viewer.zoomTo(dataSource);

      // 添加点击事件
      const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
      handler.setInputAction(function(click) {
        const pickedObject = viewer.scene.pick(click.position);
        if (Cesium.defined(pickedObject) && pickedObject.id) {
          const name = pickedObject.id.name;
          const population = pickedObject.id.population;
          // 显示信息窗口
          viewer.selectedEntity = pickedObject.id;
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    });
  })
  .catch(error => {
    console.error('Error fetching GeoJSON data:', error);
  });

// 添加路径实体
const pathPositions = Cesium.Cartesian3.fromDegreesArray([
  116.4074, 39.9042, // 北京
  121.4737, 31.2304, // 上海
  113.2644, 23.1291  // 广州
]);

const pathEntity = viewer.entities.add({
  name: '飞行路径',
  polyline: {
    positions: pathPositions,
    width: 5,
    material: Cesium.Color.GREEN,
  }
});

// 添加飞行按钮
const toolbar = document.createElement('div');
toolbar.style.position = 'absolute';
toolbar.style.top = '10px';
toolbar.style.left = '10px';
toolbar.style.backgroundColor = 'rgba(42, 42, 42, 0.8)';
toolbar.style.padding = '10px';
toolbar.style.borderRadius = '5px';
toolbar.style.zIndex = 1;

const flyButton = document.createElement('button');
flyButton.textContent = '飞行路径';
flyButton.style.cursor = 'pointer';
flyButton.onclick = () => {
  viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 10000000),
    orientation: {
      heading: Cesium.Math.toRadians(0.0),
      pitch: Cesium.Math.toRadians(-90.0),
      roll: 0.0
    },
    duration: 5
  });
};

toolbar.appendChild(flyButton);
document.body.appendChild(toolbar);

解释:

  • Fetch API: 从后端 API 获取城市数据,并加载到 Cesium 中。
  • Click Event: 监听鼠标点击事件,选中实体时显示信息窗口。
  • Path Entity: 添加一条连接北京、上海、广州的绿色路径线。
  • Fly Button: 添加一个自定义按钮,点击时摄像机沿路径飞行。
添加样式和控件

index.html 中添加必要的样式和控件样式引用。

<link href="https://cesium.com/downloads/cesiumjs/releases/1.93/Build/Cesium/Widgets/widgets.css" rel="stylesheet">

解释:

  • 引入 Cesium Widgets 的 CSS 样式,确保控件正常显示。

运行项目

确保后端服务器正在运行,然后在终端中运行:

npm start

浏览器将打开 http://localhost:8080/,您应能看到多个城市的位置标注,以及一个飞行路径按钮。点击按钮,相机会飞行到路径起点。

添加动态人口变化

需求:

展示城市人口随时间的变化,动态更新实体信息。

实现步骤:

  1. 扩展后端 API

    修改后端 API,支持根据时间参数返回不同时间点的城市数据。

    app.get('/api/cities', async (req, res) => {
      const { date } = req.query; // 接收日期参数
      try {
        const result = await pool.query(`
          SELECT name, population, ST_AsGeoJSON(geom) AS geojson
          FROM cities
          WHERE updated_at <= $1
        `, [date || '2024-12-31']);
        const features = result.rows.map(row => ({
          type: 'Feature',
          properties: {
            name: row.name,
            population: row.population,
          },
          geometry: JSON.parse(row.geojson),
        }));
        res.json({
          type: 'FeatureCollection',
          features: features,
        });
      } catch (err) {
        console.error(err);
        res.status(500).send('Server Error');
      }
    });
    

    解释:

    • date: 从查询参数获取日期,用于过滤数据。
    • updated_at: 假设 cities 表有 updated_at 字段,记录数据更新时间。
  2. 在前端添加时间控制

    使用 Cesium 的时间线和时钟控制动态数据加载。

    // 设置时钟
    viewer.clock.startTime = Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z');
    viewer.clock.currentTime = Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z');
    viewer.clock.stopTime = Cesium.JulianDate.fromIso8601('2024-12-31T23:59:59Z');
    viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
    viewer.clock.multiplier = 1000; // 时间流速
    
    // 监听时间变化,动态加载数据
    viewer.clock.onTick.addEventListener(function(clock) {
      const isoDate = Cesium.JulianDate.toIso8601(clock.currentTime, Cesium.Iso8601Format.DATE_TIME);
      fetch(`http://localhost:3001/api/cities?date=${isoDate}`)
        .then(response => response.json())
        .then(geoJson => {
          viewer.dataSources.removeAll();
          Cesium.GeoJsonDataSource.load(geoJson).then(dataSource => {
            viewer.dataSources.add(dataSource);
          });
        })
        .catch(error => {
          console.error('Error fetching GeoJSON data:', error);
        });
    });
    

    解释:

    • viewer.clock: 设置时钟的开始时间、当前时间、结束时间、范围和流速。
    • onTick: 在每次时钟滴答时触发,获取当前时间并请求后端 API 获取对应时间点的数据。
    • removeAll: 移除当前所有数据源,加载新的数据源。
    • GeoJsonDataSource.load: 加载新的 GeoJSON 数据。

效果:

随着时间的推进,Cesium 视图中的城市人口数据将动态更新,实体信息窗口显示最新的人口信息。

高级功能与扩展

Cesium 提供了许多高级功能和扩展,适用于更复杂的应用场景和需求。以下介绍一些常用的高级功能与扩展操作。

粒子系统

Cesium 支持粒子系统,用于创建烟雾、火焰、雨雪等动态效果。

示例:

const particleSystem = viewer.scene.primitives.add(new Cesium.ParticleSystem({
  image: './path_to_particle_image.png',
  startColor: Cesium.Color.WHITE.withAlpha(0.7),
  endColor: Cesium.Color.WHITE.withAlpha(0.0),
  startScale: 1.0,
  endScale: 4.0,
  minimumParticleLife: 2.0,
  maximumParticleLife: 5.0,
  minimumSpeed: 1.0,
  maximumSpeed: 3.0,
  imageSize: new Cesium.Cartesian2(20, 20),
  emissionRate: 1000,
  lifetime: 30.0,
  emitter: new Cesium.CircleEmitter(5.0)
}));

解释:

  • image: 粒子图像路径。
  • startColorendColor: 粒子的起始和结束颜色。
  • startScaleendScale: 粒子的起始和结束缩放比例。
  • minimumParticleLifemaximumParticleLife: 粒子的最小和最大寿命。
  • minimumSpeedmaximumSpeed: 粒子的最小和最大速度。
  • imageSize: 粒子图像大小。
  • emissionRate: 每秒发射的粒子数量。
  • lifetime: 粒子系统的寿命。
  • emitter: 粒子发射器,这里使用圆形发射器。

效果:

在 Cesium 视图中创建一个烟雾效果,粒子从圆形区域内发射,逐渐扩散和消失。

自定义渲染效果

通过自定义渲染效果,可以实现更复杂的视觉效果,如光影、特殊材质等。

示例:

使用着色器材质为实体添加动态效果。

const shaderMaterial = new Cesium.Material({
  fabric: {
    type: 'Image',
    uniforms: {
      image: './path_to_custom_shader_image.png',
    },
    sources: {
      image: `
        uniform sampler2D image;
        czm_material czm_getMaterial(czm_materialInput materialInput)
        {
          czm_material material = czm_getDefaultMaterial(materialInput);
          material.diffuse = texture(image, materialInput.st).rgb;
          material.alpha = texture(image, materialInput.st).a;
          return material;
        }
      `,
    },
  },
});

viewer.entities.add({
  name: '自定义材质实体',
  position: Cesium.Cartesian3.fromDegrees(120.1551, 30.2741),
  box: {
    dimensions: new Cesium.Cartesian3(100000.0, 100000.0, 100000.0),
    material: shaderMaterial,
  },
});

解释:

  • Cesium.Material: 创建自定义材质,使用 GLSL 着色器代码定义材质效果。
  • box.material: 将自定义材质应用到实体的盒子形状上。

效果:

在 Cesium 视图中添加一个带有自定义着色器效果的盒子实体,展示独特的视觉效果。

集成第三方库

Cesium 可以与其他 JavaScript 库集成,扩展功能和交互性。例如,与 D3.js 集成,实现数据驱动的可视化效果。

示例:

import * as d3 from 'd3';

// 创建 D3 相关的数据
const data = [
  { longitude: 116.4074, latitude: 39.9042, value: 10 },
  { longitude: 121.4737, latitude: 31.2304, value: 20 },
  { longitude: 113.2644, latitude: 23.1291, value: 30 },
];

// 使用 D3 生成颜色比例尺
const colorScale = d3.scaleSequential(d3.interpolateViridis)
  .domain([0, d3.max(data, d => d.value)]);

// 添加实体并应用 D3 颜色
data.forEach(d => {
  viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(d.longitude, d.latitude),
    point: {
      pixelSize: d.value,
      color: Cesium.Color.fromCssColorString(colorScale(d.value)),
    },
    label: {
      text: `${d.value}`,
      font: '12px sans-serif',
      fillColor: Cesium.Color.WHITE,
      outlineColor: Cesium.Color.BLACK,
      outlineWidth: 3,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      pixelOffset: new Cesium.Cartesian2(0, -15),
    },
  });
});

解释:

  • D3.js: 用于数据驱动的可视化和颜色比例尺生成。
  • colorScale: 使用 D3 的颜色比例尺,根据数据值生成颜色。
  • Entities.add: 根据数据动态添加实体,并应用 D3 生成的颜色。

效果:

在 Cesium 视图中根据数据值添加多个点,点的大小和颜色由 D3.js 动态生成,实现数据驱动的可视化效果。

性能优化

在处理大规模数据和复杂场景时,性能优化至关重要。以下是一些常用的性能优化技巧:

  • 使用 Primitives 代替 Entities:Primitives 更高效,适用于渲染大量简单对象。
  • 批量添加数据:尽量一次性加载和添加数据,减少渲染开销。
  • 使用 3D Tiles:用于高效传输和渲染大规模 3D 数据。
  • 启用硬件加速:确保浏览器支持并启用 WebGL 硬件加速。
  • 减少渲染细节:通过 LOD(Level of Detail)技术,减少远距离对象的渲染细节。
  • 优化着色器和材质:使用高效的着色器代码,避免不必要的计算。

示例:

// 使用 Primitives 添加大量点
const positions = [];
const colors = [];
const pointSize = 5;

for (let i = 0; i < 10000; i++) {
  const longitude = Math.random() * 360 - 180;
  const latitude = Math.random() * 180 - 90;
  positions.push(Cesium.Cartesian3.fromDegrees(longitude, latitude));

  colors.push(Cesium.Color.fromRandom({ alpha: 1.0 }));
}

const pointPrimitiveCollection = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection());

positions.forEach((position, index) => {
  pointPrimitiveCollection.add({
    position: position,
    color: colors[index],
    pixelSize: pointSize,
  });
});

解释:

  • PointPrimitiveCollection: 使用 Primitives 集合添加大量点,提高渲染性能。
  • fromRandom: 为每个点生成随机颜色。
  • pixelSize: 设置点的像素大小。

效果:

在 Cesium 视图中高效地渲染 10,000 个随机分布的彩色点,保持流畅的渲染性能。

自定义控件

Cesium 提供了多种内置控件,但您也可以创建自定义控件,增强用户交互体验。

示例:

// 创建自定义按钮
const toolbar = document.createElement('div');
toolbar.style.position = 'absolute';
toolbar.style.top = '10px';
toolbar.style.left = '10px';
toolbar.style.backgroundColor = 'rgba(42, 42, 42, 0.8)';
toolbar.style.padding = '10px';
toolbar.style.borderRadius = '5px';
toolbar.style.zIndex = 1;

const flyButton = document.createElement('button');
flyButton.textContent = '飞行到上海';
flyButton.style.cursor = 'pointer';
flyButton.onclick = () => {
  viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(121.4737, 31.2304, 1000000),
  });
};

toolbar.appendChild(flyButton);
document.body.appendChild(toolbar);

解释:

  • DOM 操作: 创建一个自定义工具栏和按钮,添加到页面中。
  • 按钮事件: 点击按钮时,摄像机会飞行到上海位置。

效果:

在 Cesium 视图的左上角添加一个按钮,点击按钮后,摄像机飞行到上海位置。

案例分析

通过具体案例,深入理解 Cesium 的应用和功能。

案例 1:展示多个城市的位置和信息

需求:

在 Cesium 中展示多个城市的位置,并在点击时显示城市的详细信息。

实现步骤:

  1. 准备 GeoJSON 数据

    创建一个 cities.geojson 文件,包含多个城市的地理位置和属性信息。

    {
      "type": "FeatureCollection",
      "features": [
        {
          "type": "Feature",
          "properties": { "name": "北京", "population": 21540000 },
          "geometry": {
            "type": "Point",
            "coordinates": [116.4074, 39.9042]
          }
        },
        {
          "type": "Feature",
          "properties": { "name": "上海", "population": 24240000 },
          "geometry": {
            "type": "Point",
            "coordinates": [121.4737, 31.2304]
          }
        },
        {
          "type": "Feature",
          "properties": { "name": "广州", "population": 13500000 },
          "geometry": {
            "type": "Point",
            "coordinates": [113.2644, 23.1291]
          }
        }
      ]
    }
    
  2. 加载 GeoJSON 数据

    index.js 中加载 GeoJSON 数据,并添加到 Cesium Viewer。

    fetch('http://localhost:3001/api/cities')
      .then(response => response.json())
      .then(geoJson => {
        Cesium.GeoJsonDataSource.load(geoJson).then(dataSource => {
          viewer.dataSources.add(dataSource);
          viewer.zoomTo(dataSource);
    
          // 添加点击事件
          const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
          handler.setInputAction(function(click) {
            const pickedObject = viewer.scene.pick(click.position);
            if (Cesium.defined(pickedObject) && pickedObject.id) {
              const name = pickedObject.id.name;
              const population = pickedObject.id.population;
              // 显示信息窗口
              viewer.selectedEntity = pickedObject.id;
            }
          }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
        });
      })
      .catch(error => {
        console.error('Error fetching GeoJSON data:', error);
      });
    

效果:

在 Cesium 视图中展示北京、上海、广州三个城市的位置,点击任意城市点会弹出包含城市名称和人口的提示框。

案例 2:路径动画

需求:

展示一条飞行路径,并让相机沿路径飞行。

实现步骤:

  1. 定义路径

    使用多段经纬度坐标定义飞行路径。

    const pathPositions = Cesium.Cartesian3.fromDegreesArray([
      116.4074, 39.9042, // 北京
      121.4737, 31.2304, // 上海
      113.2644, 23.1291  // 广州
    ]);
    
    const pathEntity = viewer.entities.add({
      polyline: {
        positions: pathPositions,
        width: 5,
        material: Cesium.Color.BLUE,
      }
    });
    
  2. 实现相机飞行

    使用 camera.flyTo 方法沿路径飞行。

    viewer.camera.flyTo({
      destination: pathPositions[0],
      duration: 2,
      complete: () => {
        viewer.camera.flyTo({
          destination: pathPositions[1],
          duration: 2,
          complete: () => {
            viewer.camera.flyTo({
              destination: pathPositions[2],
              duration: 2,
            });
          }
        });
      }
    });
    

效果:

相机会依次飞行到北京、上海、广州,沿途展示路径线。

项目实践:构建一个 3D 城市可视化应用

通过一个实际项目,综合应用所学的 Cesium 知识,构建一个简单的 3D 城市可视化应用。

项目背景

构建一个展示多个城市的 3D 可视化应用,包含以下功能:

  • 展示城市的位置和人口信息。
  • 点击城市点查看详细信息。
  • 展示城市间的飞行路径。
  • 动态展示城市人口变化。

步骤详解

设计数据库(PostGIS)

确保您的 PostGIS 数据库中有 cities 表,包含城市名称、人口、地理位置等信息。

示例表结构:

CREATE TABLE cities (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    population INTEGER,
    geom GEOGRAPHY(Point, 4326)
);

插入示例数据:

INSERT INTO cities (name, population, geom)
VALUES
('北京', 21540000, ST_GeogFromText('SRID=4326;POINT(116.4074 39.9042)')),
('上海', 24240000, ST_GeogFromText('SRID=4326;POINT(121.4737 31.2304)')),
('广州', 13500000, ST_GeogFromText('SRID=4326;POINT(113.2644 23.1291)'));
开发后端 API

确保后端 API 能够从 PostGIS 数据库中获取城市数据,并以 GeoJSON 格式返回。参考前述的 与 PostGIS 集成 部分。

开发前端 Cesium 应用
设置项目

按照前述的 Cesium 的安装与配置 步骤,创建并配置 Cesium 项目。

加载城市数据

index.js 中加载后端 API 提供的城市数据,并展示在 Cesium 中。

import Cesium from 'cesium/Cesium';
import 'cesium/Widgets/widgets.css';

// Cesium 配置
window.CESIUM_BASE_URL = './';

// 创建 Viewer 实例
const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain(),
});

// 从后端 API 获取城市数据并加载
fetch('http://localhost:3001/api/cities')
  .then(response => response.json())
  .then(geoJson => {
    Cesium.GeoJsonDataSource.load(geoJson).then(dataSource => {
      viewer.dataSources.add(dataSource);
      viewer.zoomTo(dataSource);

      // 添加点击事件
      const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
      handler.setInputAction(function(click) {
        const pickedObject = viewer.scene.pick(click.position);
        if (Cesium.defined(pickedObject) && pickedObject.id) {
          const name = pickedObject.id.name;
          const population = pickedObject.id.population;
          // 显示信息窗口
          viewer.selectedEntity = pickedObject.id;
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    });
  })
  .catch(error => {
    console.error('Error fetching GeoJSON data:', error);
  });

// 添加路径实体
const pathPositions = Cesium.Cartesian3.fromDegreesArray([
  116.4074, 39.9042, // 北京
  121.4737, 31.2304, // 上海
  113.2644, 23.1291  // 广州
]);

const pathEntity = viewer.entities.add({
  name: '飞行路径',
  polyline: {
    positions: pathPositions,
    width: 5,
    material: Cesium.Color.GREEN,
  }
});

// 添加飞行按钮
const toolbar = document.createElement('div');
toolbar.style.position = 'absolute';
toolbar.style.top = '10px';
toolbar.style.left = '10px';
toolbar.style.backgroundColor = 'rgba(42, 42, 42, 0.8)';
toolbar.style.padding = '10px';
toolbar.style.borderRadius = '5px';
toolbar.style.zIndex = 1;

const flyButton = document.createElement('button');
flyButton.textContent = '飞行路径';
flyButton.style.cursor = 'pointer';
flyButton.onclick = () => {
  viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 10000000),
    orientation: {
      heading: Cesium.Math.toRadians(0.0),
      pitch: Cesium.Math.toRadians(-90.0),
      roll: 0.0
    },
    duration: 5
  });
};

toolbar.appendChild(flyButton);
document.body.appendChild(toolbar);

解释:

  • Fetch API: 从后端 API 获取城市数据,并加载到 Cesium 中。
  • Click Event: 监听鼠标点击事件,选中实体时显示信息窗口。
  • Path Entity: 添加一条连接北京、上海、广州的绿色路径线。
  • Fly Button: 添加一个自定义按钮,点击时摄像机沿路径飞行。
添加样式和控件

index.html 中添加必要的样式和控件样式引用。

<link href="https://cesium.com/downloads/cesiumjs/releases/1.93/Build/Cesium/Widgets/widgets.css" rel="stylesheet">

解释:

  • 引入 Cesium Widgets 的 CSS 样式,确保控件正常显示。

运行项目

确保后端服务器正在运行,然后在终端中运行:

npm start

浏览器将打开 http://localhost:8080/,您应能看到多个城市的位置标注,以及一个飞行路径按钮。点击按钮,相机会飞行到路径起点。

添加动态人口变化

需求:

展示城市人口随时间的变化,动态更新实体信息。

实现步骤:

  1. 扩展后端 API

    修改后端 API,支持根据时间参数返回不同时间点的城市数据。

    app.get('/api/cities', async (req, res) => {
      const { date } = req.query; // 接收日期参数
      try {
        const result = await pool.query(`
          SELECT name, population, ST_AsGeoJSON(geom) AS geojson
          FROM cities
          WHERE updated_at <= $1
        `, [date || '2024-12-31']);
        const features = result.rows.map(row => ({
          type: 'Feature',
          properties: {
            name: row.name,
            population: row.population,
          },
          geometry: JSON.parse(row.geojson),
        }));
        res.json({
          type: 'FeatureCollection',
          features: features,
        });
      } catch (err) {
        console.error(err);
        res.status(500).send('Server Error');
      }
    });
    

    解释:

    • date: 从查询参数获取日期,用于过滤数据。
    • updated_at: 假设 cities 表有 updated_at 字段,记录数据更新时间。
  2. 在前端添加时间控制

    使用 Cesium 的时间线和时钟控制动态数据加载。

    // 设置时钟
    viewer.clock.startTime = Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z');
    viewer.clock.currentTime = Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z');
    viewer.clock.stopTime = Cesium.JulianDate.fromIso8601('2024-12-31T23:59:59Z');
    viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
    viewer.clock.multiplier = 1000; // 时间流速
    
    // 监听时间变化,动态加载数据
    viewer.clock.onTick.addEventListener(function(clock) {
      const isoDate = Cesium.JulianDate.toIso8601(clock.currentTime, Cesium.Iso8601Format.DATE_TIME);
      fetch(`http://localhost:3001/api/cities?date=${isoDate}`)
        .then(response => response.json())
        .then(geoJson => {
          viewer.dataSources.removeAll();
          Cesium.GeoJsonDataSource.load(geoJson).then(dataSource => {
            viewer.dataSources.add(dataSource);
          });
        })
        .catch(error => {
          console.error('Error fetching GeoJSON data:', error);
        });
    });
    

    解释:

    • viewer.clock: 设置时钟的开始时间、当前时间、结束时间、范围和流速。
    • onTick: 在每次时钟滴答时触发,获取当前时间并请求后端 API 获取对应时间点的数据。
    • removeAll: 移除当前所有数据源,加载新的数据源。
    • GeoJsonDataSource.load: 加载新的 GeoJSON 数据。

效果:

随着时间的推进,Cesium 视图中的城市人口数据将动态更新,实体信息窗口显示最新的人口信息。

优化与扩展

  • 添加更多属性:如城市的行政区划、经济数据等,丰富实体的属性信息。
  • 引入更多空间数据:如道路、河流、区域边界等,丰富地理信息系统的功能。
  • 实现高级空间分析:如空间聚类、热点分析、路径优化等,提升数据分析能力。
  • 集成其他数据源:如实时交通数据、气象数据等,实现实时数据可视化。

常见问题与解决方法

Cesium 无法正确加载资源

问题原因: 静态资源路径配置不正确,导致 Cesium 无法找到 Worker、Assets 等资源。

解决方法:

  • 确保静态资源路径正确:在 Webpack 配置中使用 CopyWebpackPlugin 复制 Cesium 的 Workers、Assets 和 Widgets 目录。
  • 设置 CESIUM_BASE_URL:确保 window.CESIUM_BASE_URL 指向正确的资源路径。

示例:

window.CESIUM_BASE_URL = './';

后端 API 请求被阻止(CORS 问题)

问题原因: 浏览器的同源策略阻止前端应用访问不同源的后端 API。

解决方法:

  • 在后端服务器中启用 CORS:使用 cors 中间件解决跨域问题。

示例:

const cors = require('cors');
app.use(cors());

Cesium 报错 “Cesium.js not found”

问题原因: Cesium 的静态资源未正确加载,导致 Cesium.js 文件无法找到。

解决方法:

  • 确认 Webpack 已正确复制 Cesium 的静态资源文件:检查 CopyWebpackPlugin 配置是否正确。
  • 检查 publicPath 配置是否正确:确保 publicPath 指向 Cesium 静态资源的正确位置。

性能问题

问题原因: 加载过多的实体或高分辨率的 3D Tiles,导致渲染性能下降。

解决方法:

  • 优化数据量:减少加载的数据量,使用数据分块或延迟加载技术。
  • 使用 Primitives:对于大量简单对象,使用 Primitives 提高渲染效率。
  • 启用硬件加速:确保浏览器支持并启用 WebGL 硬件加速。
  • 使用 Level of Detail(LOD)技术:减少远距离对象的渲染细节,提升性能。
  • 优化着色器和材质:使用高效的着色器代码,避免复杂计算。

地理坐标系错误

问题原因: 加载的数据使用了错误的坐标系,导致在 Cesium 中显示位置不正确。

解决方法:

  • 确保所有地理数据使用 WGS84 坐标系(EPSG:4326)
  • 使用 PostGIS 的 ST_Transform 函数转换坐标系

示例:

SELECT name, population, ST_AsGeoJSON(ST_Transform(geom, 4326)) AS geojson
FROM cities;

进一步学习资源

官方文档

教程与书籍

社区与论坛

视频课程

开源项目

  • Cesium Examples - 互动式代码示例,方便学习和实验。
  • TerriaJS - 基于 Cesium 的开源地理空间数据平台。
  • Cesium Ion - 提供数据托管和处理服务的商业平台,部分功能开源。

工具与插件

  • Cesium Editor: 交互式代码编辑和调试工具。
  • 3D Tiles Tools: 用于创建和优化 3D Tiles 数据的工具集。
  • Cesium for Unreal: 将 Cesium 与 Unreal Engine 集成,实现高质量的 3D 可视化。
  • CesiumJS Plugins: 如地理编码、数据转换等插件,扩展 Cesium 的功能。
### starRC、LEF 和 DEF 文件的 EDA 工具使用教程 #### 关于 starRC 的使用说明 starRC 是由 Synopsys 开发的一款用于寄生参数提取 (PEX) 的工具,在 detail routing 完成之后被调用,以提供精确的电阻电容延迟分析数据[^2]。该工具能够处理复杂的多层互连结构并支持多种工艺节点。 对于 starRC 的具体操作指南,通常可以从官方文档获取最权威的信息。访问 Synopsys 官方网站的技术资源页面,可以找到最新的产品手册以及应用笔记等资料。此外,还可以通过在线帮助系统获得交互式的指导和支持服务。 #### LEF 和 DEF 文件格式解析及其在 Cadence 中的应用 LEF(Library Exchange Format)和 DEF(Design Exchange Format)是两种广泛应用于集成电路布局布线阶段的标准文件格式之一[^3]。前者主要用于描述标准单元库中的元件几何形状;后者则记录了整个芯片版图的设计信息,包括但不限于各个模块的位置关系、网络连接情况等重要细节。 当涉及到这些文件类型的编辑或读取时,Cadence 提供了一系列强大的平台级解决方案,比如 Virtuoso Layout Editor 就可以直接打开并修改 LEF/DEF 格式的项目工程。为了更好地理解和运用这两种文件格式,建议参阅 Cadence 发布的相关培训材料或是参加其举办的专项课程学习活动。 ```bash # 示例命令:查看 LEF 或 DEF 文件内容 cat my_design.lef cat my_design.def ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值