目录
(8)加载和设计实体(Loading and Styling Entities)
(9)3D瓦片(3D Tiles)
(10)交互(Interactivity)
(11)相机模式(Camera Modes)
(12)附加内容(Extras)
(13)下一步工作(Next Steps)
开发资源(Development Resources)
Showcase your work on cesiumjs.org(展示你的作品)
Discover and process content on Cesium ion
既然我们已经使用视图配置、影像和地形,这已经为应用程序做好了准备,那么我们可以添加应用程序的主焦点--样例geocache数据(藏宝点数据)。
为了简单可视化,Cesium支持流行的矢量格式数据GeoJson和KML,还有我们特地为描述Cesium中场景所开发的开源数据格式CZML。
无论初始化格式如何,Cesium中所有空间数据都使用实体API表示(the Entity API)。实体API提供了一种灵活可视化格式,这种格式对于Cesium的展现非常有效。一个Cesium实体(Entity)是一种数据对象,它可以与样式化的图形表示形式配对,并定位于空间和时间上,沙堡长廊提供了许多简单实体的例子(many examples of simple entities)。为了快速进入实体API的基础,从这些应用程序中抽出时间来阅读可视化空间数据的(Visualizing Spatial Data)教程。
下面是一些不同实体类型的示例:
一旦你掌握了实体的样子,那么使用Cesium加载数据集就变得很好理解。为了读入一个数据文件,需要创建一个适合数据格式的数据源(DataSource),其将解析托管在指定url中的数据文件,并创建一个实体收集器(EntityCollection),其包含数据集中每个地理空间对象的实体。数据源仅仅定义了数据接口-你需要的确切数据源类型取决于数据格式。例如,KML使用KmlDataSource,它是这样的:
var kmlOptions = {
camera : viewer.scene.camera,
canvas : viewer.scene.canvas,
clampToGround : true
};
// Load geocache points of interest from a KML file
// Data from : 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(options)来从一个KML文件中读取我们样例的藏宝点数据。对于一个KmlDataSource,相机和画布选项是必须的,clampToGround选项支持地面贴近(ground clampling),这是一种流行的显示选项,它使地面几何实体(如多边形和椭圆)符合地形,而不是曲线符合WGS84椭球面。
由于该数据是异步加载的,因此它向KmlDataSource返回一个承诺(Promise),KmlDataSource将保存我们创建的所有实体。
如果对异步函数的Promise API不熟悉的话,这里的异步主要意思是,应该对提供给.then回调函数中的数据做需要做的事情。实际上为了给场景增加这样的实体收集器,我们必须要等promise解决,然后向viewer.datasources增加KmlDataSource。 取消注释下面代码:
// Add geocache billboard entities to scene and style them
geocachePromise.then(function(dataSource) {
// Add the new data as entities to the viewer
viewer.dataSources.add(dataSource);
});
默认情况下,新创建的实体具备可用的功能。点击将显示与实体相关的元数据信息框(Infobox),双击放大并查看实体。要停止查看实体的话,可以点击home按钮,或者点击信息框的关闭相机图标。接下来,我们将添加定制样式来改进应用程序的外观。
对于KML和CZML文件,声明样式已经创建到了文件中。然而,对于这个应用来说,我们将练习手工设置实体的样式。要这样做的话,通过等待数据源加载完成,采取一种相似的方法来实现样式例子(this styling example),然后遍历数据源中的所有实体,修改和增加属性。默认情况下,我们藏宝点标记是作为广告牌(Billboards)和标签(Labels)创建的,因此为了修改这些实体的外观,我们这样做:
// Add geocache billboard entities to scene and style them
geocachePromise.then(function(dataSource) {
// Add the new data as entities to the viewer
viewer.dataSources.add(dataSource);
// Get the array of entities
var geocacheEntities = dataSource.entities.values;
for (var i = 0; i < geocacheEntities.length; i++) {
var entity = geocacheEntities[i];
if (Cesium.defined(entity.billboard)) {
// Entity styling code here
}
}
});
我们可以通过调整标记的锚点、移除标签以减少混乱及设置displayDistanceCondition来改善标记的外观,这样只有在相机设置的距离范围内的点才能可见。
// Add geocache billboard entities to scene and style them
if (Cesium.defined(entity.billboard)) {
// Adjust the vertical origin so pins sit on terrain
entity.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
// Disable the labels to reduce clutter
entity.label = undefined;
// Add distance display condition
entity.billboard.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(10.0, 20000.0);
}
更多distanceDisplayCondition的帮助,可以查看沙堡例子(sandcastle example)。
接下来,让我们改进每个藏宝点实体的Infobox。信息盒子的标题是实体的名字,内容是实体的表述,显示为HTML。
你可能注意到了,默认的描述不是很有用。由于我们正在显示藏宝点的位置,所以更新用来显示点的经纬度信息。
首先,将实体的位置转化为地图,然后从地图中读取经纬度,再增加到HTML表的描述中。
当点击的时候,藏宝点实体将显示一个格式好的信息框(Infobox),其中包含我们需要的数据。
// Add geocache billboard entities to scene and style them
if (Cesium.defined(entity.billboard)) {
// Adjust the vertical origin so pins sit on terrain
entity.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
// Disable the labels to reduce clutter
entity.label = undefined;
// Add distance display condition
entity.billboard.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(10.0, 20000.0);
// Compute longitude and latitude in degrees
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);
// Modify description
// Modify description
var description = '' +
'' + "Longitude" + ' ' + longitude.toFixed(5) + ' ' +
'' + "Latitude" + ' ' + latitude.toFixed(5) + ' ' +
'
';
entity.description = description;
}
我们的藏宝点标记现在看起来应该是这样的:
对于我们的地理藏宝应用程序,可视化一个特殊点将落在那个地区也是非常有用的。让我们加载一个GeoJson文件,其包含纽约市每个社区的多边形。加载GeoJson文件根本上与KML的加载过程非常相似。但是,在这个例子中,我们使用GeoJsonDataSource代替。像之前的数据源一样,我们需要将它添加到viewer.datasources,来实际增加数据到场景。
var geojsonOptions = {
clampToGround : true
};
// Load neighborhood boundaries from KML file
var neighborhoodsPromise = Cesium.GeoJsonDataSource.load('./Source/SampleData/neighborhoods.geojson', geojsonOptions);
// Save an new entity collection of neighborhood data
var neighborhoods;
neighborhoodsPromise.then(function(dataSource) {
// Add the new data as entities to the viewer
viewer.dataSources.add(dataSource);
});
让我们来设计加载的社区多边形。像我们之前广告牌样式一样,当数据源加载后,首先遍历社区数据源实体,同时检查是否定义了每个实体的多边形。
// Save an new entity collection of neighborhood data
var neighborhoods;
neighborhoodsPromise.then(function(dataSource) {
// Add the new data as entities to the viewer
viewer.dataSources.add(dataSource);
neighborhoods = dataSource.entities;
// Get the array of entities
var neighborhoodEntities = dataSource.entities.values;
for (var i = 0; i < neighborhoodEntities.length; i++) {
var entity = neighborhoodEntities[i];
if (Cesium.defined(entity.polygon)) {
// entity styling code here
}
}
});
因为我们现实是社区,所有使用地区作为名字重新命名每个实体。读入的原始GeoJson文件具有邻居属性。Cesium在entity.properties下存储GeoJson属性,所以我们可以这样来设置邻居名:
// entity styling code here
// Use geojson neighborhood value as entity name
entity.name = entity.properties.neighborhood;
我们可以通过设置材料一个随机颜色(Color),来指定每个多边形一个新ColorMaterialProperty,而不是所有社区有相同的颜色。
// entity styling code here
// Set the polygon material to a random, translucent color.
entity.polygon.material = Cesium.Color.fromRandom({
red : 0.1,
maximumGreen : 0.5,
minimumBlue : 0.5,
alpha : 0.6
});
// Tells the polygon to color the terrain. ClassificationType.CESIUM_3D_TILE will color the 3D tileset, and ClassificationType.BOTH will color both the 3d tiles and terrain (BOTH is the default)
entity.polygon.classificationType = Cesium.ClassificationType.TERRAIN;
最后,使用一些基本样式选项给每个实体生成一个标签(Label)。为了保持整洁,无论什么3D对象都可能遮挡标签,我们可以用disableDepthTestDistance使Cesium一直在前面渲染它。
然后,要注意的是标签一直被定位在entity.position。一个多边形使用一个未定义的位置来创建,因为它有一个定义多边形边界的位置列表。我们可以通过取多边形位置的中心来生成一个位置:
// entity styling code here
// Generate Polygon position
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;
// Generate labels
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
};
这给我们标记了像这样的多边形:
最后,通过添加一个无人机在城市上空飞行,给NYC藏宝点增加一项高科技视图。因为飞行路线是一些时间序列的位置点,所以使用CZML文件来增加这些数据。CZML是描述时间变化的动态图形场景文件格式,主要用于在运行Cesium的web浏览器中显示。CZML描述线、点、广告牌、模型和其它图像元素,并确定它们如何随时间变化。CZML对于Cesium类同与KML对Google Earth一样,是一种标准格式,其允许通过声明样式语言(在本例中是JSON模式)使用大多数Cesium特性。
我们的CZML文件定义了一个实体(默认情况下可视化为一个点),其位置定义为不同时间点的一
系列位置。实体API中有几种属性类型可以用于处理时间动态数据,下面演示一个例子:
属性类型演示(Property Types Demo):
// Load a drone flight path from a CZML file
var dronePromise = Cesium.CzmlDataSource.load('./Source/SampleData/SampleFlight.czml');
dronePromise.then(function(dataSource) {
viewer.dataSources.add(dataSource);
});
CZML文件使用Cesium显示无人机飞行路径(Path),这是实体的属性,显示其随时间变化的位置。路径使用离散点连接到连续的直线上,然后使用插值来可视化。
最后,让我们改善无人机飞行的外观。首先,我们可以加载一个3D模型来表示无人机,并将其附加到实体上,而不是设置成一个简单的点。
Cesium支持加载基于glTF(GL传输格式)的3D模型,Cesium团队与科纳斯团队(Khronos group)共同开发的一种开放规范(open-specification),通过最小化文件大小和运行时处理,使应用程序高效加载3D模型。没有glTF模型的话,我们提供了一个在线转换方式(online converter),转换COLLADA和OBJ文件成glTF格式。
让我们加载一个无人机模型(Model),使其具备精致的物理外形和一些动画:
var drone;
dronePromise.then(function(dataSource) {
viewer.dataSources.add(dataSource);
// Get the entity using the id defined in the CZML data
drone = dataSource.entities.getById('Aircraft/Aircraft1');
// Attach a 3D model
drone.model = {
uri : './Source/SampleData/Models/CesiumDrone.gltf',
minimumPixelSize : 128,
maximumScale : 1000,
silhouetteColor : Cesium.Color.WHITE,
silhouetteSize : 2
};
});
现在我们的模型看起来不错,但不像原来的点,无人机模型有方位,当无人机向前移动时没有转向,这看起来很奇怪。庆幸的是,Cesium提供了VelocityOrientationProperty,其根据实体在时间上前后采样的位置自动计算方向。
// Add computed orientation based on sampled positions
drone.orientation = new Cesium.VelocityOrientationProperty(drone.position);
现在,无人机模型可以按照预期转向了。
我们还可以做一件事来改善无人机飞行的外观。从远处看,这可能并不明显,但无人机的飞行路径是由看起来不自然的线段构成的,这是因为Cesium在默认情况下,使用线性插值采样点来构造路径。然后,可以配置插值选项。
为了获得光滑的飞行路径,我们可以像下面这样修改插值选项:
// Smooth path interpolation
drone.position.setInterpolationOptions({
interpolationDegree : 3,
interpolationAlgorithm : Cesium.HermitePolynomialApproximation
});
我们团队有时会将Cesium描述为真实世界数据的3D游戏引擎。然而,使用真实世界的数据要比使用典型的电子游戏资产困难的多,因为真实数据可能具有难以置信的高分辨率,并且需要精确的可视化。庆幸的是,Cesium与开源社区合作已经开发了3D Tiles,一种用于流化大量异构三维地理空间数据集的开放规范(open specification)。
使用一种概念上类似Cesium的地形和影像流技术,3D Tiles可以查看巨大的模型,包含建筑数据、CAD(或者BIM)模型、点云和摄影测量模型,否则就不可能以交互方式进行查看。
3D Tiles Inspector是一个调式工具,它提供了一个底层视图。
下面是一些3D Tiles演示示例,展示了不同的数据格式:
在我们的应用程序中,将使用Cesium3DTileset显示纽约所有建筑的完整3D模型来为可视化添加现实主义。NYC 瓦片托管在Cesium Ion中,可以使用IonResource.fromAssetId:
// Load the NYC buildings tileset
var city = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(3839) }));
你可能注意到这些建筑在地面上有不正确的位置,幸运的是这比较容易修正。我们可以通过修改它的modelMatrix来调整tileset的位置。
可以通过将tileset的边界球转换成地图(Cartographic),然后添加所需的偏移量并重新设置模型矩阵,从而找到模型从地面到当前的偏移量:
// Adjust the tileset height so its not floating above terrain
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 styling language)来样式化部分tileset。3D Tiles样式定义了用于计算颜色(RGB和半透明)和显示Cesium3DtileFeature属性的表达式,Cesium3DtileFeature是tileset的一部分,例如城市中的单个建筑。样式化通常基于存储在tile批处理表中的特性,特征属性可以是任何东西,例如高度、名字、坐标、建设日期等等,而是内置在tileset资产中。样式使用JSON来定义,表达式是用扩展样式化的JavaScript小子集编写的。此外,样式化语言提供了一组内置函数来支持常见的数学操作。
像这样定义一个Cesium3DtileStyle:
var defaultStyle = new Cesium.Cesium3DTileStyle({
color : "color('white')",
show : true
});
这种样式只使我们纽约的所有建筑都是白色的,而且总是可见。为了实际设置tileset来使用这个样式,我们设置city.style:
city.style = defaultStyle;
我们可以定义许多样式,下面是另一种方式,使建筑透明化:
1. var transparentStyle = new Cesium.Cesium3DTileStyle({
2. color : "color('white', 0.3)",
3. show : true
4. });
对瓦片中的每个特性使用相同的样式只是表面现象。我们可以对每个feature使用特定属性来决定其样式。下面是根据建筑物高度来渲染它们:
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 sandcastle demos(3D瓦片沙堡演示)。
3D 瓦片演示:
如果你有数据,并需要帮助转换为3D瓦片,请继续关注Cesium平台更新。订阅更新(Subscribe for updates here)
最后,让我们添加一些鼠标交互功能。为了改善藏宝点标签的可见性,当用户悬停在标记上时突出显示,我们可以改变它们的样式。
为了实现这一点,我们将使用picking(挑选),一种Cesium的特性,它从3D场景中返回给定像素位置的数据。
下面是不同类型的picking:
下面是一些实际操作重的例子:
由于希望在悬停触发时突出显示效果,首先我们需要创建一个鼠标操作处理程序。这就需要使用到ScreenSpaceEventHandler,在用户输入操作时触发指定函数的一组处理程序。用户操作类型ScreenSpaceEventType,运行特定的函数,传递用户操作作为参数。这里,我们将传递一个以运动为输入的函数:
接下来写突显函数。处理程序将传入一个鼠标移动,从中我们可以提取一个窗口位置,以便于pick()一起使用。如果pick返回一个广告牌对象,就知道我们正悬停在一个标签上。然后,使用学到的实体样式内容,实现突出显示样式。
这样成功触发标记的突出显示样式的改变。然后,你会发现当移开光标的时候,标签仍然保持高亮。我们可以通过跟踪高亮显示的最后一个标记来修复这个问题,并恢复原始的样式。
下面是完整的功能,标记高亮和非高亮工作:
就是这样,我们成功添加一个鼠标移动处理程序和标记实体悬停行为。
为了展示无人机飞行,让我们来试验下相机模式。我们将保持简单,用户可以在两种基本相机模式之间替换。
因为自由模式使用的默认控制,所以不需要任何代码。至于无人机跟随模式,我们可以使用查看器内置的实体跟踪功能,以一定偏移量来定位相机来查看无人机。即使实体在移动,这样设置相机与特定实体有一个固定的偏移量。为了跟踪实体,只需要设置viewer.trackedEntity。
要切换回自由相机模式,只需要设置查看器viewer.trackedEntity返回到未定义,然后使用camera.flyTo()返回主view。
下面是相机模式函数:
为了将它附加到HTML输入中,我们可以附加这个函数来改变适当元素上的事件:
最后,当用户双击实体时,其将被自动跟踪。如果用户通过单击来开始跟踪无人机,那么我们可以添加一些处理来自动更新UI:
这就是我们两个相机模式-现在可以自由切换到无人机相机视图,结果看起来像这样:
其余的代码只是增加了一些额外的可视化选项。类似于我们之前与HTML元素的交互,可以附加监听函数来触发影子,和社区多边形显示。
让我们从创建一个简单方式来触发切换社区多边形开始。通常,可以通过Entity.show设置可见性来隐藏实体,但是,这只设置了单个实体的可见性,我们希望同时设置隐藏或显示所有社区实体。
我们可以这样做,将所有社区实体添加到父实体来实现这一点,正如示例(this example)中所示或者通过简单地使用EntityCollection的show属性。然后,我们可以通过更改neighborhoods.show的值来同时设置所有子实体的可见性:
可以使用相类似的方式来改变阴影的可见性:
最后,由于3D瓦片可能不会立即加载,我们可以增加一个加载指示器,其只有在加载了tileset之后才被删除(因此应答也就得到了解决)。
祝贺哦~!~!你已经成功完成了CesiumJS应用程序!可以随意探索和试验这里提供的代码,以进一步学习Cesium。我们非常欢迎你来Cesium社区,并期待看到你使用CesiumJS库构建的惊人应用程序。
接下来是什么呢?
对于本教程以及余下的Cesium开发中,我们鼓励你依靠以下资源:
任何时候,当你遇到困难的时候,这些资源中很有可能会有你想要的答案。
我们喜欢分享Cesium社区创建的所有惊人的应用程序。世界各地的开发者已经创建了比我们想象的更有趣的应用程序!只要你的应用程序准备好与世界分享,请联系我们,在CesiumJS演示页面(CesiumJS Demos Page)展示你的应用程序。更多关于你应用程序展示的信息,请参阅本文(this blog post)。
Cesium ion是一个新的商业平台,由web服务和工具组成来完善CesiumJS的可视化,创建一个完整的3D地图平台。Cesium团队对ion特别感兴趣,因为它是我们围绕CesiumJS构建的第一个产品,用来支持开发开源CesiumJS。
ion的愿景包含订阅:
更多信息请访问cesium.com。
Happy developing!