- 什么是实体(Entity)API?
- 我们的第一个实体
- 形状和物体
- 高度与挤压
- Viewer中的Entity功能
- Camera控制
- 管理Entities
- Picking 拾取
- Points, billboards, and labels(点、广告牌和标签)
- 3D models (三维模型)
- The property system(属性系统)
- 作业
1 - 空间数据可视化之Entity
本教程将教读者如何使用Cesium的实体(Entity)API绘制空间数据,如点、标记、标签、线、模型、形状和物体。
什么是实体(Entity)API?
Cesium具有丰富的用于空间数据的API,可以分为两类:面向图形开发人员的低级API(通常称为原始(Primitive)API)和用于数据驱动的可视化的高级API(称为实体(Entity)API)。
原始API的主要目标是暴露手头执行任务所需的最小抽象量。它希望我们像图形程序员一样思考,并使用图形术语。它的结构是为给定的可视化类型提供最有性能和灵活性的实现,而不是为了API的一致性。加载一个模型不同于创建一个广告牌,两者都与创建多边形完全不同。每种类型的可视化都有其独特的特征。此外,它们各自具有不同的性能特征,并且需要遵循不同的最佳实践。虽然它功能强大且灵活,但大多数应用程序都比Primitive API提供的抽象级别更高。原始API的主要目标是开放手头当前研发工作的所需的最小抽象量。它希望我们像图形程序员一样思考,并使用图形术语。它的结构是为给定的可视化类型提供最有性能和灵活性的实现,而不是为了API的一致性。此外,它们各自具有不同的性能特征,并且需要遵循不同的最佳实践。虽然它功能强大且灵活,但大多数应用程序都提供比Primitive API的抽象级别更高的服务接口。
实体API的目的是公开一组设计一致的高级对象,这些对象将相关的可视化和信息聚合到一个统一的数据结构中,我们称之为实体。它让我们专注于展示我们的数据,而不是担心可视化的潜在机制。它还提供了易于构建复杂的、时间动态可视化的构造,这种可视化方式与静态数据自然相适应。虽然实体API实际上在背后使用了原始API(Primitive API),但这是我们(几乎)永远不必关注的实现细节。通过将各种启发式应用到我们提供的数据,实体API能够提供灵活的、高性能的可视化,同时公开一致的、易于学习和易于使用的接口。
我们的第一个实体
学习实体API的基本方法之一是通过查看一些代码。为了让事情简单化,我们将在Cesium的Hello World例子中构建。如果读者在本地开发Cesium,可以自由地使用自己的应用程序。
假设我们想从经度和纬度的列表中添加一个美国州怀俄明的多边形。(怀俄明之所以被选中是因为它是一个简单的多边形)我们可以把下面的代码复制粘贴到http://111.22.69.99:58080/cesium/Apps/Sandcastle去做:
var viewer = new Cesium.Viewer('cesiumContainer');
var wyoming = viewer.entities.add({
name : 'Wyoming',
polygon : {
hierarchy : Cesium.Cartesian3.fromDegreesArray([
-109.080842,45.002073,
-105.91517,45.002073,
-104.058488,44.996596,
-104.053011,43.002989,
-104.053011,41.003906,
-105.728954,40.998429,
-107.919731,41.003906,
-109.04798,40.998429,
-111.047063,40.998429,
-111.047063,42.000709,
-111.047063,44.476286,
-111.05254,45.002073]),
height : 0,
material : Cesium.Color.RED.withAlpha(0.5),
outline : true,
outlineColor : Cesium.Color.BLACK
}
});
viewer.zoomTo(wyoming);
点击运行按钮(或者F8)能看到下面的图像:
因为我们的一个目标是让Cesium的代码易于理解,希望这是不言自明的。我们创建了Viewer widget,它充当几乎所有Cesium应用程序的基础,然后通过viewer.entities.add. 添加一个新的Entity。我们传递的需add的对象只是一个提供初始值的选项参数。返回值是实际的实体实例。最后,我们调用 viewer.zoomTo 以确保实体处于视图中。 有很多实体选项可用,但是现在我们指定polygon内部的半透明红色和边界的黑色轮廓。我们也给实体一个“Wyoming 怀俄明”的显示名称。
形状和物体
有了创建多边形的基本知识,并且由于实体API的同质性,我们现在可以通过简单地使用http://111.22.69.99:58080/cesium/Apps/Sandcastle中的示例作为参考来创建各种图形。下面是支持的形状和物体的完整列表。
Boxes entity.box
Circles and ellipses entity.ellipse
Corridor entity.corridor
Cylinder and cones entity.cylinder
Polygons entity.polygon
Polylines entity.polyline
Polyline volumes entity.polylineVolume
Rectangles entity.rectangle
Spheres and ellipsoids entity.ellipsoid
Walls entity.wall
材质和轮廓
不管它们的几何定义如何,所有形状和物体都有一组共同的属性来控制它们的外观。fill
属性是一个布尔值,它指定是否填充了表面的内部,而outline
属性控制形状的边缘是否被轮廓化。 当fill
设为true
,material
属性决定填充物是什么。在接下来的例子中,让我们创建一个半透明的蓝色椭圆。默认情况下,fill
是true
,outline
是false
,所以我们只需要指定material
。
var entity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-103.0, 40.0),
ellipse : {
semiMinorAxis : 250000.0,
semiMajorAxis : 400000.0,
material : Cesium.Color.BLUE.withAlpha(0.5)
}
});
viewer.zoomTo(viewer.entities);
var ellipse = entity.ellipse; // For upcoming examples
Image
我们也可以将材质指定为一个图片链接:
ellipse.material = '//cesiumjs.org/tutorials/images/cats.jpg';
在上述两种情况下,在赋值时自动为我们创建一个ColorMaterialProperty属性或ImageMaterialProperty属性。对于更复杂的材料,我们需要自己创建一个材料属性实例。目前,实体形状和物体支持颜色、图像、棋盘、条纹和网格材料。
棋盘
ellipse.material = new Cesium.CheckerboardMaterialProperty({
evenColor : Cesium.Color.WHITE,
oddColor : Cesium.Color.BLACK,
repeat : new Cesium.Cartesian2(4, 4)
});
条纹
ellipse.material = new Cesium.StripeMaterialProperty({
evenColor : Cesium.Color.WHITE,
oddColor : Cesium.Color.BLACK,
repeat : 32
});
网格
ellipse.material = new Cesium.GridMaterialProperty({
color : Cesium.Color.YELLOW,
cellAlpha : 0.2,
lineCount : new Cesium.Cartesian2(8, 8),
lineThickness : new Cesium.Cartesian2(2.0, 2.0)
});
轮廓
与fill
属性不同,outline
没有相应的材料,而是依赖于两个独立的outlineColor
和outlineWidth
属性。outlineWidth
只适用于非Windows系统,如Android、iOS、Linux和OS X。这是由于WebGL是如何在Windows上的所有三个主要浏览器引擎中实现所限制的。
ellipse.fill = false;
ellipse.outline = true;
ellipse.outlineColor = Cesium.Color.YELLOW;
ellipse.outlineWidth = 2.0;
折线
折线是一种特殊情况,因为它们没有填充或轮廓属性。相反,他们依靠专门的材料来代替颜色。由于这些特殊材料,不同宽度和轮廓宽度的折线将对所有系统起作用。
var entity = viewer.entities.add({
polyline : {
positions : Cesium.Cartesian3.fromDegreesArray([-77, 35,
-77.1, 35]),
width : 5,
material : Cesium.Color.RED
}});
viewer.zoomTo(viewer.entities);
var polyline = entity.polyline // For upcoming examples
折线轮廓
polyline.material = new Cesium.PolylineOutlineMaterialProperty({
color : Cesium.Color.ORANGE,
outlineWidth : 3,
outlineColor : Cesium.Color.BLACK
});
折现光晕
polyline.material = new Cesium.PolylineGlowMaterialProperty({
glowPower : 0.2,
color : Cesium.Color.BLUE
});
高度与挤压
覆盖在地球上的所有形状,当前是圆、椭圆、多边形和矩形,也可以放置在海拔高度或挤压成一个物体。在这两种情况下,形状或物体仍然符合其下方的地球曲率。
对于高度,我们所要做的就是在相应的图形对象上设置高度属性,对于上面列出的所有形状都是一样的。这可能是提到Cesium总是使用米、弧度和秒作为单位的好时机,除非函数明确地表示了其他情况,比如Cartesian3.fromDegrees。下面的代码行将多边形提升到地球上方250000米处。
wyoming.polygon.height = 250000;
将形状挤压成物体同样容易,我们只需要设置
extrudedHeight
属性。物体将在height
和extrudedHeight
之间产生。如果height
是undefined
,则物体从0开始。为了创造一个物体从200000米开始,延伸到250000米,我们可以使用下面的代码。这当然意味着物体本身是50000米高。
wyoming.polygon.height = 200000;
wyoming.polygon.extrudedHeight = 250000;
Viewer中的Entity功能
让我们看看Viewer为操作entities提供出来的功能函数。
选中和描述
点击Viewer中的entity将在entity的位置上呈现SelectionIndicator控件,提供一个InfoBox用于呈现更多的信息。我们可以设置name,来定义InfoBox的标题。我们也以HTML样式来提供Entity.description的属性。
wyoming.name = 'wyoming';
wyoming.description = '\
<img\
width="50%"\
style="float:left; margin: 0 1em 1em 0;"\
src="//cesium.com/docs/tutorials/creating-entities/Flag_of_Wyoming.svg"/>\
<p>\
Wyoming is a state in the mountain region of the Western \
United States.\
</p>\
<p>\
Wyoming is the 10th most extensive, but the least populous \
and the second least densely populated of the 50 United \
States. The western two thirds of the state is covered mostly \
with the mountain ranges and rangelands in the foothills of \
the eastern Rocky Mountains, while the eastern third of the \
state is high elevation prairie known as the High Plains. \
Cheyenne is the capital and the most populous city in Wyoming, \
with a population estimate of 63,624 in 2017.\
</p>\
<p>\
Source: \
<a style="color: WHITE"\
target="_blank"\
href="http://en.wikipedia.org/wiki/Wyoming">Wikpedia</a>\
</p>';
InfoBox中展示的所有HTML是沙盒式的。这组织了外部数据源带有恶意的注入到Cesium的应用程序中。为了在描述中运行JavaScript或浏览器插件,访问沙盒中的iframe通过viewer.infoBox.frame属性。参考该文可以获得更多的信息用于控制iframe中的沙盒。
Camera控制
使用iewer.zoomto命令查看特定Entity。还有一个viewer.flyto方法,用于对Entity执行动画Camera飞行。这两种方法都可以传递给Entity、EntityCollection、DataSource或实体数组。
任何一种方法都会计算所有提供的实体的视图。默认情况下,Camera朝向北方,并从45度角向下看。通过传入HeadingPitchrange自定义此项。
var heading = Cesium.Math.toRadians(90);
var pitch = Cesium.Math.toRadians(-30);
viewer.zoomTo(wyoming, new Cesium.HeadingPitchRange(heading, pitch));
zoomTo和flyTo都是异步函数;也就是说,它们不能保证在返回之前完成。例如,飞行到Entity会在许多动画帧后发生。这两个功能都返回Promises,可用于计划飞行或缩放完成后要执行的功能。用下面的代码片段替换示例中的zoomTo。这架飞机飞往怀俄明州,并在飞行结束后选中它。
viewer.flyTo(wyoming).then(function(result){
if (result) {
viewer.selectedEntity = wyoming;
}
});
如果航班飞行成功完成,则传递给回调函数的结果参数将为true;如果航班被取消,则结果参数将为false,即,在此航班完成之前,用户启动了另一个航班或缩放;或者,由于目标没有相应的可视化效果,即没有可缩放的对象。
有时,特别是在处理时间动态数据时,我们希望Camera聚焦跟随一个entity而不是地球的中心。可以通过设置viewer.trackedEntity完成。跟踪实体需要设置position。
wyoming.position = Cesium.Cartesian3.fromDegrees(-107.724, 42.68);
viewer.trackedEntity = wyoming;
停止跟踪需要设置viewer.trackedEntity为undefined或远离entity双击屏幕即可。调用zoomTo或flyTo也可以取消跟踪。
管理Entities
EntityCollection是用于管理和监视一组实体的关联数组。viewer.entities是EntityCollection。EntityCollection包括用于管理实体的方法,如add、remove和removeAll。
有时我们需要更新我们以前创建的实体。所有实体实例都有一个唯一的id,可用于从集合中检索实体。我们可以为实体指定一个ID,否则将自动生成一个ID。
viewer.entities.add({
id : 'uniqueId'
});
使用getByiId检索实体。如果不存在具有提供的ID的实体,则返回undefined。
var entity = viewer.entities.getById('uniqueId');
要获取实体或创建新实体(如果不存在),请使用getOrCreateEntity。
var entity = viewer.entities.getOrCreateEntity('uniqueId');
手动创建新实体,然后使用add将其添加到集合中。如果集合中已存在具有相id的实体,则此方法将抛出异常。
var entity = new Entity({
id : 'uniqueId'
});
viewer.entities.add(entity);
EntityCollection的功能使用CollectionChanged事件发光。当在集合中添加、删除或更新实体时,会通知监听器。
使用http://111.22.69.99:58080/cesium/Apps/Sandcastle中的Geometry Showcase 示例。在创建viewer的行后粘贴以下代码。
function onChanged(collection, added, removed, changed){
var msg = 'Added ids';
for(var i = 0; i < added.length; i++) {
msg += '\n' + added[i].id;
}
console.log(msg);
}
viewer.entities.collectionChanged.addEventListener(onChanged);
运行该示例时,您应该在控制台中看到大约65条消息,每次调用viewer.entities.add时都会看到一条消息。
当一次更新大量的实体时,将队列更新结束后并在最后发送一个整体事件,这样更具性能。这样Cesium可以在一次通过中处理所需的变化。在示例末尾,在viewer.entities.add之前调用viewer.entities.suspendEvents,并调用viewer.entities.resumeEvents。当再次运行演示时,我们现在得到包含所有65个实体的单一事件。这些调用是引用计数的,因此可以嵌套多个挂起和恢复调用。
Picking 拾取
选择(单击选择一个对象)是我们需要与基本API进行短暂交互的领域之一。使用scene.pick和scene.drillpick检索实体。
/**
* Returns the top-most entity at the provided window coordinates
* or undefined if no entity is at that location.
*
* @param {Cartesian2} windowPosition The window coordinates.
* @returns {Entity} The picked entity or undefined.
*/
function pickEntity(viewer, windowPosition) {
var picked = viewer.scene.pick(windowPosition);
if (defined(picked)) {
var id = Cesium.defaultValue(picked.id, picked.primitive.id);
if (id instanceof Cesium.Entity) {
return id;
}
}
return undefined;
};
/**
* Returns the list of entities at the provided window coordinates.
* The entities are sorted front to back by their visual order.
*
* @param {Cartesian2} windowPosition The window coordinates.
* @returns {Entity[]} The picked entities or undefined.
*/
function drillPickEntities(viewer, windowPosition) {
var i;
var entity;
var picked;
var pickedPrimitives = viewer.scene.drillPick(windowPosition);
var length = pickedPrimitives.length;
var result = [];
var hash = {};
for (i = 0; i < length; i++) {
picked = pickedPrimitives[i];
entity = Cesium.defaultValue(picked.id, picked.primitive.id);
if (entity instanceof Cesium.Entity &&
!Cesium.defined(hash[entity.id])) {
result.push(entity);
hash[entity.id] = true;
}
}
return result;
};
Points, billboards, and labels(点、广告牌和标签)
通过设置position、point和label来创建图形点或标签。例如,在我们最喜欢的运动队的主体育场放置一个点。
var viewer = new Cesium.Viewer('cesiumContainer');
var citizensBankPark = viewer.entities.add({
name : 'Citizens Bank Park',
position : Cesium.Cartesian3.fromDegrees(-75.166493, 39.9060534),
point : {
pixelSize : 5,
color : Cesium.Color.RED,
outlineColor : Cesium.Color.WHITE,
outlineWidth : 2
},
label : {
text : 'Citizens Bank Park',
font : '14pt monospace',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth : 2,
verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
pixelOffset : new Cesium.Cartesian2(0, -9)
}
});
viewer.zoomTo(viewer.entities);
默认情况下,标签水平和垂直居中。由于标签和点共享相同的位置,它们在屏幕上重叠。为避免这种情况,请指定标签源Verticalorigin.BOTTOM并将像素偏移量设置为(0,-9)。 将该点替换为一个billboard,它是一个始终面向用户的标记。
var citizensBankPark = viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-75.166493, 39.9060534),
billboard : {
image : '//cesiumjs.org/tutorials/Visualizing-Spatial-Data/images/Philadelphia_Phillies.png',
width : 64,
height : 64
},
label : {
text : 'Citizens Bank Park',
font : '14pt monospace',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth : 2,
verticalOrigin : Cesium.VerticalOrigin.TOP,
pixelOffset : new Cesium.Cartesian2(0, 32)
}
});
有关更多自定义选项,请参见http://111.22.69.99:58080/cesium/Apps/Sandcastle标签和广告牌示例。
3D models (三维模型)
CesiumJS支持通过glTF(运行时asset format)创建3D模型。您可以在3D models中找到示例模型。
将位置和URI设置为glTF 模型以创建模型实体。
var viewer = new Cesium.Viewer('cesiumContainer');
var entity = viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706),
model : {
uri : '../../../../Apps/SampleData/models/GroundVehicle/GroundVehicle.glb'
}
});
viewer.trackedEntity = entity;
默认情况下,模型是垂直的,面向东。通过为Entity.Orientation属性指定Quaternion来控制模型的方向。这将控制模型的heading、pitch和roll。
var viewer = new Cesium.Viewer('cesiumContainer');
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706);
var heading = Cesium.Math.toRadians(45.0);
var pitch = Cesium.Math.toRadians(15.0);
var roll = Cesium.Math.toRadians(0.0);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(heading, pitch, roll));
var entity = viewer.entities.add({
position : position,
orientation : orientation,
model : {
uri : '../../../../Apps/SampleData/models/GroundVehicle/GroundVehicle.glb'
}
});
viewer.trackedEntity = entity;
有关更高级的模型功能,请参见3D模型教程。如果你创建自己的模型,一定要看到我们关于glTF Tips for Artists提示的帖子。
The property system(属性系统)
我们为实体定义的所有值都存储为property对象。例如,请参见怀俄明州大纲的值:
console.log(typeof wyoming.polygon.outline);
outline是ConstantProperty的一个实例。本教程使用一种称为隐式属性转换(implicit property conversion)的速记形式,它自动获取原始值并在引擎盖下创建相应的Property。如果没有这个速记,我们将不得不编写一个更长版本的初始示例:
var wyoming = new Cesium.Entity();
wyoming.name = 'Wyoming';
var polygon = new Cesium.PolygonGraphics();
polygon.material = new Cesium.ColorMaterialProperty(Cesium.Color.RED.withAlpha(0.5));
polygon.outline = new Cesium.ConstantProperty(true);
polygon.outlineColor = new Cesium.ConstantProperty(Cesium.Color.BLACK);
wyoming.polygon = polygon;
viewer.entities.add(wyoming);
之所以使用属性,是因为实体API不仅能够表示常量值,而且能够表示随时间变化的值。请参阅Callback Property和Interpolation示例,了解一些时间动态属性。
作业
使用Sandcastle.addToolbarMenu设置下拉列表,用来展示所有的entity类型:包括十个几何类型、点、广告牌和标签和gltf