700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Cesium中文教程-空间数据可视化Visualizing Spatial Data(二)

Cesium中文教程-空间数据可视化Visualizing Spatial Data(二)

时间:2020-11-15 09:32:24

相关推荐

Cesium中文教程-空间数据可视化Visualizing Spatial Data(二)

目录

(5)相机控制(Camera Controls)

(6)管理实体(Managing Entities)

挑选Picking

(7)点、广告牌、标签(Points,Billboards,and Labels)

(8)3D模型(3D Models)

(9)The Property System

(10)下一步工作(Where to go from here)

(5)相机控制(Camera Controls)

前面第一个例子中,使用zoomTo控制展示到一个特定的实体。其实也可以通过按下InfoBox左上角的camera按钮或双击一个实体来完成。还有一种flyTo的方法,可以达到相同的视图效果,采用摄像机飞行的动画来完成,而不是立即跳转。除了能捕捉到单一的实体,这两种方法都可以被传递为一个EntityCollection(之后讨论哦),一个DataSource,常用来加载通用的数据格式,如GeoJSON,或者一个标准JavaScript的实体数组。

默认情况下,任何方法都将计算出一个视角,来确保整个实体是可见得。相机将被调整为朝北,45度角直视目标物。可以通过提供的heading,pith和range来定制视角。下面的代码转动相机从东面30度来直视Wyoming,由于没有定义range,使用了计算的默认值。

varheading=Cesium.Math.toRadians(90);varpitch=Cesium.Math.toRadians(-30);viewer.zoomTo(wyoming,newCesium.HeadingPitchRange(heading,pitch));

zoomTo和flyTo都是异步函数,也就是说它们不能保证在返回之前已经完成。例如,飞到一个实体发生在许多动画帧。这些函数返回一个应答(Promises),这可以让用户预订在飞行或者缩放完成后执行函数。使用下面的语句替换例子中的zoomTo,这不仅会飞到wyoming,还会在飞行完后选中它。

viewer.flyTo(wyoming).then(function(result){if(result){viewer.selectedEntity=wyoming;}});

如果飞行成功完成,那么传递给回调函数的result参数是true,如果飞行被取消,即用户初始化另一个飞行或者在完成之前缩放,或因为目标没有相应的可视化功能,即无法实现缩放,那么传递给回调函数的result参数是false。

有时,在应用于时间序列的动态数据时,希望相机集中在实体而不是地球。这种情况可以设置viewer.trackedEntity属性。追踪实体需要设置position,在第一个例子中尝试这种方式,可以给wyoming增加一个位置来追踪它:

wyoming.position=Cesium.Cartesian3.fromDegrees(-107.724,42.68);viewer.trackedEntity=wyoming;

通过不定义viewer.trackedEntity或者点击InfoBox消息框左上角的取消按钮可以停止追踪。调用zoomTo或者flyTo也将取消追踪,viewer.trackedEntity将成为没有定义。

对于许多用例,viewer提供的专对于实体的相机功能已经足够了,如果想进一步在应用程序中定制视图,可以查看相机教程(Camera tutorial)。

(6)管理实体(Managing Entities)

EntityCollection是一个关联矩阵,可以方便地管理和监控一组实体。以viewer.entities属性的形式已经使用过它,EntityCollection即有一些传统的方法,例如增加(add)、去除(remove)、去除所有(removeAll)等,也具有实体特殊性的函数,将在接下来的章节讨论。

许多应用程序依赖于存储在服务器上的数据,直到客户机需要它为止。有时我们需要更新之前创建的实体,所有实体的唯一id属性可以用到这种情况。由于在第一个例子中没有定义标识,而Cesium为我们创建了一个全局唯一标识(GUID),其生成一个唯一字符串例如182bdba4-2b3e-47ae-bf0b-83f6fde285fd。服务器上的数据很可能具有自己唯一的标识符,使用它来替代,这样可以在实体创建时指定它。

viewer.entities.add({id:'uniqueId'});

之后,可以通过getById重新获取实体,如果提供id的实体不存在,那么返回是未定义的。

varentity=viewer.entities.getById('uniqueId');

另一种情况是,如果实体存在的话就去更新它,如果不存在,那么创建一个新的。getOrCreateEntity返回一个提供id的可用实体,必要的话会创建一个新实例并增添到收集器。

varentity=viewer.entities.getOrCreateEntity('uniqueId');

最后,我们可以手动创建一个新实体,并增添(add)到实体集中。这种情况中,add检查到向它传递了一个现有的实体,并简单返回相同的实例。

varentity=newEntity({id:'uniqueId'});viewer.entities.add(entity);

EntityCollection能力开始起作用的地方是在collectionChanged Event事件中,这可以被用来知道什么时候一个实体在收集器中被增加、去除、或者更新。当构建用户界面或应用程序其它部分需要监控可能在别的地方生成的数据时,这一点特别有用。

为了看到这样的效果,使用沙堡中的Geometry Showcase示例。复制粘贴下面的代码到头部附近,当viewer创建之后立马可以看到效果。

functiononChanged(collection,added,removed,changed){varmsg='Addedids';for(vari=0;i<added.length;i++){msg+='\n'+added[i].id;}console.log(msg);}viewer.entities.collectionChanged.addEventListener(onChanged);

当我们运行上面的示例时,在终端中可以看到65个信息,每个都调用viewer.entitier.add(这里只是在增加实体,所以removed和changed数组一直是空的)。Cesium也在内部订阅了此事件,需要时可以用于更新可视化。当同时更新大量实体时,更有效的方式是排队改变并在最后发送一个大事件。这样提高了性能,因为cesium可以一次性处理任何需要的变化,为此,可以在更新开始时,调用viewer.entities.supendEvents,然后是viewer.entities.resumeEvents。

在第一个viewer.entities.add之前添加一个挂起的调用,在示例的最后添加一个resume恢复调用。再一次运行示例,我们获得包含65个实体的一个唯一事件。这些调用是引用计数的,因此可以嵌套多个挂起和恢复调用,而不会出现任何问题。但是,在处理结束时忘记调用resume将导致场景中不会显示任何内容,因为在每个挂起调用都有一个对应的恢复匹配的调用之前,不会发送任何消息。

挑选Picking

挑选(Picking),即通常使用鼠标在给定的屏幕坐标上挑选一个对象。其是cesium中需要与原始API(PIPA Primitive API)进行简单接口的领域之一。这将在cesium的未来版本中得到补充,引入实体特性的挑选功能。对于目前,可以使用一些低级函数,scene.pick和scene.drillPick来检索实体。下面是实体挑选的基本实现,可以使用到任何应用中。

/***Returnsthetop-mostEntityattheprovidedwindowcoordinates*orundefinedifnoEntityisatthatlocation.**@param{Cartesian2}windowPositionThewindowcoordinates.*@returns{Entity}ThepickedEntityorundefined.*/functionpickEntity(viewer,windowPosition){varpicked=viewer.scene.pick(windowPosition);if(defined(picked)){varid=Cesium.defaultValue(picked.id,picked.primitive.id);if(idinstanceofCesium.Entity){returnid;}}returnundefined;};/***ReturnsthelistofEntitiesattheprovidedwindowcoordinates.*TheEntitiesaresortedfronttobackbytheirvisualorder.**@param{Cartesian2}windowPositionThewindowcoordinates.*@returns{Entity[]}ThepickedEntitiesorundefined.*/functiondrillPickEntities(viewer,windowPosition){vari;varentity;varpicked;varpickedPrimitives=viewer.scene.drillPick(windowPosition);varlength=pickedPrimitives.length;varresult=[];varhash={};for(i=0;i<length;i++){picked=pickedPrimitives[i];entity=Cesium.defaultValue(picked.id,picked.primitive.id);if(entityinstanceofCesium.Entity&&!Cesium.defined(hash[entity.id])){result.push(entity);hash[entity.id]=true;}}returnresult;};

虽然各种场景选择方法返回的是带有primitive信息的对象,而不是实体实例,但是实体API(EAPI Entity API)是结构化的,这样与实体相对应的每个Primitive都有该实体的id属性。我们要做的就是检查选中对象的id是否是实体(Entity)的一个实例。虽然这些函数很简单,但它们并没有包含在cesium中,因为我们计划了更强大的功能。

(7)点、广告牌、标签(Points,Billboards,and Labels)

离开形状(shapes)和容器(volumes)章节之后,来了解下如何在cesium中可视化表示兴趣点。

创建一个图形点或者标签是很简单的,仅仅需要为实体、点(point)、标签(label)等可视化实体指定一个坐标(position)。例如,我们想在最喜欢的球队主场上放置一个点。

varviewer=newCesium.Viewer('cesiumContainer');varcitizensBankPark=viewer.entities.add({name:'CitizensBankPark',position:Cesium.Cartesian3.fromDegrees(-75.166493,39.9060534),point:{pixelSize:5,color:Cesium.Color.RED,outlineColor:Cesium.Color.WHITE,outlineWidth:2},label:{text:'CitizensBankPark',font:'14ptmonospace',style:Cesium.LabelStyle.FILL_AND_OUTLINE,outlineWidth:2,verticalOrigin:Cesium.VerticalOrigin.BOTTOM,pixelOffset:newCesium.Cartesian2(0,-9)}});viewer.zoomTo(viewer.entities);

这些选项大多数是自己编制的,但其中一部分需要加以说明。例如,字体设置遵循CSS compact font syntax。

默认情况下,标签在水平和垂直位置居中。由于标签和点的位置相同,它们通常会在屏幕上重叠。为了将标签绘制在该点之上,我们可以指定它的原点应该是在底部,这意味着文本的底部是接触实体位置的部分,还可以在屏幕中指定偏移量。在上面的例子中,使用(0,-9),当结合VerticalOrigin.BOTTOM时,表示标签底部位于实体上方9个像素处。值为负值,是因为想减少屏幕坐标的Y值,Y值从顶部开始,随着屏幕向下移动而增加。设置原点和偏移值,无论用户从哪个角度或旋转角度查看,都可以确保标签一直在点的上面。

当然,使用一个点不是很令人激动,所有可以用一个billboard(广告牌)代替这个点,这是一个始终面向用户的标记。

varcitizensBankPark=viewer.entities.add({position:Cesium.Cartesian3.fromDegrees(-75.166493,39.9060534),billboard:{image:'///tutorials/Visualizing-Spatial-Data/images/Philadelphia_Phillies.png',width:64,height:64},label:{text:'CitizensBankPark',font:'14ptmonospace',style:Cesium.LabelStyle.FILL_AND_OUTLINE,outlineWidth:2,verticalOrigin:Cesium.VerticalOrigin.TOP,pixelOffset:newCesium.Cartesian2(0,32)}});

在上面的例子中,我们明确设置了广告牌的宽度和高度,但这不是必需的。如果省略掉,则使用文件的原有宽度和高度。

标签的广告牌还有许多选项在这里没有提到,可以在沙堡中查看他们每一个起到的作用:Labels,Billboards。

(8)3D模型(3D Models)

Cesium支持通过glTF格式的3D模型,WebGL,OpenGL ES和OpneGL运行时支持的格式,还有一些随时可用的glTF模型:转动螺旋桨的飞机,转动车轮的陆地车,一个带有皮肤的周期行走角色。这些都可以在3D models沙堡例子中看到。

加载模型与我们目前使用的其他任何类型的可视化并没有太大的不同。所需要的就是一个实体位置和一个glTF模型的uri(统一资源标识符)。

varviewer=newCesium.Viewer('cesiumContainer');varentity=viewer.entities.add({position:Cesium.Cartesian3.fromDegrees(-123.0744619,44.0503706),model:{uri:'../../../../Apps/SampleData/models/GroundVehicle/GroundVehicle.glb'}});viewer.trackedEntity=entity;

可以选择提供一个缩放属性,该属性从模型的原始大小均匀地缩放模型以及一个minimumPixelSize(最小像素大小)属性,该属性可以防止模型看起来小于提供的像素大小。

默认情况下,模型是竖直面朝东,可以通过定义实体方位(Entity.orientation)属性四元素(Quaternion)来控制模型的方位。这比仅仅有位置的示例稍微复杂些,让我们来控制模型的俯仰、滚动、偏航(heading、pitch、roll)。复制下面的代码到沙堡中,改变它们的值,观察是如何影响方位的。

varviewer=newCesium.Viewer('cesiumContainer');varposition=Cesium.Cartesian3.fromDegrees(-123.0744619,44.0503706);varheading=Cesium.Math.toRadians(45.0);varpitch=Cesium.Math.toRadians(15.0);varroll=Cesium.Math.toRadians(0.0);varorientation=Cesium.Transforms.headingPitchRollQuaternion(position,newCesium.HeadingPitchRoll(heading,pitch,roll));varentity=viewer.entities.add({position:position,orientation:orientation,model:{uri:'../../../../Apps/SampleData/models/GroundVehicle/GroundVehicle.glb'}});viewer.trackedEntity=entity;

由于在cesium中需要将模型转化为gltF格式使用,所以我们提供了一个基于网络的转化器(web-based converter),其上传一个COLLADA模型、返回一个glTF格式版本的模型。

虽然实体API(Entity API)目前不支持高级模型的用例,例如节点选择或直接动画控制,但是我们可以使用基本API(Primitive API)来完成,会有单独的章节3D Model Primitive(3D模型元素)教程去介绍这种情况。我们有明确的计划来增强实体API(Entity API)以包含这些特性。Primitive教程包含调试模型的小提示,这些模型不能在Cesium中正确渲染的时候,可以调试。如果创建自己的模型,那么一定要看gITF Tips for Artists。

(9)The Property System

到目前为止,我们仅仅是给实体和图片对象赋值,而从来没有读取过任何值。实际上,如果我们真的那么做结果可能会感到惊讶。以多边形轮廓属性为例,在第一个示例中我们赋值为true。直觉告诉我们,调用console.log返回wyoming.polygon.outline的类型会简单的打印出boolean。

console.log(typeofwyoming.polygon.outline);

上面代码实际的输出是object,这是因为outline不是一个简单的boolean类型,而是ConstantProperty常量属性实例。实际上,在整个教程中,一直使用一种称为隐式属性转换的简写形式,在本例中,隐式属性转换自动获取原始值并为我们创建相应的ConstantProperty实例。如果没有简写的话,我们将不得不编写一个更长的初始化示例版本(为了简单起见,省略了位置):

varwyoming=newCesium.Entity();wyoming.name='Wyoming';varpolygon=newCesium.PolygonGraphics();polygon.material=newCesium.ColorMaterialProperty(Cesium.Color.RED.withAlpha(0.5));polygon.outline=newCesium.ConstantProperty(true);polygon.outlineColor=newCesium.ConstantProperty(Cesium.Color.BLACK);wyoming.polygon=polygon;viewer.entities.add(wyoming);

但是这些属性为什么会存在呢?原因是简单的,整个Entity API不仅有表达常量值的能力,而且还有随着时间改变的值。

所有属性完成了Property(属性)接口,在cesium中有很多不同种类的属性。本教程的第二部分将关注属性系统,并使用其创建动画和其它时间序列的可视化。目前为止,我们必须知道的事是为了读取属性的值,我们需要调用getValue函数。所以为了从多边形中得到轮廓属性,我们需要调用下面的代码,其时间是当前场景时间。

console.log(wyoming.polygon.outline.getValue(viewer.clock.currentTime));

从技术上讲,如果我们确定要查询的属性是ConstantProperty,可以避免传递时间,但是通常指定它是一种很好的实践。

(10)下一步工作(Where to go from here)

目前所了解的cesium中处理空间数据仅仅是皮毛,但是这已经展现出巨大的能力。当你在等待本教程的第二部分时,可能对Cesium的Imagery Layers和Terrain and Water感兴趣。往常一样,可以先查看Cesium教程的列表来决定下一步喜欢的学习内容。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。