Cesium 是一个开源的 JavaScript 库,用于在网页浏览器中创建高性能的 3D 地球和地图应用。它支持丰富的地理空间数据可视化,广泛应用于地理信息系统(GIS)、仿真、数据分析等领域。无论您是前端开发者、GIS 专业人士,还是对 3D 地图感兴趣的爱好者,学习 Cesium 都能帮助您构建交互式、动态的地理空间应用。本指南将全面介绍 Cesium 的安装、配置、基础操作、高级功能、与 PostGIS 的集成、案例分析及项目实践,帮助您系统掌握并应用 Cesium。
目录
- 什么是 Cesium?
- Cesium 的安装与配置
- Cesium 基础概念
- 创建第一个 Cesium 应用
- 加载与展示空间数据
- 与 PostGIS 集成
- 常用功能与操作
- 高级功能与扩展
- 案例分析
- 项目实践:构建一个 3D 城市可视化应用
- 常见问题与解决方法
- 进一步学习资源
什么是 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,可以按照以下步骤进行安装:
-
下载 Node.js:访问 Node.js 官方网站 下载适用于您操作系统的安装包并安装。
-
验证安装:
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-loader
和css-loader
。 - Resolve Alias: 设置
cesium
别名,指向 Cesium 的主文件。
创建项目结构
在 src
目录下创建 index.js
和 index.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: 粒子图像路径。
- startColor 和 endColor: 粒子的起始和结束颜色。
- startScale 和 endScale: 粒子的起始和结束缩放比例。
- minimumParticleLife 和 maximumParticleLife: 粒子的最小和最大寿命。
- minimumSpeed 和 maximumSpeed: 粒子的最小和最大速度。
- 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 中展示多个城市的位置,并在点击时显示城市的详细信息。
实现步骤:
-
准备 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] } } ] }
-
加载 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:路径动画
需求:
展示一条飞行路径,并让相机沿路径飞行。
实现步骤:
-
定义路径
使用多段经纬度坐标定义飞行路径。
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, } });
-
实现相机飞行
使用
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/
,您应能看到多个城市的位置标注,以及一个飞行路径按钮。点击按钮,相机会飞行到路径起点。
添加动态人口变化
需求:
展示城市人口随时间的变化,动态更新实体信息。
实现步骤:
-
扩展后端 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
字段,记录数据更新时间。
-
在前端添加时间控制
使用 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: 粒子图像路径。
- startColor 和 endColor: 粒子的起始和结束颜色。
- startScale 和 endScale: 粒子的起始和结束缩放比例。
- minimumParticleLife 和 maximumParticleLife: 粒子的最小和最大寿命。
- minimumSpeed 和 maximumSpeed: 粒子的最小和最大速度。
- 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 中展示多个城市的位置,并在点击时显示城市的详细信息。
实现步骤:
-
准备 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] } } ] }
-
加载 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:路径动画
需求:
展示一条飞行路径,并让相机沿路径飞行。
实现步骤:
-
定义路径
使用多段经纬度坐标定义飞行路径。
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, } });
-
实现相机飞行
使用
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/
,您应能看到多个城市的位置标注,以及一个飞行路径按钮。点击按钮,相机会飞行到路径起点。
添加动态人口变化
需求:
展示城市人口随时间的变化,动态更新实体信息。
实现步骤:
-
扩展后端 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
字段,记录数据更新时间。
-
在前端添加时间控制
使用 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 入门教程
- 《Learning CesiumJS》 - 适合初学者的详细教程书籍。
社区与论坛
- Cesium Discourse 论坛
- Stack Overflow 上的 Cesium 标签
- GIS Stack Exchange
视频课程
- YouTube - Cesium 教程
- 在线学习平台如 Udemy、Coursera 上的 Cesium 相关课程。
开源项目
- Cesium Examples - 互动式代码示例,方便学习和实验。
- TerriaJS - 基于 Cesium 的开源地理空间数据平台。
- Cesium Ion - 提供数据托管和处理服务的商业平台,部分功能开源。
工具与插件
- Cesium Editor: 交互式代码编辑和调试工具。
- 3D Tiles Tools: 用于创建和优化 3D Tiles 数据的工具集。
- Cesium for Unreal: 将 Cesium 与 Unreal Engine 集成,实现高质量的 3D 可视化。
- CesiumJS Plugins: 如地理编码、数据转换等插件,扩展 Cesium 的功能。