原文地址:https://cesiumjs.org/tutorials/Cesium-Workshop/javascript
咱们很高兴欢迎你加入Cesium社区!为了让你能基于Cesium开发本身的3d 地图项目,这个教程将从头至尾讲解一个基础的Cesium程序的开发过程。这个教程将用到不少重要的CesiumAPI,可是并非全部的(CesiumJS有不少不少功能)。咱们目标是教会你基于Cesium作开发的基本原则和工具,在你的项目里能触类旁通,解决其余问题。css
咱们建立一个简单的程序去可视化纽约市的一些地理位置。咱们将加载各类类型各类样式的二维和三维数据,而且建立若干个相机位置,而且展现一些用户交互的UI。最后,作为一个高科技地图,咱们加载了一个无人机三维模型,充分利用3d可视化的优点去观察一些地理位置。html
在完成教程后,你对Cesium的功能会有几个基本概念,包括配置viewer、加载数据、建立各类样式的几何体、使用3d tiles(三维模型切片)、控制相机、增长鼠标交互事件。java
再开发前的几个必备步骤:node
cesium-workshop
目录下.npm install
。npm start
。控制台应该输出下面信息:react
Cesium development server running locally. Connect to http://localhost:8080
注意不能关闭控制台窗口,开发中须要保证这个进程运行着。webpack
下一步, 在浏览器里打开 localhost:8080
。你应该能看到咱们的程序已经运行了。git
这个教程里提到的workshop是基于cesium1.45开发的,里面的地形服务器已经失效了,致使cesium加载并不成功,使用这个代码看不到效果。github
解决方法也很简单,咱们使用Cesium最新版1.51里的文件替换到以下目录web
再次刷新页面,就能够了,效果以下:
在程序根目录下,有以下文件和文件夹. 这个程序已经被设计为尽量的简单,只包含cesiumjs的库。
Source/
: 咱们项目的代码。ThirdParty/
: 外部js库,目前只包含cesium。LICENSE.md
: 咱们项目的说明条款。index.html
: 主页,包含项目程序代码和页面结构。server.js
: 简单的基于nodejs的http服务器。CesiumJS是彻底兼容现代javascript 库和框架,因此放心大但的使用。
下面是一些示例:
下来咱们看看index.html。为cesium的控件建立div,以及一些输入元素。咱们注意到,Cesium的控件就是一个普通的div,它能够被css样式设置,而且和其余div交互。
有一些关键的行:
受限在html的标签内引用cesium.js。这个定义了Cesium对象,而且包含整个CesiumJS的库。
<script src="ThirdParty/Cesium/Cesium.js"></script>
为了减少开发的项目最终的js文件大小,固然你也能够包含ThirdParty/Cesium/Source/目录下的独立的Cesium源码模块。不过咱们为了简单的测试API,咱们直接包含了整个CesiumJS库。
在HTML的body部分,有一个div为了建立Cesium控件。
<div id="cesiumContainer"></div>
为了在div建立成功后再执行其余代码,能够再HTML的body部分增长script标签去引用js文件。
<script src="Source/App.js"></script>
使用index.css文件定义了HTML元素的样式,能够在HTML的head元素里引用它。
<link rel="stylesheet" href="index.css" media="screen">
Cesium的全部小控件下面这个CSS来定义样式。须要在index.css以前引用。
@import url(ThirdParty/Cesium/Widgets/widgets.css);
咱们的页面已经有了基本样式,而且咱们在index.css设定的样式能够覆盖Cesium默认的控件样式。
步骤以下:
Source/App.js
,而且清空里面内容。Source/AppSkeleton.js
的内容拷贝到 Source/App.js
。cesium-workshop
目录运行着。Source/App.js
,刷新浏览器,应该有些效果改变了。还有问题? 那你先跟着sandcastle去作一个没有UI的简单程序:
下来咱们真正开始。
Cesium的最基础对象就是 Viewer
, 一个具备不少功能的3d地球的黑盒子. 使用下面的代码建立viewer并附着到id为 "cesiumContainer"`的div上。
var viewer = new Cesium.Viewer('cesiumContainer');
这简单的一行代码实际包含了不少内容,成功后你应该能看见基础的地球,像下面同样:
默认状况下这个场景能处理鼠标和触摸事件。 试下下面的相机控制方法:
左键单击和拖拽
- 沿着地球表面平移(调整相机位置).
右键单击和拖拽
- 相机放大缩小(调整相机距离).
滚轮
- 相机放大缩小(调整相机距离).
中间按下和拖拽
- 围绕地球表面旋转相机(调整相机方向)。
除了地球, Viewer还默认包含了一些有用的控件:
Geocoder
: 地理位置查询定位控件,默认使用bing地图服务.HomeButton
: 默认相机位置。SceneModePicker
: 3D、2D和哥伦布模式的切换按钮.BaseLayerPicker
: 选择地形、影像等图层。NavigationHelpButton
: 显示默认的相机控制提示.Animation
: 控制场景动画的播放速度.CreditsDisplay
: 展现数据版权属性。Timeline
: 时间滚动条。FullscreenButton
: 全屏切换。能够传递一个options对象作为配置参数,去控制上面这些控件的显示或者不显示。对于示例代码,删除第一行,打开后面几行的注释,代码以下:
var viewer = new Cesium.Viewer('cesiumContainer', { scene3DOnly: true, selectionIndicator: false, baseLayerPicker: false });
这几行代码建立了一个不包含选择指示器(selection indicators),基础底图选择控件的viewer。完整的options配置看文档Viewer
。
影像是Cesium程序一个关键元素。它是覆盖在地表的各类不一样精度的图像集合。根据相机的朝向和距离,Cesium将请求和渲染不一样LOD或者缩放级别下的图像。
Cesium支持多个影像图层同时加载、删除、排序和调整。
Cesium为影像图层提供了大量方法,相似调整颜色、混合等。下面是Sandcastle中的一些示例代码:
Cesium提供了多种影像数据来源 多用影像数据源 。
支持的格式:
Cesium默认使用Bing map的影像图层。这个影像图层常常用来作demo演示。为了使用这个影像,须要建立一个Cesium ion帐户,而且生成一个访问token。
(译者注:考虑到国内的环境,修改了官方的示例,直接加载谷歌地图的影像)
// 删除默认的影像图层 viewer.imageryLayers.remove(viewer.imageryLayers.get(0)); // 增长谷歌影像底图 viewer.imageryLayers.addImageryProvider(new Cesium.UrlTemplateImageryProvider({ url: 'http://www.google.cn/maps/vt?lyrs=s&x={x}&y={y}&z={z}', tilingScheme: new Cesium.WebMercatorTilingScheme() }) );
运行后有以下效果:
后续教程还有一篇专门讲影像图层的 影像图层教程.
Cesium支持渐进流式加载和渲染全球高精度地形,而且包含海、湖、河等水面效果。相对2D地图,山峰、山谷等其余地形特征的更适宜在这种3D地球中展现。和影像图层同样,Cesium须要在服务端预先把地形数据处理为切片形式,在客户端基于当前相机位置去请求和渲染地形切片。
下面是一些示例和地形数据集以及配置选项:
支持的格式:
CesiumTerrainProvider
, 设置一个url以及不多的几个配置项,而后把这个provider设置到 viewer.terrainProvider
.这里,咱们使用 Cesium全球地形,这个数据存储在Cesium ion服务器上,已经默认到你的帐户里的“My Assets”中。这种前提下,咱们使用createWorldTerrain
辅助函数去建立 Cesium全球地形 .
// Load Cesium World Terrain viewer.terrainProvider = Cesium.createWorldTerrain({ requestWaterMask : true, // required for water effects requestVertexNormals : true // required for terrain lighting });
requestWaterMask
和 requestVertexNormals
两个选项都是可选的,他们告知Cesium去请求额外的水面数据和光照数据。 默认都为false.
最终,咱们有了地形效果,咱们可能须要再写一行代码,确保地形如下的物体不可见。
// 打开深度检测,那么在地形如下的对象不可见 viewer.scene.globe.depthTestAgainstTerrain = true;
纽约的地表很是平,能够漫游到其余地方去浏览. 为了明显看到效果,能够到珠峰附近去查看。
后续有一个地形的详细教程 地形教程.
为了咱们的viewer的展现时间和空间正确,须要一些更多的配置。这部分主要和 viewer.scene
打交道, 这个类控制了咱们的viewer中全部的图形元素。
使用下面这句话,开启全球光照,光照方向依据太阳方向。
// 开启全球光照 viewer.scene.globe.enableLighting = true;
随着时间的变化,光照方向也在变换。若是缩小后,咱们能看到一部分的地球是黑色的,由于这部分此时晚上。
在初始化视图以前,先学下基本的cesium 类型:
Cartesian3
: 三维笛卡尔(直角)坐标 – 当用来表示位置的时候,这个坐标指在地固坐标系(Earth fixed-frame (ECEF))下,相对地球中心的坐标位置,单位是米。Cartographic
:使用经纬度(弧度)和高度(WGS84地球高程)描述的三维坐标 。HeadingPitchRoll
:Quaternion
:使用四维坐标描述的三维旋转。如今,咱们把相机定位到咱们数据所在的位置--纽约。
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)
: 绕着任意轴旋转相机。更详细的能够去学习下面两个示例:
Cartesian3
表示位置,一个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()函数飞到咱们设置的位置:
// 增长相机飞行动画参数 homeCameraView.duration = 2.0; homeCameraView.maximumHeight = 2000; homeCameraView.pitchAdjustHeight = 2000; homeCameraView.endTransform = Cesium.Matrix4.IDENTITY; // Override the default home button viewer.homeButton.viewModel.command.beforeExecute.addEventListener(function (e) { e.cancel = true; viewer.scene.camera.flyTo(homeCameraView); });
参看这篇教程学习更多相机操做方法 camera教程.
下来,咱们经过配置viewer的 时钟(Clock)
和时间线(Timeline)
去控制场景中的时间流逝。
时钟(clock)API教程.
Cesium使用 JulianDate
描述某个时刻,这个时间存储了自从公元前4712年1月1日中午的天数。为了提升精度,这个类里分开存储了时刻的日期部分和时刻的秒部分。为了数学运算的安全和闰秒(leap seconds)的问题,这个时刻是按照国际原子时标准(International Atomic Time standard)存储的。
下面是一些关于scene中时间的配置选项:
// 设置时钟和时间线 viewer.clock.shouldAnimate = true; // 当viewer开启后,启动动画 viewer.clock.startTime = Cesium.JulianDate.fromIso8601("2017-07-11T16:00:00Z"); viewer.clock.stopTime = Cesium.JulianDate.fromIso8601("2017-07-11T16:20:00Z"); viewer.clock.currentTime = Cesium.JulianDate.fromIso8601("2017-07-11T16:00:00Z"); viewer.clock.multiplier = 2; // 设置加速倍率 viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER; // tick computation mode(还没理解具体含义) viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 循环播放 viewer.timeline.zoomTo(viewer.clock.startTime, viewer.clock.stopTime); // 设置时间的可见范围
上述代码设定了场景动画播放速率,开始和结束时间,而且设置为循环播放。而且设置了时间线控件在合适的时间范围。使用这个 示例 去试验更多时间设置
初始化配置完成了,当你运行代码,能看到以下效果
上面咱们程序里已经添加了viewer 、影像图层、地形图层。下来重点说项目里的示例点位数据(the sample geocache data)。
为了更方便的可视化,Cesium支持流行的矢量格式GeoJson和KML,同时也支持咱们团队定义的一种格式 CZML.
不管最初是什么格式,全部的空间矢量数据在Cesium里都是使用Entity 相关API去展现的。Entity API 使用了灵活高效的可视化渲染方式。 Entity
是一种对几何图形作空间和时间展现的数据对象。sandcastle 里提供了不少简单的entity。
为了能快速的学习Entity API,建议先花点时间去读下 空间数据可视化教程 。
下面一些使用Entity API的示例:
一旦你已经理解了Entity
是什么东西,使用Cesium加载数据就很容易理解了。为了读取数据文件,须要根据你的数据格式建立一个合适的 DataSource
,它将负责解析你配置的url里的数据,而后建立一个[EntityCollection
]用来存储从数据里加载的每个Entity
。DataSource 只是定义一些接口,依据数据格式的不一样会有不一样的解析过程。好比,KML使用KmlDataSource
。以下面代码:
var kmlOptions = { camera : viewer.scene.camera, canvas : viewer.scene.canvas, clampToGround : true }; // 从这个KML的url里加载POI点位 : http://catalog.opendata.city/dataset/pediacities-nyc-neighborhoods/resource/91778048-3c58-449c-a3f9-365ed203e914 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
。
geocachePromise.then(function(dataSource) { // 把全部entities添加到viewer中显示 viewer.dataSources.add(dataSource); });
这些新加入到场景的entity默认有不少功能。单击它们会在 Infobox
显示属性, 双击它相机转换为居中观察模式(look at). 使用HOME按钮或者infobox旁边的相机按钮能够中止这种模式。下来咱们来自定义样式。
KML和CZML格式,在文件内有明确的样式定义。为了学习,咱们手动去建立样式。数据载入以后,咱们依据这个 示例 遍历全部entity修改或者增长属性。咱们的POI点默认都是使用 Billboards
和 Labels
显示, 根据下面的代码来修改某些entity的显示样式:
geocachePromise.then(function(dataSource) { // 把全部entities添加到viewer中显示 viewer.dataSources.add(dataSource); // 得到entity列表 var geocacheEntities = dataSource.entities.values; for (var i = 0; i < geocacheEntities.length; i++) { var entity = geocacheEntities[i]; if (Cesium.defined(entity.billboard)) { // 对这个entity设置样式 } } });
经过调整锚点(anchor point)来改进显示效果,而且为了不杂乱删除了文字标注(labels),最后设置了 displayDistanceCondition
控制只显示和相机必定距离内的点.
if (Cesium.defined(entity.billboard)) { // 调整垂直方向的原点,保证图标里的针尖对着地表位置 entity.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM; // 去掉文字的显示 entity.label = undefined; // 设置可见距离 entity.billboard.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(10.0, 20000.0); }
关于distanceDisplayCondition
,能够学习下 sandcastle 示例.
下来,咱们改进下 Infobox
。Infobox的标题栏显示的是entity的name属性, 它的内容显示的是description属性(使用HTML文本显示)。
你发现咱们这个数据默认的description属性没什么意义,咱们把这个属性更改成显示每一个点的经纬度。
首先咱们把entity的position属性转换为Cartographic,而后把经度和纬度构造一个HTML的table并赋值到description属性里。 如今单击咱们的点在 Infobox
会显示一个格式规整的信息。
if (Cesium.defined(entity.billboard)) { 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 = '<table class="cesium-infoBox-defaultTable cesium-infoBox-defaultTable-lighter"><tbody>' + '<tr><th>' + "经度" + '</th><td>' + longitude.toFixed(5) + '</td></tr>' + '<tr><th>' + "纬度" + '</th><td>' + latitude.toFixed(5) + '</td></tr>' + '</tbody></table>'; entity.description = description; }
最后效果:
或许把每一个POI点所在的行政区展现出来很是有用。咱们试着经过一个GeoJson文件来建立NYC的全部行政区域多边形。加载GeoJson和上面加载KML基本没什么区别,只是使用 GeoJsonDataSource
。和前面同样,也必须在promise的then函数里把数据添加到viewer.datasources
中,数据才能显示。
var geojsonOptions = { clampToGround : true }; // 从geojson文件加载行政区多边形边界数据 var neighborhoodsPromise = Cesium.GeoJsonDataSource.load('./Source/SampleData/neighborhoods.geojson', geojsonOptions); var neighborhoods; neighborhoodsPromise.then(function(dataSource) { viewer.dataSources.add(dataSource); });
下来设置多边形数据的样式。和上面调整billboard样式同样,咱们设置行政区域多边形也必须在数据彻底载入后去作。
var neighborhoods; neighborhoodsPromise.then(function(dataSource) { viewer.dataSources.add(dataSource); neighborhoods = dataSource.entities; // 获取enity列表遍历 var neighborhoodEntities = dataSource.entities.values; for (var i = 0; i < neighborhoodEntities.length; i++) { var entity = neighborhoodEntities[i]; if (Cesium.defined(entity.polygon)) { // 设置样式代码 } } });
首先,咱们从新设置每一个entity的name属性和行政区的名称相同。原始的GeoJson文件有一个neighborhood的属性。Cesium使用entity.properties
来存储GeoJson的属性。因此咱们这么设置:
// 设置样式代码 // 把properties里的neighborhood设置到name entity.name = entity.properties.neighborhood;
为了不全部多边形颜色都相同,可使用一个随机颜色 Color
去设置每一个多边形的 ColorMaterialProperty
属性。
// 设置一个随机半透明颜色 entity.polygon.material = Cesium.Color.fromRandom({ red : 0.1, maximumGreen : 0.5, minimumBlue : 0.5, alpha : 0.6 }); // 设置这个属性让多边形贴地,ClassificationType.CESIUM_3D_TILE 是贴模型,ClassificationType.BOTH是贴模型和贴地 entity.polygon.classificationType = Cesium.ClassificationType.TERRAIN;
最后,咱们再建立一个基本的文字标注 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 };
最终效果:
最后,增长一个无人机飞跃城市上空的高科技效果。
由于飞行路径只是一系列带着时间属性的位置点,咱们经过CZML 文件来加载。CZML是一种在Cesium里描述时序图形场景的文件格式。它包含折线(lines)、点(points)、图标(billboards)、模型(models)和其余图形元素,以及他们随时间变化的属性。如同Google Earth的KML,CZML经过一种描述性语言(基于json格式)来存储Cesium大部分的功能。
咱们得CZML文件定义一个包含不一样时刻得一个位置列表Entity(默认显示为一个point)。在Entity API中有一些处理时间序列数据的属性类型。参考下面的示例:
// 从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
:
var drone; dronePromise.then(function(dataSource) { viewer.dataSources.add(dataSource); // 使用id获取在CZML 数据中定义的无人机entity drone = dataSource.entities.getById('Aircraft/Aircraft1'); // 附加一些三维模型 drone.model = { uri : './Source/SampleData/Models/CesiumDrone.gltf', minimumPixelSize : 128, maximumScale : 1000, silhouetteColor : Cesium.Color.WHITE, silhouetteSize : 2 }; });
如今咱们的模型看起来还不错,不像最初那个简单的点效果,这个无人机模型有方向,可是效果有点奇怪,并无朝向无人机的前进方向。幸亏,Cesium提供了VelocityOrientationProperty
,这个会根据entity的位置点信息和时间来自动计算朝向。
// 基于无人机轨迹的位置点,自动计算朝向 drone.orientation = new Cesium.VelocityOrientationProperty(drone.position);
如今咱们的无人机模型朝向正确了。咱们还能够改进下无人机飞行效果。Cesium依据离散点,使用线形插值构造了一条折线,虽然远处看不明显,可是这些折线段让路径看着不天然。有一些插值配置选项:
// 光滑的路径插值 drone.position.setInterpolationOptions({ interpolationDegree : 3, interpolationAlgorithm : Cesium.HermitePolynomialApproximation });
咱们的团队有时候描述Cesium像一个使用真实世界数据的三维游戏引擎。但是,加载真实世界的数据要比游戏引擎的数据困难不少,主要由于真实数据有很是高得分辨率,并且要求精确得可视化。幸亏,Cesium和开源社区合做开发了3D Tiles格式。它是一个流式载入海量各类类型得空间三维数据的 开放协议 。
使用一种相似Cesium的地形和影像数据切片技术,3d tiles格式使原先那些不可能作可视化交互的大模型数据可以展现出来,包括建筑物数据、CAD(或者BIM)模型,点云,倾斜模型。
这是一些不一样类型的3d tile模型数据:
这个项目中,使用 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 tileset var 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
示例以下:
var defaultStyle = new Cesium.Cesium3DTileStyle({ color : "color('white')", show : true });
这个样式只是简单的让纽约的全部建筑均可见。把它设置到 city.style
就能够看到可视化效果。
city.style = defaultStyle;
下面这个样式让模型半透明:
var transparentStyle = new Cesium.Cesium3DTileStyle({ color : "color('white', 0.3)", show : true });
全部元素使用相一样式只是小儿科。咱们可使用属性对每一个元素设置不一样样式。下面是一个依据建筑高度去着色的示例:
var heightStyle = new Cesium.Cesium3DTileStyle({
color : {
conditions : [
["${height} >= 300", "rgba(45, 0, 75, 0.5)"], ["${height} >= 200", "rgb(102, 71, 151)"], ["${height} >= 100", "rgb(170, 162, 204)"], ["${height} >= 50", "rgb(224, 226, 238)"], ["${height} >= 25", "rgb(252, 230, 200)"], ["${height} >= 10", "rgb(248, 176, 87)"], ["${height} >= 5", "rgb(198, 106, 11)"], ["true", "rgb(127, 59, 8)"] ] } });
为了在这些样式之间切换,咱们增长一点点代码去监听HTML的输入框变化:
var tileStyle = document.getElementById('tileStyle'); function set3DTileStyle() { var selectedStyle = tileStyle.options[tileStyle.selectedIndex].value; if (selectedStyle === 'none') { city.style = defaultStyle; } else if (selectedStyle === 'height') { city.style = heightStyle; } else if (selectedStyle === 'transparent') { city.style = transparentStyle; } } tileStyle.addEventListener('change', set3DTileStyle);
若是想学习更多关于3D Tiles如何配置样式,请查看这个 示例。
一些其余3D Tiles的示例:
最后,咱们添加一些鼠标交互。咱们改进下效果,当鼠标划过的时候,高亮图标。 为了作出这个效果,咱们使用拾取技术(picking),它可以根据一个屏幕上的像素位置返回三维场景中的对象信息。
有好几种拾取:
Scene.pick
: 返回窗口坐标对应的图元的第一个对象。Scene.drillPick
:返回窗口坐标对应的全部对象列表。Globe.pick
: 返回一条射线和地形的相交位置点。这是一些示例:
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 entity if (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.trackedEntity
。viewer.trackedEntity
设置为undefined,而后可使用camera.flyTo()
返回到初始位置。这是相机模式代码:
function setViewMode() { if (droneModeElement.checked) { viewer.trackedEntity = drone; } else { viewer.trackedEntity = undefined; viewer.scene.camera.flyTo(homeCameraView); } }
只须要把这个函数绑定到HTML元素的change
事件上。
var freeModeElement = document.getElementById('freeMode'); var droneModeElement = document.getElementById('droneMode'); function setViewMode() { if (droneModeElement.checked) { viewer.trackedEntity = drone; } else { viewer.trackedEntity = undefined; viewer.scene.camera.flyTo(homeCameraView); } } freeModeElement.addEventListener('change', setCameraMode); droneModeElement.addEventListener('change', setCameraMode);
当咱们双击entity的时候,就会自动进行跟随模式。若是用户经过点击跟踪无人机,添加一些处理去自动更新UI界面:
viewer.trackedEntityChanged.addEventListener(function() { if (viewer.trackedEntity === drone) { freeModeElement.checked = false; droneModeElement.checked = true; } });
咱们能够经过界面自由切换相机模式了:
剩下的代码咱们增长一些其余的可视化效果。如同前面提到的HTML元素交互方式,咱们能够添加阴影的切换界面,以及行政区多边形的可见性控制。
首先,简单的控制下行政区划的可见性。一般,经过设置Entity.show
属性来隐藏entity。但是,这个仅仅设置一个entity,咱们但愿一次性控制全部行政区划面的可见性。
能够像这个示例同样,把全部行政区entity放在一个父entity中,或者经过设置EntityCollection
的 show
属性来控制。只须要设置一次neighborhoods.show
属性便可控制全部entity的可见性。
var neighborhoodsElement = document.getElementById('neighborhoods'); neighborhoodsElement.addEventListener('change', function (e) { neighborhoods.show = e.target.checked; });
如同切换阴影同样:
var shadowsElement = document.getElementById('shadows'); shadowsElement.addEventListener('change', function (e) { viewer.shadows = e.target.checked; });
由于3D Tiles数据可能不是瞬间载入,能够添加一个载入指示器,当全部切片都载入后隐藏。
// 当城市数据初始化完成后,移除加载指示器 var loadingIndicator = document.getElementById('loadingIndicator'); loadingIndicator.style.display = 'block'; city.readyPromise.then(function () { loadingIndicator.style.display = 'none'; });
恭喜!你已经成功完成了CesiumJS项目。在Cesium的培训过程当中,请随意使用咱们提供的代码去测试和开发。咱们很高兴欢迎你加入Cesium社区,而且指望看到你基于CesiumJS开发的酷炫程序。
为了你的Cesium开发事业,咱们鼓励你访问下面的资源:
咱们很乐意去分享全部Cesium社区建立的酷炫项目。遍及世界的的开发者建立了不少有意思的咱们历来没考虑过的项目。一旦你的项目准备分享给全世界,请跟咱们联系放到[CesiumJS示例页面] (https://cesiumjs.org/demos)。具体请阅读 这个博客提交你的项目示例。