本文共 17743 字,大约阅读时间需要 59 分钟。
原文地址:
概述我们很高兴欢迎你加入Cesium社区!为了让你能基于Cesium开发自己的3d 地图项目,这个教程将从头到尾讲解一个基础的Cesium程序的开发过程。这个教程将用到很多重要的CesiumAPI,但是并不是所有的(CesiumJS有很多很多功能)。我们目标是教会你基于Cesium做开发的基本原则和工具,在你的项目里能举一反三,解决其他问题。我们创建一个简单的程序去可视化纽约市的一些地理位置。我们将加载各种类型各种样式的二维和三维数据,并且创建若干个相机位置,并且展示一些用户交互的UI。最后,做为一个高科技地图,我们加载了一个无人机三维模型,充分利用3d可视化的优势去观察一些地理位置。在完成教程后,你对Cesium的功能会有几个基本概念,包括配置viewer、加载数据、创建各种样式的几何体、使用3d tiles(三维模型切片)、控制相机、增加鼠标交互事件。带交互的可视化纽约城地理位置
步骤再开发前的几个必备步骤:访问这个页面确认你的电脑环境适合Cesium Cesium Viewer. 如果没有看到地球? 点这个链接 Troubleshooting.
安装Node.js.下载教程代码 workshop code。使用git clone 或者手动下载zip并解压缩。在cmd命令行下,使用cd命令定位到 cesium-workshop目录下.运行 npm install。运行 npm start。控制台应该输出下面信息:
Source/ : 我们项目的代码。
ThirdParty/ : 外部js库,目前只包含cesium。
LICENSE.md : 我们项目的说明条款。
index.html : 主页,包含项目程序代码和页面结构。
server.js : 简单的基于nodejs的http服务器。
CesiumJS是完全兼容现代javascript 库和框架,所以放心大但的使用。
下面是一些示例:Cesium and webpack 教程展示了使用webpack集成cesium去更高效的开发web项目。
React集成CesiumJS和Threejs集成页面结构
下来我们看看index.html。为cesium的控件创建div,以及一些输入元素。我们注意到,Cesium的控件就是一个普通的div,它可以被css样式设置,并且和其他div交互。有一些关键的行:引入CesiumJS受限在html的标签内引用cesium.js。这个定义了Cesium对象,并且包含整个CesiumJS的库。使用你最擅长的文本编辑器(推荐sublime)打开 Source/App.js,并且清空里面内容。
把文件Source/AppSkeleton.js的内容拷贝到 Source/App.js。确认你的http服务还在 cesium-workshop 目录运行着。使用你的浏览器打开 localhost:8080.推荐使用chrome,但是现在浏览器都可以. 你应该能看到一个黑色背景。在代码里去掉注释,保存 Source/App.js,刷新浏览器,应该有些效果改变了。还有问题? 那你先跟着sandcastle去做一个没有UI的简单程序:
完整的代码
注释的代码下来我们真正开始。
创建ViewerCesium的最基础对象就是 Viewer, 一个具有很多功能的3d地球的黑盒子. 使用下面的代码创建viewer并附着到id为 "cesiumContainer"`的div上。HomeButton : 默认相机位置。
SceneModePicker : 3D、2D和哥伦布模式的切换按钮.
BaseLayerPicker : 选择地形、影像等图层。
NavigationHelpButton : 显示默认的相机控制提示.
Animation : 控制场景动画的播放速度.
CreditsDisplay : 展示数据版权属性。
Timeline : 时间滚动条。
FullscreenButton : 全屏切换。
可以传递一个options对象做为配置参数,去控制上面这些控件的显示或者不显示。对于示例代码,删除第一行,打开后面几行的注释,代码如下:
影像图层基本效果
影像图层调整颜色调整影像图层顺序影像的屏幕分割(卷帘效果)Cesium提供了多种影像数据来源 多用影像数据源 。
支持的格式:WMS
TMSWMTS (with time dynamic imagery)ArcGISBing MapsGoogle EarthMapboxOpen Street MapCesium默认使用Bing map的影像图层。这个影像图层经常用来做demo演示。为了使用这个影像,需要创建一个Cesium ion账户,并且生成一个访问token。
(译者注:考虑到国内的环境,修改了官方的示例,直接加载谷歌地图的影像)ArcticDEM : 高精度北极地形。
PAMAP Terrain : 高精度宾夕法尼亚州地形
地形配置 : 地形配置和格式
地形夸张 : 使地形起伏差异更大
支持的格式:
Quantized-mesh, Cesium团队定义的不规则地形三角网格式。
HeightmapGoogle Earth Enterprise为了增加一个地形数据,我们需要创建一个 CesiumTerrainProvider, 设置一个url以及很少的几个配置项,然后把这个provider设置到 viewer.terrainProvider.这里,我们使用 Cesium全球地形,这个数据存储在Cesium ion服务器上,已经默认到你的账户里的“My Assets”中。这种前提下,我们使用createWorldTerrain辅助函数去创建 Cesium全球地形 .
最终,我们有了地形效果,我们可能需要再写一行代码,确保地形以下的物体不可见。
场景配置
为了我们的viewer的展示时间和空间正确,需要一些更多的配置。这部分主要和 viewer.scene打交道, 这个类控制了我们的viewer中所有的图形元素。使用下面这句话,开启全球光照,光照方向依据太阳方向。Cartesian3 : 三维笛卡尔(直角)坐标 – 当用来表示位置的时候,这个坐标指在地固坐标系(Earth fixed-frame (ECEF))下,相对地球中心的坐标位置,单位是米。
Cartographic :使用经纬度(弧度)和高度(WGS84地球高程)描述的三维坐标 。
HeadingPitchRoll :
在ENU(East-North-Up)坐标系中,相对坐标轴的旋转(弧度)。Heading 相对负z轴(垂直向下). Pitch 相对负y轴. Roll相对正x轴.Quaternion :使用四维坐标描述的三维旋转。
这是在Cesium的scene中摆放对象的基本类型,Cesium提供了一系列的方便的转换函数。具体请查看cesium文档。现在,我们把相机定位到我们数据所在的位置--纽约。
相机控制Camera 是 viewer.scene的一个属性,用来控制当前可见范围。使用Cesium Camera API 我们可以直接设置相机的位置和朝向。一些最常用的方法:Camera.setView(options) : 立即设置相机位置和朝向。
Camera.zoomIn(amount) : 沿着相机方向移动相机。
Camera.zoomOut(amount) : 沿着相机方向远离
Camera.flyTo(options) : 创建从一个位置到另一个位置的相机飞行动画。
Camera.lookAt(target, offset) : 依据目标偏移来设置相机位置和朝向。
Camera.move(direction, amount) : 沿着direction方向移动相机。
Camera.rotate(axis, angle) : 绕着任意轴旋转相机。
更详细的可以去学习下面两个示例:
Camera API示例
自定义相机控制
我们测试一个方法,把相机位置放置到纽约。分别使用一个 Cartesian3表示位置,一个HeadingPitchRoll表示朝向。个HeadingPitchRoll表示朝向。// 创建相机初始位置和朝向
var initialPosition = new Cesium.Cartesian3.fromDegrees(-73.998114468289017509, 40.674512895646692812, 2631.082799425431);var initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees(7.1077496389876024807, -31.987223091598949054, 0.025883251314954971306);var homeCameraView = { destination : initialPosition,orientation : { heading : initialOrientation.heading,pitch : initialOrientation.pitch,roll : initialOrientation.roll}};// 设置视图viewer.scene.camera.setView(homeCameraView);使用一个js对象保存相机的参数,设置后,相机此时是垂直俯视曼哈顿(Manhattan)。事实上,我们可以使用这个view参数来更改home按钮的效果。与其设置地球的默认视图参数,我们还不如重写这个按钮,点击之后飞行到曼哈顿。可以通过其他参数来调节动画过程,并且可以设置一个事件监听取消默认的飞行过程,然后调用新的flyto()函数飞到我们设置的位置:Polygon
PolylineBillboardLabel一旦你已经理解了Entity是什么东西,使用Cesium加载数据就很容易理解了。为了读取数据文件,需要根据你的数据格式创建一个合适的 DataSource ,它将负责解析你配置的url里的数据,然后创建一个[EntityCollection]用来存储从数据里加载的每一个Entity 。DataSource 只是定义一些接口,依据数据格式的不同会有不同的解析过程。比如,KML使用KmlDataSource。如下面代码:
var kmlOptions = { camera : viewer.scene.camera,canvas : viewer.scene.canvas,clampToGround : true};// 从这个KML的url里加载POI点位 : var geocachePromise = Cesium.KmlDataSource.load('./Source/SampleData/sampleGeocacheLocations.kml', kmlOptions);这段代码使用 KmlDataSource.load(optinos) 来从KML文件中读取点位数据。 对于KmlDataSource,camera 和 canvas 选项必须要配置。clampToGround 选项控制数据是否贴地, 贴地效果是最常见的矢量数据可视化效果,保证数据紧贴地形起伏,而不是仅仅相对WGS84绝对球表面。
因为数据是异步加载的,所以这个函数实际返回一个 Promise , 最后使用KmlDataSource 存储我们新创建的Entity。Promise 是一种异步处理机制,这里的“异步”是指需要在.then函数里操作数据,而不是直接在 .load函数之后立即操作。为了能在scene中使用这些载入的entity,只有当这个promise的then回调中才可以把KmlDataSource添加到 viewer.datasources。entity.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM; entity.label = undefined; entity.billboard.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(10.0, 20000.0); // 计算经度和纬度(角度表示) var cartographicPosition = Cesium.Cartographic.fromCartesian(entity.position.getValue(Cesium.JulianDate.now())); var longitude = Cesium.Math.toDegrees(cartographicPosition.longitude); var latitude = Cesium.Math.toDegrees(cartographicPosition.latitude); // 修改描述信息 var description = '
' + "经度" + ' | ' + longitude.toFixed(5) + ' |
---|---|
' + "纬度" + ' | ' + latitude.toFixed(5) + ' |
最后效果:
var neighborhoods;
neighborhoodsPromise.then(function(dataSource) { viewer.dataSources.add(dataSource);});下来设置多边形数据的样式。和上面调整billboard样式一样,我们设置行政区域多边形也必须在数据完全载入后去做。最后,我们再创建一个基本的文字标注 Label。 为了保证显示效果清晰,我们设置了一个 disableDepthTestDistance 确保这个标注不会被其他对象盖住。
可是,Label需要通过entity.position属性设置位置。但是Polygon 是有一个positions列表组成的边界,我们使用这个positions列表的中心点来计算。// 获取多边形的positions列表 并计算它的中心点var polyPositions = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()).positions;var polyCenter = Cesium.BoundingSphere.fromPoints(polyPositions).center;polyCenter = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(polyCenter);entity.position = polyCenter;// 生成文字标注entity.label = { text : entity.name,showBackground : true,scale : 0.6,horizontalOrigin : Cesium.HorizontalOrigin.CENTER,verticalOrigin : Cesium.VerticalOrigin.BOTTOM,distanceDisplayCondition : new Cesium.DistanceDisplayCondition(10.0, 8000.0),disableDepthTestDistance : 100.0};最终效果:Property Types 示例
// 从CZML中载入无人机轨迹var dronePromise = Cesium.CzmlDsataSource.load('./Source/SampleData/SampleFlight.czml');dronePromise.then(function(dataSource) {
viewer.dataSources.add(dataSource);});这个CZML中使用 Path去展示无人机轨迹, 以及一个展示不同时刻位置的属性.。使用插值算法把一个路径的离散点链接为一个连续的折线。我们继续改进下无人机的显示样式。我们可以用一个三维模型去表示我们的无人机,并把它设置到entity上,而不是仅仅用一个简单的点。三维模型示例
三维模型带颜色示例Cesium支持加载glTF格式的三维模型格式。glTF是一个由Cesium团队和 Khronos group一起开发的开源三维模型格式,这种格式尽量减少传输和实时处理过程中的模型数据量。如果没有glTF模型,我们提供了一个 在线转换工具 把DAE,obj等格式转为glTF。
我们载入一个效果不错的,又带动画的无人机模型 Model :插值示例
为了飞行路径更平滑,可以如下修改配置 :3D Tiles 调试器 ,它是一个能够查看各种3d tile后台信息的调试工具。
这是一些不同类型的3d tile模型数据:
倾斜模型
BIM数据点云所有类型这个项目中,使用 Cesium3DTileset 类添加整个纽约的真实建筑物模型,改进了可视化效果的真实性。
// 加载纽约建筑物模型var city = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(3839) }));你会发现这些建筑物的高度好像不正确。这个可以简单修正下。通过一个 modelMatrix,我们可以调整这个数据的位置。把数据当前的包围球转为Cartographic,就能计算出模型现在相对于地面的偏移,然后增加这个偏移值,然后重设modelMatrix:// 调整3dtile模型的高度,让他刚好放在地表var heightOffset = -32;city.readyPromise.then(function(tileset) { // Position tilesetvar boundingSphere = tileset.boundingSphere;var cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);var surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0);var offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset);var translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);});现在我们有了110万个建筑物模型。3D Tiles 支持使用3D Tiles样式语言去对一部分数据进行样式配置。3D Tiles的样式依据一个表达式,根据Cesium3DTileFeature模型属性去修改某一部分甚至某一栋建筑物的颜色(RGB和透明度)。这些元素属性(feature property)通常存储在每个模型切片的batchtable中。元素属性可以是任意属性,比如高度,名称,坐标,创建日期等等。样式语言使用JSON格式定义,并且支持JavaScript的表达式(a small subset of JavaScript augmented)。另外,样式语言提供了一些内置的函数,支持数学计算。Cesium3DTileStyle示例如下:格式
倾斜模型样式配置
如果你有各种三维数据需要转换为3D tiles,请下载我们的CesiumLab。交互
最后,我们添加一些鼠标交互。我们改进下效果,当鼠标划过的时候,高亮图标。 为了做出这个效果,我们使用拾取技术(picking),它能够根据一个屏幕上的像素位置返回三维场景中的对象信息。有好几种拾取:Scene.pick : 返回窗口坐标对应的图元的第一个对象。
Scene.drillPick :返回窗口坐标对应的所有对象列表。
Globe.pick : 返回一条射线和地形的相交位置点。
这是一些示例:
拾取示例
3D Tiles 对象拾取
因为我们想实现鼠标滑过的高亮效果,首先需要创建一个鼠标事件处理器。 ScreenSpaceEventHandler是可以处理一系列的用户输入事件的处理器. ScreenSpaceEventHandler.setInputAction()`](/Cesium/Build/Documentation/ScreenSpaceEventHandler.html#setInputAction) 监听某类型的用户输入事件 -- [ScreenSpaceEventType
用户输入事件类型,做为一个参数传递过去。这里我们设置一个回调函数来接受鼠标移动事件:var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction(function(movement) {}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);下来我们写高亮函数。我们可以在回调函数里获得一个窗口坐标,并传递到pick()方法里。 如果拾取到一个billboard对象,我们就知道目前鼠标在一个图标上了。然后使用我们前面学过的相关Entity接口,去修改它的样式做高亮效果。// 当鼠标移到了我们关注的图标上,修改entity 的billboard 缩放和颜色handler.setInputAction(function(movement) { var pickedPrimitive = viewer.scene.pick(movement.endPosition);var pickedEntity = (Cesium.defined(pickedPrimitive)) ? pickedPrimitive.id : undefined;// Highlight the currently picked entityif (Cesium.defined(pickedEntity) && Cesium.defined(pickedEntity.billboard)) { pickedEntity.billboard.scale = 2.0;pickedEntity.billboard.color = Cesium.Color.ORANGERED;}}, Cesium.ScreenSpaceEventType.MOUSE_MOVE); 高亮样式设置成功了。可是,当鼠标不在图标上,这个高亮样式依然有效。为了解决这个问题,我们使用一个变量来存储上次的高亮图标,当鼠标不在它上面的时候,恢复它原来的样式。
这是包含高亮和不高亮完整功能的代码:var previousPickedEntity = undefined;handler.setInputAction(function(movement) { var pickedPrimitive = viewer.scene.pick(movement.endPosition);var pickedEntity = (Cesium.defined(pickedPrimitive)) ? pickedPrimitive.id : undefined;// 取消上一个高亮对象的高亮效果if (Cesium.defined(previousPickedEntity)) { previousPickedEntity.billboard.scale = 1.0;previousPickedEntity.billboard.color = Cesium.Color.WHITE;}// 当前entity高亮if (Cesium.defined(pickedEntity) && Cesium.defined(pickedEntity.billboard)) { pickedEntity.billboard.scale = 2.0;pickedEntity.billboard.color = Cesium.Color.ORANGERED;previousPickedEntity = pickedEntity;}}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);好了,我们添加了完整的图标entity的鼠标交互响应。自由模式 :默认的相机控制方式
无人机模式 : 以一个固定距离跟随无人机自由模式下不需要任何代码。无人机跟随模式下,我们使用viewer内置的跟随函数,确保相机一直居中观察无人机。这种模式下,即便对象是移动的,相机也能和目标之间保持一个固定的偏移距离。只需要简单的设置viewer.trackedEntity。切换到自由模式,只需要把viewer.trackedEntity 设置为undefined,然后可以使用camera.flyTo()返回到初始位置。这是相机模式代码:
官方文档 : 完整的CesiumAPI文档,包含一些示例代码
Sandcastle :一个所见即所得的编码环境,包含大量的代码示例.
官方教程 :详细描述基于Cesium开发的方方面面。
官方论坛 : Cesium相关问题的主要讨论平台.
Cesium实验室 : 中国最专业的Cesium的讨论学习QQ群:595512567。
一旦有问题,上面的资源可能有你的答案。在cesiumjs.org展示你的项目
我们很乐意去分享所有Cesium社区创建的酷炫项目。遍布世界的的开发者创建了很多有意思的我们从来没考虑过的项目。一旦你的项目准备分享给全世界,请跟我们联系放到[CesiumJS示例页面] ( 这个博客提交你的项目示例。转载于:https://blog.51cto.com/14117342/2327088