# 原文链接
Cesium 基础使用介绍
https://www.cnblogs.com/shoufengwei/p/7998468.html#commentform
# 前言
最近折腾了一下三维地球,本文简单为大家介绍一款开源的三维地球软件 ——Cesium,以及如何快速上手 Cesium。当然三维地球重要的肯定不是数据显示,这只是数据可视化的一小部分,重要的应该是背后的数据生成及处理等。本文先为大家介绍这简单的部分。
# Cesium 简介
Github 地址:https://github.com/AnalyticalGraphicsInc/cesium。官方介绍如下:
An open-source JavaScript library for world-class 3D globes and maps.
非常简洁:Cesium 是一款开源的基于 JS 的 3D 地图框架。具体这里也不多做介绍,各位可以自行浏览其网站。其实他就是一个地图可视化框架,与 Leaft-let 以及 OpenLayer 等没有本质的区别,只是 Cesium 支持三维场景,做的更漂亮。
# Cesium 简单使用
# 安装及测试
最简单的安装方式,就是普通的 JS 文件加载,只需要从 Github 中下载其 js 代码,放到自己的项目中,在 html 页面中引用即可。如下:
当然如果要直接使用其示例等,还是需要按照其文档使用 nodejs 一步步安装。安装完之后,新建 html 页面并引用 Cesium.js,如下:
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Hello 3D Earth</title> | |
<script src="CesiumUnminified/Cesium.js"></script> | |
<style> | |
@import url(CesiumUnminified/Widgets/widgets.css); | |
html, body, #cesiumContainer { | |
width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="cesiumContainer"></div> | |
<script src="app.js"></script> | |
</body> | |
</html> |
app.js 只需要一行代码即可,内容如下:
viewer = new Cesium.Viewer('cesiumContainer'); |
其中 cesiumContainer 为 html 中的地图显示 div 的 id。就是这么简单,浏览器打开上述 html 页面,便可看到一个三维地球。底图为微软影像只是加载到了三维地球上,包含放大、缩小、平移等基本在线地图功能,同时还包含了时间轴等与时间有关的控件,这是 Cesium 的一个特色,其地图、对象以及场景等能与时间相关联。
# Viewer 和地图图层
# Viewer
Viewer 是 Cesium 的核心,上面的一行代码实现了基本框架的加载,我们可以为其添加参数,实现不同类型的地图加载,如下:
var viewer = new Cesium.Viewer("cesiumContainer", { | |
animation: true, // 是否显示动画控件 (左下方那个) | |
baseLayerPicker: true, // 是否显示图层选择控件 | |
geocoder: true, // 是否显示地名查找控件 | |
timeline: true, // 是否显示时间线控件 | |
sceneModePicker: true, // 是否显示投影方式控件 | |
navigationHelpButton: false, // 是否显示帮助信息控件 | |
infoBox: true, // 是否显示点击要素之后显示的信息 | |
}); |
这里面设置了地图浏览中几个控件的显示与否。这里主要介绍 baseLayerPicker 项,他可以设置图层选择空间是否可见,如果设置不可见,则需要设置自定义图层作为默认图层。当然设置可见之后也可以更改其中的图层为自定义图层。
# 图层介绍
Cesium 中的图层分为两种:一种是普通图层,包含影像、线划等普通显示图层;还有一种是地形图层,用于真实的模拟地球表面的场景,Cesium 会根据加载到的地形瓦片以三维的方式显示出山川、大海等。
那么首先来介绍一下在 Cesium 中如何创建一个图层。
第一种方式可以直接在基本图层上添加一个图层,如注记等等。方式如下:
// 全球影像中文注记服务 | |
var my_layer = viewer.imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({ | |
url: "http://t0.tianditu.com/cia_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default.jpg", | |
layer: "tdtAnnoLayer", | |
style: "default", | |
format: "image/jpeg", | |
tileMatrixSetID: "GoogleMapsCompatible", | |
show: false | |
})); |
这段代码实现在影像的基础上叠加天地图注记层。当然也可以添加其他已知商业地图的图层或者自定义地图,但是需要做好 CORS,详细请参考 geotrellis 使用(三十五)Cesium 加载 geotrellis TMS 瓦片。
第二种方式大同小异,如下:
var my_layer = viewer.scene.imageryLayers.addImageryProvider( | |
new Cesium.UrlTemplateImageryProvider({ | |
url : 'http://my_url/{z}/{x}/{y}', | |
format: "image/png" | |
}) | |
); |
区别在于不是直接加载到 viewer 的 imageryLayers 而是 scene 的 imageryLayers,但是查看 Cesium 的源代码你会发现二者是一致的,viewer.imageryLayers 返回的正是 viewer.scene.imageryLayers。所以二者都可以通过下述方式设置透明度和亮度,防止压盖等。
//50% 透明度 | |
my_layer.alpha = 0.5; | |
// 两倍亮度 | |
my_layer.brightness = 2.0; |
这里就已经介绍了 Cesium 的两种图层对象:UrlTemplateImageryProvider、WebMapTileServiceImageryProvider,其还有 Cesium.createTileMapServiceImageryProvider、SingleTileImageryProvider 等等好几种,只是 url 的组织方式不同,可以根据需要自行查阅相关源码即可。
# 默认图层设置
上文已经说了可以设置 baseLayerPicker 为 false 或 true 来控制图层选择控件是否可见,当设置为 false 的时候可以在创建 viewer 时添加一项来设置默认显示的底图,否则仍然显示微软的默认影像。与添加图层的方式基本一致,如下:
imageryProvider : new Cesium.WebMapTileServiceImageryProvider({ | |
url: "http://t0.tianditu.com/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles", | |
layer: "tdtVecBasicLayer", | |
style: "default", | |
format: "image/jpeg", | |
tileMatrixSetID: "GoogleMapsCompatible", | |
show: false | |
}) |
当 baseLayerPicker 设置为 true 的时候,我们也可以修改里面的默认图层为我们想要的图层。只需要在创建 viewer 时再添加一项即可,如下:
imageryProviderViewModels: imageryLayers// 设置影像图列表 | |
terrainProviderViewModels: terrainLayers// 设置地形图列表 |
其中 imageryLayers 为影像(普通)图层数组,terrainLayers 为地形图层数组,有关地图图层在下面介绍。
# 地形
Cesium 中的地形系统是一种由流式瓦片数据生成地形 mesh 的技术,厉害指出在于其可以自动模拟出地面、海洋的三维效果。创建地形图层的方式如下:
var terrainProvider = new Cesium.CesiumTerrainProvider({ | |
url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles', // 默认立体地表 | |
// 请求照明 | |
requestVertexNormals: true, | |
// 请求水波纹效果 | |
requestWaterMask: true | |
}); | |
viewer.terrainProvider = terrainProvider; |
Cesium 支持两种类型的地形,STK World Terrain 和 Small Terrain。
# STK World Terrain
STK World Terrain 是高分辨率,基于 quantized mesh 的地形。这是一种基于网格的地形,可充分利用 GL 中的 Shader 来渲染,效果相当逼真。STK World Terrain 使用了多种数据源,分别适应不同地区和不同精度时的情形。比如对于美国本土使用 National Elevation Dataset (NED) 的高程,精度 3-30 米;对于欧洲使用 EU-DEM 高程,精度 30 米;对于澳洲使用 Australia SRTM-derived 1 Second DEM 高程,精度 30 米;对于 - 60 至 60 纬度段使用 CGIAR SRTM 高程,精度 90 米;对于整个地球使用 GTOPO30,精度 1000 米。STK World Terrain 地形是怎样生成的是不公开的,如需应用于封闭的局域网时,则需购买 AGI 的 STK terrain server。但是 AGI 提供了一个 webapi 可供因特网上调用,并提供了这种地形的格式细节。就是上面的 url
# Small Terrain
Small Terrain 是中等高分辨率基于 heightmap 的地形,渲染出的地形效果不如 quantized mesh 的地形,但也基本能接受。可以由 DEM 数据生成这种规范的.terrain 文件。生成工具见 https://groups.google.com/forum/#!topic/cesium-dev/rBieaEBJHi,需要 gdal 库和 numpy。
我有话说:由 DEM 数据生成这种规范的 .terrain 文件可以通过 CesiumLab 软件很方便的实现。详情可以看我的这篇文章 Cesium - 手拉手教你发布自己的离线三维地形图
# 坐标转换
Cesium 其实是一个封装好的 WebGL 库,当然这里面就牵扯到好几套坐标问题:屏幕坐标、三维空间坐标、投影坐标。而且坐标转换肯定是我们在开发任何地理信息系统中经常会碰到的问题,也比较复杂,简单总结了几种转换方式:
# 坐标系
new Cesium.Cartesian2(1,1) // 表示一个二维笛卡尔坐标系,也就是直角坐标系(屏幕坐标系) | |
new Cesium.Cartesian3(1,1,1) // 表示一个三维笛卡尔坐标系,也是直角坐标系 (就是真实世界的坐标系) |
# 二维屏幕坐标系到三维坐标系的转换
var pick1= scene.globe.pick(viewer.camera.getPickRay(pt1), scene) // 其中 pt1 为一个二维屏幕坐标 |
# 三维坐标到地理坐标的转换
var geoPt1= scene.globe.ellipsoid.cartesianToCartographic(pick1) // 其中 pick1 是一个 Cesium.Cartesian3 对象。 |
# 地理坐标到经纬度坐标的转换
var point1=[geoPt1.longitude / Math.PI * 180,geoPt1.latitude / Math.PI * 180]; // 其中 geoPt1 是一个地理坐标。 |
# 经纬度坐标转地理坐标(弧度)
var cartographic = Cesium.Cartographic.fromDegree(point) //point 是经纬度值 | |
var coord_wgs84 = Cesium.Cartographic.fromDegrees(lng, lat, alt);// 单位:度,度,米 |
# 经纬度坐标转世界坐标
var cartesian = Cesium.Cartesian3.fromDegree(point) |
# 计算两个三维坐标系之间的距离
var d = Cesium.Cartesian3.distance( | |
new Cesium.Cartesian3(pick1.x, pick1.y, pick1.z), | |
new Cesium.Cartesian3(pick3.x, pick3.y, pick3.z) | |
); //pick1、pick3 都是三维坐标系 |
# 加载 3D 对象(Entity)
通过 Cesium 可以很清楚的将一个三维模型加载到地球中。有两种方式可以实现此功能。
# 直接添加
var entity = viewer.entities.add({ | |
position : Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706), | |
model : { uri : '../Apps/SampleData/models/CesiumGround/Cesium_Ground.gltf' } | |
}); | |
viewer.trackedEntity = entity; // 镜头追踪,将镜头固定在对象上 |
清晰明了,不做过多介绍。
# 添加 primitives
// 这种方式会以最大最小值为缩放边界,采用 entity 的方式会完全根据地图进行缩放 | |
var scene = viewer.scene; | |
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(-123.0745725, 44.0503706)); | |
var model = scene.primitives.add(Cesium.Model.fromGltf({ | |
url: 'data/Cesium_Ground.gltf', | |
// 以下这些信息也均可在 entity 中设置 | |
color : Cesium.Color.fromAlpha(Cesium.Color.RED, parseFloat(0.5)),// 模型颜色,透明度 | |
silhouetteColor : Cesium.Color.fromAlpha(Cesium.Color.GREEN, parseFloat(0.5)),// 轮廓线 | |
colorBlendMode : Cesium.ColorBlendMode.MIX,// 模型样式 ['Highlight', 'Replace', 'Mix'] | |
modelMatrix: modelMatrix, | |
minimumPixelSize : 256, // 最小的缩放尺寸,256 个像素,就是一个瓦片的尺寸。 | |
maxiumScale: 2 // 最大的缩放倍数 | |
})); |
其中 modelMatrix 定义了对象的位置,第一种添加方式模型会自动按照 gltf 设置好的动画进行播放,第二种方式则需要添加下述代码设置动画。
// 添加动画 | |
Cesium.when(model.readyPromise).then(function (model) { | |
model.activeAnimations.addAll({ | |
loop: Cesium.ModelAnimationLoop.REPEAT,// 控制重复 | |
speedup: 0.5, // 速度,相对于 clock | |
reverse: true // 动画反转 | |
}) | |
}); |
# 加载 GeoJson、KML、CZML 数据
这几类数据归为一类都是矢量数据,所以这里要介绍的就是如何加载矢量数据,当然数据量特别大的时候就需要考虑矢量瓦片,Cesium 也正在开发矢量瓦片相关版本,之前看到一个折中方法是先读取矢量瓦片而后转换成 GeoJson 进行加载,这里不做过多介绍。那么这三类数据虽然都是矢量数据,但稍微有些不同,下面逐一介绍。
# GeoJson
GeoJson 是较为通用的一种网络矢量数据传输方案。其加载方式如下:
viewer.dataSources.add(Cesium.GeoJsonDataSource.load('mydata.geojson', { | |
stroke: Cesium.Color.BLUE.withAlpha(0.8), | |
strokeWidth: 2.3, | |
fill: Cesium.Color.RED.withAlpha(0.3), | |
clampToGround : true | |
} | |
)); |
Cesium.GeoJsonDataSource.load 函数即为加载 geojson 数据,并配置相关属性。通过这种方式就可将数据加载到三维地球中,并设置边线以及填充等,clampToGround 用于设置对象是否贴着地形,如为 true 则对象会随地势起伏而变化。当然我们可以为 geojson 中的各个要素设置不同的渲染方式,如下:
Cesium.Math.setRandomNumberSeed(0); | |
var promise = Cesium.GeoJsonDataSource.load('data/county3.geojson'); //load 完之后即为一个 promise 对象 | |
promise.then(function(dataSource) { // 此处类似于添加 3D 对象中的动画。 | |
viewer.dataSources.add(dataSource); // 先添加对象 | |
var entities = dataSource.entities.values; // 获取所有对象 | |
var colorHash = {}; | |
for (var i = 0; i < entities.length; i++) { // 逐一遍历循环 | |
var entity = entities[i]; | |
var name = entity.properties.GB1999; // 取出 GB1999 属性内容 | |
var color = colorHash[name]; // 如果 GB1999 属性相同,则赋同一个颜色。 | |
if (!color) { | |
color = Cesium.Color.fromRandom({ | |
alpha : 1.0 | |
}); | |
colorHash[name] = color; | |
} | |
entity.polygon.material = color; // 设置 polygon 对象的填充颜色 | |
entity.polygon.outline = false; //polygon 边线显示与否 | |
entity.polygon.extrudedHeight = entity.properties.POPU * 1000; // 根据 POPU 属性设置 polygon 的高度 | |
} | |
}); | |
viewer.zoomTo(promise); |
此种方式实现原理为先 load 数据,而后逐一设置 load 后数据的 entity。geojson 中的对象的属性可以通过 entity.properties.GB1999 的方式取出,其中 GB1999 表示属性名称。注意数据最好是 84 投影经纬度坐标,下同。
# KML
KML 是 Google Earth 定义的一种矢量数据组织方式,其加载方式与 GeoJson 基本相同,如下:
var promise = Cesium.KmlDataSource.load('data.kml'); |
剩下的处理方式与 GeoJson 相同。
# CZML
CZML 是 Cesium 中很重要的一个概念,也是一个亮点,CZML 使得 cesium 很酷很炫地展示动态数据成为可能。CZML 是一种 JSON 格式的字符串,用于描述与时间有关的动画场景,CZML 包含点、线、地标、模型、和其他的一些图形元素,并指明了这些元素如何随时间而变化。某种程度上说,Cesium 和 CZML 的关系就像 Google Earth 和 KML。
CZML 的一个典型结构如下
[ | |
// packet one | |
{ | |
"id": "GroundControlStation" | |
"position": { "cartographicDegrees": [-75.5, 40.0, 0.0] }, | |
"point": { | |
"color": { "rgba": [0, 0, 255, 255] }, | |
} | |
}, | |
// packet two | |
{ | |
"id": "PredatorUAV", | |
// ... | |
} | |
] |
CZML 可以记录对象与时间的关系,其时间序列相关属性如下:
{ | |
// ... | |
"someInterpolatableProperty": { | |
"cartesian": [ | |
"2012-04-30T12:00Z", 1.0, 2.0, 3.0, // 表示当时间为 2012-04-30T12:00Z,坐标为 (1,2,3) | |
"2012-04-30T12:01Z", 4.0, 5.0, 6.0, // 表示当时间为 2012-04-30T12:01Z,坐标为 (4,5,6) | |
"2012-04-30T12:02Z", 7.0, 8.0, 9.0 // 表示当时间为 2012-04-30T12:02Z,坐标为 (7,8,9) | |
] | |
} | |
} | |
{ | |
// ... | |
"someInterpolatableProperty": { | |
"epoch": "2012-04-30T12:00Z", // 表示时间起点为 2012-04-30T12:00:00 | |
"cartesian": [ | |
0.0, 1.0, 2.0, 3.0, // 从起点开始,第 0 秒时坐标为 (1,2,3) | |
60.0, 4.0, 5.0, 6.0, // 从起点开始,第 60 秒时坐标为 (4,5,6) | |
120.0, 7.0, 8.0, 9.0 // 从起点开始,第 120 秒时坐标为 (7,8,9) | |
] | |
} | |
} | |
{ | |
// ... | |
"someInterpolatableProperty": { | |
"epoch": "2012-04-30T12:00Z", | |
"cartesian": [ | |
0.0, 1.0, 2.0, 3.0, | |
60.0, 4.0, 5.0, 6.0, | |
120.0, 7.0, 8.0, 9.0 | |
], | |
"interpolationAlgorithm": "LAGRANGE", // 插值算法为 LAGRANGE,还有 HERMITE,GEODESIC | |
"interpolationDegree": 5 //1 为线性插值,2 为平方插值 | |
}, | |
} |
具体的可以查阅相关资料。将 CZML 数据载入场景的方式与前两者一致,加载完后处理方式也基本一致,如下:
dataSource = new Cesium.CzmlDataSource(); | |
var czml = 'data/Vehicle.czml'; | |
dataSource.load(czml); | |
viewer.dataSources.add(dataSource); |
# 加载 3D Tile
3D 瓦片可以显示建筑物、地标乃至森林广告牌等等以及其对应的属性信息。每个 3D 瓦片就是一个 3D 对象,具体的数据范围等等信息在 tileset.json 中定义。
# 加载
var tileSet = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ | |
// 同样 url 只定义编号前面的部分,具体的编号(数字或者非数字都有可能)在此 URL 下的 tileset.json 文件中定义,包括此 3d 瓦片图层的范围等等。 | |
url: 'https://beta.cesium.com/api/assets/1461?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiMTBjN2E3Mi03ZGZkLTRhYmItOWEzNC1iOTdjODEzMzM5MzgiLCJpZCI6NDQsImlhdCI6MTQ4NjQ4NDM0M30.B3C7Noey3ZPXcf7_FXBEYwirct23fsUecRnS12FltN8&v=1.0' | |
})); | |
tileSet.readyPromise.then(function (tileset) { | |
viewer.camera.viewBoundingSphere(tileset.boundingSphere, new Cesium.HeadingPitchRange(0, -0.5, 0)); | |
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); | |
}); |
# tileset.json 文件
不同分辨率显示不同 3D 瓦片,全靠此文件定义。
{ | |
"asset": { | |
"version": "1.0" | |
}, | |
"geometricError": 500, | |
"root": { | |
"transform": [ | |
96.86356343768793, | |
24.848542777253734, | |
0, | |
0, | |
-15.986465724980844, | |
62.317780594908875, | |
76.5566922962899, | |
0, | |
19.02322243409411, | |
-74.15554020821229, | |
64.3356267137516, | |
0, | |
1215107.7612304366, | |
-4736682.902037748, | |
4081926.095098698, | |
1 | |
], | |
"boundingVolume": { | |
"box": [ | |
0, | |
0, | |
0, | |
7.0955, | |
0, | |
0, | |
0, | |
3.1405, | |
0, | |
0, | |
0, | |
5.0375 | |
] | |
}, | |
"geometricError": 100, | |
"refine": "REPLACE", | |
"content": { | |
"url": "dragon_low.b3dm" | |
}, | |
"children": [ | |
{ | |
"boundingVolume": { | |
"box": [ | |
0, | |
0, | |
0, | |
7.0955, | |
0, | |
0, | |
0, | |
3.1405, | |
0, | |
0, | |
0, | |
5.0375 | |
] | |
}, | |
"geometricError": 10, | |
"content": { | |
"url": "dragon_medium.b3dm" | |
}, | |
"children": [ | |
{ | |
"boundingVolume": { | |
"box": [ | |
0, | |
0, | |
0, | |
7.0955, | |
0, | |
0, | |
0, | |
3.1405, | |
0, | |
0, | |
0, | |
5.0375 | |
] | |
}, | |
"geometricError": 0, | |
"content": { | |
"url": "dragon_high.b3dm" | |
} | |
} | |
] | |
} | |
] | |
} | |
} |
其中 boundingVolume.region 属性是包含六个元素的数组对象,用于定义边界地理区域,格式是 [west,
south, east, north, minimum height, maximum height]。经度和维度以弧度为单位,高度以米为单位(高于或低于 WGS84 椭球体)除了 region,也有其他边界体可以用,比如 box 和 sphere。其余各个字段包含信息可以查阅官方手册。
# 支持的格式
- b3dm: Batched 3D Model 用于展示城市建筑等大规模的 3D 对象
- l3dm: Instanced 3D Model 用于展示模型等。
- pnts: Point Cloud 用于展示大量的 3D 点。
- vctr: Vector Data 用于展示矢量元素,代替 KML(那么 CZML 呢?动画?)
- cmpt: Composite 用于合并异构 3D 瓦片,如将城市建筑的 b3dm 和树的 i3dm 合在一起展示。
# Style
可以根据对象的属性信息进行不同的可视化处理,包括颜色、显示与否等等。
var styleJson = { | |
color : { | |
conditions : [ | |
["${height} > 70.0", "rgb(0, 0, 255)"], | |
["${height} > 50.0", "rgb(0, 255, 0)"], | |
["${height} > 30.0", "rgb(0, 255, 255)"], | |
["${height} > 10.0", "color('purple', 1)"], | |
["${height} > 1.0", "color('gray', 0.5)"], | |
["true", "color('blue')"] // conditions | |
] | |
}, | |
show : '${height} > 0', | |
meta : { | |
description : '"Building id ${id} has height ${height}."' | |
} | |
}; | |
tileSet.style = new Cesium.Cesium3DTileStyle(styleJson); |
注意 conditions 中条件必须闭合,不能出现分类不完整,所以一般最后会加一个 true 项,相当于 default。
# 总结
本文简单介绍了 Cesium 三维数据可视化框架以及其简单的使用,比较笼统细节也并未深究可能也有错漏,感兴趣的可以自行查阅相关资料,随着学习的深入我也会增加些详细信息。总之,Cesium 是一款不错的 3D 地图数据可视化引擎,值得拥有。