Cesium工作车间教程

admin0条评论 8,910 次浏览

概观

欢迎来到铯社区!我们很高兴有你加入我们。为了帮助您开发自己的Web地图应用程序,本教程将引导您逐步开发一个简单的Cesium应用程序。本教程将涉及CesiumJS API的许多重要方面,但并不完整(CesiumJS有很多功能!)。我们的目标是介绍探索Cesium其余部分所需的基础知识和工具。

我们将创建一个简单的应用程序,用于可视化纽约市的示例geocache位置。我们将加载和设置多种类型的2D和3D数据,并创建多个相机和显示选项,用户可以交互设置。最后,作为高科技的geocachers,我们将加载3D无人机模型来侦察geocache位置并充分利用我们的3D可视化。

在本教程结束时,您将对Cesium的功能进行工作概述,包括配置Cesium查看器,加载数据集,创建和设置几何体,使用3D Tiles,控制摄像头以及向应用程序添加鼠标交互性。

完成的应用程序我们的应用程序交互式可视化示例geocache位置。

建立

只需几个设置步骤,然后才能开发。

  1. 通过访问Cesium Viewer确保您的系统兼容Cesium。没有地球仪?请参阅故障排除
  2. 安装Node.js的
  3. 获取车间代码。克隆或下载zip并提取内容。
  4. 在您的控制台中,导航到根cesium-workshop目录。
  5. 运行npm install
  6. 运行npm start

控制台应该告诉你“Cesium开发服务器在本地运行。连接到http:// localhost:8080 /“。不要关闭控制台!我们需要保持这个过程运行。

接下来,在浏览器中导航到localhost:8080。您应该看到我们的研讨会应用程序正在运行。卡住?该入门教程去更多的了解CesiumJS安装深度。

应用程序目录

现在浏览我们的应用程序目录!请注意,此应用程序目录设计得尽可能简单,只包含CesiumJS库。铯与所有现代JavaScript框架兼容,所以请随时尝试!

  • 来源:我们的应用程序代码和数据。
  • ThirdParty:外部库,在这种情况下只是CesiumJS库。
  • LICENSE.md:此应用程序的使用条款。
  • index.html:我们的主要html页面。
  • server.js:我们将运行我们的应用程序的服务器。

现在看看index.html。这div为我们的Cesium小部件和几个基本输入元素创建了一个。注意到Cesium Widget只是一个普通的div,可以像任何其他样式一样进行风格和互动div

有几条重要线路可以设置:

首先,我们Cesium.js在HTML头中包含一个脚本标签。这定义了Cesium包含整个CesiumJS库的对象。

<script src="ThirdParty/Cesium/Cesium.js"></script>

Cesium附带一组需要此CSS的小部件。

<style>@import url(ThirdParty/Cesium/Widgets/widgets.css);</style>

在HTML正文中,我们div为Cesium Viewer小部件创建了一个。

<div id="cesiumContainer"></div>

最后,在另一个脚本标签中,我们在HTML主体的末尾添加了应用程序的JavaScript。

<script src="Source/App.js"></script>

就是这样!该文件中的其余部分HTML用于收集用户输入,我们将在稍后使用。

工作流程

遵循本教程:

  1. Source/App.js在您最喜欢的文本编辑器中打开并删除内容。
  2. 将内容复制Source/AppSkeleton.jsSource/App.js
  3. 确保您的服务器仍在cesium-workshop目录中运行,如安装程序中所述
  4. 打开您最喜欢的网络浏览器并导航到localhost:8080。我们大多数人使用Chrome,但Cesium可以在所有现代Web浏览器中运行。你现在应该看到一个大部分是黑色的页面
  5. 在本教程指导您时,取消注释代码,保存Source/App.js并刷新页面以查看反映的新更改。

真的卡住了吗?您可以使用应用程序的简化版本(无UI)在沙堡中进行关注:

现在让我们开始吧!

创建查看器

任何Cesium应用程序的基础都是Viewer一个开箱即用的多功能交互式3D地球仪。div通过取消注释第一行,将查看器添加到’cesiumContainer’ 。

var viewer = new Cesium.Viewer('cesiumContainer');

这一行中包含很多内容!你应该看到这样一个基本的地球仪:

铯浏览器

默认情况下,场景处理鼠标和触摸输入。尝试使用默认相机控件探索全球:

  • 左键单击并拖动 – 将相机移到地球表面。
  • 右键单击并拖动 – 放大和缩小相机。
  • 中间轮滚动 – 也放大和缩小相机。
  • 中点击并拖动 – 围绕地球表面上的点旋转摄像机。

除了地球本身之外,查看器默认还附带一些有用的小部件,标注在上图中。

  1. Geocoder:将相机飞行到查询位置的位置搜索工具。默认情况下使用Bing地图数据。
  2. HomeButton :将查看器返回到默认视图。
  3. SceneModePicker :在3D,2D和哥伦布视图(CV)模式之间切换。
  4. BaseLayerPicker :选择要在地球上显示的图像和地形。
  5. NavigationHelpButton :显示默认的相机控件。
  6. Animation :控制视图动画的播放速度。
  7. CreditsDisplay:显示数据归属。几乎总是需要!
  8. Timeline :指示当前时间并允许用户使用洗涤器跳至特定时间。
  9. FullscreenButton :使查看器全屏。

我们可以将我们的查看器配置为包含或排除这些功能,以及在创建选项对象时作为参数传入。对于这个应用程序,删除第一行并通过取消注释接下来的几行来配置新的查看器:

var viewer = new Cesium.Viewer('cesiumContainer', {
    scene3DOnly: true,
    selectionIndicator: false,
    baseLayerPicker: false
});

这将创建一个没有选择指示器,基础图层选择器或场景模式选择器小部件的查看器,因为这些对于我们的应用程序来说是不必要的。有关全套Viewer选项,请参阅Viewer文档。

添加图像

我们的Cesium应用程序的下一个关键元素是图像。这是以各种分辨率在我们的虚拟地球上铺设的一组图像。为了提供最佳性能,Cesium只请求和渲染在当前视图中可见并且分辨率(也称为缩放级别)适合相机与地球表面和地球仪距离的图像块maximumScreenSpaceError

铯提供了许多用于处理图像层的方法,例如颜色调整和图层混合。一些代码示例:

Cesium为许多不同提供商提供的图像支持。

支持的图像格式:

  • WMS
  • TMS
  • WMTS(带时间动态图像)
  • ArcGIS中
  • Bing地图
  • 谷歌地球
  • Mapbox
  • 打开街道地图

默认情况下,Cesium将Bing地图用于图像。请注意,不同的数据提供商有不同的归因要求 – 确保您有权使用特定提供商提供的数据,并使用信用选项来相应地归因这些来源。与Viewer一起打包的图像主要用于演示目的。Bing地图要求您创建一个帐户并生成访问密钥以使用图像。在本演示中,我们将使用铯离子上的Sentinel-2图像。

查看ion Assets Sandcastle选项卡以获取可用离子资产的列表。

首先,我们创建一个IonImageryProvider,传入assetId对应于Sentinel-2图像的那个。然后我们添加ImageryProviderviewer.imageryLayers

// Remove default base layer
viewer.imageryLayers.remove(viewer.imageryLayers.get(0));

// Add Sentinel-2 imagery
viewer.imageryLayers.addImageryProvider(new Cesium.IonImageryProvider({ assetId: 3954 }));

通过上面的代码添加,我们的应用程序在放大时应该看起来像这样:

意象

有关图像的更多信息,请参阅我们的图像图层教程

添加地形

铯支持对海洋,湖泊和河流的全球高分辨率地形和水影响进行流式传输和可视化。与2D地图相比,山峰,山谷和其他地形特征确实显示出3D地球的优势。与图像类似,Cesium引擎将流式传输来自服务器的地形数据,仅根据当前摄像头位置请求和渲染瓦片。

以下是terrain数据集和配置选项的一些演示:

支持的地形格式:

  • 量化网格,由Cesium团队开发的一种开放格式
  • 高度图
  • Google地球企业版

为了添加地形数据,我们创建一个CesiumTerrainProvider,指定一个url和一些配置选项,然后分配提供者viewer.terrainProvider。在这种情况下,我们可以使用createWorldTerrain助手函数来创建铯离子上的Cesium World Terrain tileset。

// Load Cesium World Terrain
viewer.terrainProvider = Cesium.createWorldTerrain({
    requestWaterMask : true, // required for water effects
    requestVertexNormals : true // required for terrain lighting
});

requestWaterMask并且requestVertexNormals是配置选项,它告诉Cesium为水和照明效果请求额外的数据。默认情况下,这些设置为false。

最后,现在我们已经有了地形,我们只需要再多一行来确保地形背后的物体被正确遮挡。只有最前面的对象才可见。

// Enable depth testing so things behind the terrain disappear.
viewer.scene.globe.depthTestAgainstTerrain = true;

我们现在有地形和生气的水。纽约非常平坦,所以您可以随时探索,以便观看新的地形。举一个特别明显的例子,您可以导航到更加崎岖的地区,如大峡谷或旧金山。

地形

有关地形的更多信息,请参阅地形教程

配置场景

下一步是添加一些更多的设置,以便在正确的位置和时间启动我们的观众。这涉及与viewer.scene我们的查看器中控制所有图形元素的类进行交互。

首先,我们可以配置我们的场景,以使用此线根据太阳位置启用照明。

// Enable lighting based on sun/moon positions
viewer.scene.globe.enableLighting = true;

这将使我们的场景中的照明随着时间的变化而变化。如果你缩小,你会看到地球的一部分是黑暗的,因为太阳已经在世界的这个地方。

接下来,在我们开始设置初始视图之前,让我们回顾几种基本的Cesium类型:

  • Cartesian3 :3D笛卡尔坐标 – 当用作位置时,它使用地球固定框架(ECEF)以米为单位,相对于地球的中心,
  • Cartographic :由经度,纬度(以弧度表示)和WGS84椭球表面的高度定义的位置
  • HeadingPitchRoll:围绕East-North-Up框架中的局部轴旋转(以弧度表示)。标题是围绕负z轴的旋转。间距是围绕负y轴的旋转。Roll是围绕正x轴的旋转。
  • Quaternion :表示为4D坐标的3D旋转。

这些是在场景中定位和定位铯对象所需的基本类型,并且有许多有用的转换方法。请参阅每种类型的文档以了解更多信息。

现在让我们定位相机,在纽约观看我们的数据所在的场景。

相机控制

Camera是一个属性,viewer.scene 并控制当前可见的内容。我们可以通过直接设置相机的位置和方向来控制相机,或者使用Cesium相机API来设置相机的位置和方向。

一些最常用的方法是:

要了解API的功能,请查看这些相机演示:

让我们通过将相机移动到纽约来尝试其中一种方法。camera.setView()使用a Cartesian3和a 设置初始视图HeadingPitchRoll的位置和方向:

// Create an initial camera view
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
    }
};
// Set the initial view
viewer.scene.camera.setView(homeCameraView);

摄像机现在定位并定位为俯视曼哈顿,我们的视图参数保存在可以传递给其他摄像机方法的对象中。

事实上,我们可以使用相同的视图来更新按下主页按钮的效果。我们可以重写按钮,让我们看到曼哈顿的初始视图,而不是让我们远离全球的默认视图。我们可以通过添加更多选项来调整动画,然后添加取消默认航班的事件侦听器,并调用flyTo()我们的新主视图:

// Add some camera flight animation options
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);
});

有关基本相机控制的更多信息,请查看我们的相机教程

时钟控制

接下来,我们可以配置观看者Clock 并Timeline控制场景中的时间流逝。

这里是时钟API的行动

在特定时间工作JulianDate时,Cesium 使用这种类型,它存储自公元前4712年1月1日(公元前4713年)中午以来的天数。为了提高精度,此类将日期的整数部分和日期的秒部分存储在单独的组件中。为了算术安全并代表闰秒,日期总是存储在国际原子时间标准中。

以下是我们如何设置场景时间选项的示例:

// Set up clock and timeline.
viewer.clock.shouldAnimate = true; // make the animation play when the viewer starts
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; // sets a speedup
viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER; // tick computation mode
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // loop at the end
viewer.timeline.zoomTo(viewer.clock.startTime, viewer.clock.stopTime); // set visible range

这将设置场景动画的速率,开始时间和停止时间,并告知时钟在到达停止时间时回到起点。它还将时间线小部件设置为适当的时间范围。查看这个时钟示例代码来测试时钟设置。

这就是我们最初的场景配置!现在,当您运行应用程序时,您应该看到以下内容:

初始申请

加载和样式实体

现在我们已经为查看器配置,图像和地形设置了应用程序的舞台,我们可以添加应用程序的主要焦点 – 示例geocache数据。

为了便于观看,Cesium支持流行的矢量格式GeoJson和KML,以及我们开发的专门用于描述Cesium中称为CZML的场景的开放格式。

无论初始格式如何,Cesium中的所有空间数据均使用Entity API表示。Entity API以Cesium渲染高效的格式提供灵活的可视化。铯Entity是一种数据对象,可以与样式化的图形表示进行配对,并定位在空间和时间上。沙堡画廊提供了许多简单实体的例子。要快速了解实体API的基础知识,请从此应用程序暂停并阅读“ 可视化空间数据”教程。

以下是不同实体类型的示例:

一旦你掌握了Entity看起来像什么,使用Cesium加载数据集将会很容易理解。要读取数据文件,请创建一个DataSource适合您的数据格式,该数据格式将解析托管在指定网址的数据文件,并为数据集中的每个地理空间对象创建一个EntityCollection包含an Entity的数据文件。DataSource只是定义了一个接口 – 您需要的确切类型的数据源将取决于数据格式。例如,KML使用a 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(optinos)几个选项从KML文件中读取我们的示例geocache点。对于KmlDataSource,需要相机和画布选项。该clampToGround选项支持地面夹紧,这是一种流行的显示选项,可使地形几何实体如多边形和椭圆符合地形而不是曲线到WGS84椭球表面。

由于该数据是异步加载,这将返回Promise 到KmlDataSource其将持有我们所有新创建的实体。

如果您对Promise使用异步函数的API 不熟悉,那么这里的“异步”基本上意味着您应该在提供的回调函数中执行需要处理的数据.then。为了实际添加这个实体集合到场景中,我们必须等到承诺解决,然后添加KmlDataSourceviewer.datasources。取消注释以下几行:

// 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 与实体相关的元数据,然后双击放大并查看实体。要停止查看实体,请单击主页按钮或单击信息框上的划掉的相机图标。接下来,我们将着手添加自定义样式来改善应用程序的外观。

对于KML和CZML文件,声明式样式可以内置到文件中。但是,对于这个应用程序,我们练习手动设置我们的实体样式。为此,我们将 通过等待我们的数据源加载,然后遍历数据源集合中的所有实体以及修改和添加属性来采用类似的方法来处理这个样式示例。我们的藏宝点标记创建为 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示例

接下来,让我们改进我们的Infobox每个地理标记实体。信息框的标题是实体名称,内容是实体描述,显示为HTML。

你会注意到默认的描述不是很有帮助。由于我们正在显示地理标记位置,因此我们将其更新为显示我们观测点的经度和纬度。

首先,我们将实体的位置转换为制图,然后从制图中读取经度和纬度,并将其添加到HTML表格中的描述中。

点击后,我们的geocache点实体现在将显示一个很好的格式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
            var description = '<table class="cesium-infoBox-defaultTable cesium-infoBox-defaultTable-lighter"><tbody>';
            description += '<tr><th>' + "Longitude" + '</th><td>' + longitude + '</td></tr>';
            description += '<tr><th>' + "Latitude" + '</th><td>' + latitude + '</td></tr>';
            description += '</tbody></table>';
            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);
});

让我们来设计我们加载的邻域多边形。就像我们刚刚做的广告牌造型一样,一旦dataSource加载完成,我们首先遍历相邻的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将GeoJson属性存储在下面entity.properties,所以我们可以像这样设置邻域名称:

// entity styling code here

// Use geojson neighborhood value as entity name
entity.name = entity.properties.neighborhood;

我们可以ColorMaterialProperty 通过将材质设置为随机来为每个多边形指定一个新的颜色,而不是让所有邻域保持相同的颜色Color

// 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为每个实体生成一个基本的样式选项。为了保持整洁,我们可以disableDepthTestDistance 让Cesium总是在任何3D对象的前面渲染标签。

但是请注意,标签始终位于entity.position。A Polygon由于具有定义多边形边界的位置列表而创建,其位置未定义。我们可以通过获取多边形位置的中心来生成一个位置:

// 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是Cesium KML是Google Earth的一个标准格式,它允许大多数Cesium特性通过声明式样式语言来使用(在这种情况下是JSON模式)

我们的CZML文件定义了一个实体(默认可视化为点),并将其位置定义为不同时间点的一系列位置。Entity API中有几个属性类型可用于处理动态数据。请参阅下面的演示示例:

// 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使用a显示无人机航班Path,实体随时间显示其位置。一条路径将离散样本连接成一条连续的线,以使用插值法进行可视化。

最后,让我们改进无人机飞行的外观。首先,我们可以加载一个3D模型来表示我们的无人机并将其附加到实体上,而不是放在一个简单的点上。

Cesium支持基于glTF(GL传输格式)加载3D模型,这是Cesium 团队与Khronos团队联合开发的一个开放规格,可通过最小化文件大小和运行时处理来有效地加载应用3D模型。没有glTF模型?我们提供在线转换器, 用于将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 : 2000
    };
});

现在,我们的模型看起来不错,但与原始点不同,无人机模型具有朝向,当无人机向前移动时无法转动,这看起来很奇怪。幸运的是,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
});

飞行路径

3D瓷砖

我们的团队有时会将Cesium描述为真实世界数据的3D游戏引擎。然而,使用真实世界的数据比使用典型的视频游戏资产要困难得多,因为真实的数据可能具有令人难以置信的高分辨率,并且需要准确的可视化。幸运的是,Cesium与开源社区合作开发了3D Tiles,这是一个用于流式传输大规模异构3D地理空间数据集的开放式规范

使用概念上类似于Cesium地形和图像流技术的技术,3D Tiles可以查看巨大的模型,包括建筑数据集,CAD(或BIM)模型,点云和摄影测量模型,否则这些模型将无法以交互方式查看。

以下是一些展示不同格式的3D Tiles演示:

在我们的应用程序中,我们将Cesium3DTileset通过显示纽约所有建筑物的完整3D模型来为我们的可视化添加真实感!纽约市的这个瓷砖位于Cesium Ion,我们可以使用它来添加它IonResource.fromAssetId

// Load the NYC buildings tileset
var city = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(3839) }));

您可能会注意到建筑物在地平面上没有正确定位。幸运的是它很容易修复。我们可以通过修改它来调整tileset的位置modelMatrix

我们可以通过将瓦片集的边界球转换为a Cartographic,然后添加所需的偏移量并重新设置modelMatrix

// 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造型语言对我们的tileset的部分样式进行设计。三维瓦片样式定义表达式以评估颜色(RGB和半透明度)并显示瓦片集Cesium3DTileFeature的一部分(例如城市中的单个建筑物)的属性。样式通常基于存储在图块批处理表中的要素属性。特征属性可以是任何类似高度,名称,坐标,建筑日期等,但内置于瓷砖资产中。样式是用JSON定义的,而表达式则是用一小部分JavaScript来增加样式。另外,造型语言提供了一组内置函数来支持常见的数学运算。

Cesium3DTileStyle是这样定义的:

var defaultStyle = new Cesium.Cesium3DTileStyle({
    color : "color('white')",
    show : true
});

这种风格只会使我们纽约市瓷砖中的所有建筑变成白色并始终可见。为了实际设置tileset使用这种风格,我们设置city.style

city.style = defaultStyle;

三维瓷砖造型

我们可以根据需要定义多种样式。这是另一个,使建筑透明:

var transparentStyle = new Cesium.Cesium3DTileStyle({
    color : "color('white', 0.3)",
    show : true
});

3D瓷砖上的透明造型

将相同的样式应用于我们的tileset中的每个功能只是抓住表面。我们也可以使用特定于每个特征的属性来确定样式。以下是一个根据高度对建筑物进行着色的示例:

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演示

3D瓷砖演示:

如果您有数据并需要帮助将其转换为3D瓦片,请继续关注有关铯离子平台的更新! 在这里订阅更新

互动

最后,让我们添加一些鼠标交互性。为了提高我们的地理标记标记的可见性,我们可以在用户将鼠标悬停在标记上突出显示时更改它们的样式。

为了达到这个目的,我们将使用picking,这是一个Cesium特性,它在查看器画布上给出像素位置的情况下返回3D场景中的数据。

有几种不同类型的采摘。

  • Scene.pick :在给定的窗口位置返回一个包含原语的对象。
  • Scene.drillPick :返回包含给定窗口位置所有基元的对象列表。
  • Globe.pick :返回给定光线与地形的交点。

以下是一些采摘行动的例子:

由于我们希望突出显示效果在悬停时触发,因此首先我们需要创建一个鼠标操作处理程序。为此,我们将使用一ScreenSpaceEventHandler组处理程序来触发用户输入操作的指​​定功能。 ScreenSpaceEventHandler.setInputAction()](/Cesium/Build/Documentation/ScreenSpaceEventHandler.html#setInputAction) listens for a type of user action -- a [ScreenSpaceEventType`,并运行一个特定的函数,将用户操作作为参数传入。在这里,我们将传递一个以移动为输入的函数:

var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function(movement) {}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

接下来我们来编写我们的高亮功能。处理程序将传递一个鼠标移动,从中我们可以提取窗口位置来使用pick()。如果选择返回广告牌对象,我们知道我们正在标记上方盘旋。然后,使用我们了解的Entity样式,我们可以应用高亮样式。

// If the mouse is over a point of interest, change the entity billboard scale and color
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);

这成功触发标记的高亮样式更改。但是,您会注意到当我们移动光标时标记保持突出显示。我们可以通过跟踪突出显示的最后一个标记并恢复原始样式来解决这个问题。

这是完整的功能,标记突出显示和非高亮显示工作:

// If the mouse is over a point of interest, change the entity billboard scale and color
var previousPickedEntity = undefined;
handler.setInputAction(function(movement) {
    var pickedPrimitive = viewer.scene.pick(movement.endPosition);
    var pickedEntity = (Cesium.defined(pickedPrimitive)) ? pickedPrimitive.id : undefined;
    // Unhighlight the previously picked entity
    if (Cesium.defined(previousPickedEntity)) {
        previousPickedEntity.billboard.scale = 1.0;
        previousPickedEntity.billboard.color = Cesium.Color.WHITE;
    }
    // Highlight the currently picked 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);

而已!我们现在已经成功添加了一个鼠标移动处理程序,并针对标记实体的悬停行为。

标记实体

相机模式

为了炫耀我们的无人机飞行,让我们试验一下相机模式。用户可以在两种基本相机模式之间切换,我们将保持简单。

  • 自由模式:默认的相机控制
  • 无人机模式:让相机在无人机的固定距离通过其飞行

自由模式不需要代码,因为它使用默认控件。至于无人机跟踪模式,我们可以使用观察者内置的实体跟踪功能将摄像头定位在无人机上。这将相机设置为与指定实体相距固定偏移量,即使它移动。要跟踪一个实体,我们只需设置viewer.trackedEntity

要切换回免费相机模式,我们可以将其设置viewer.trackedEntity回未定义状态,然后使用camera.flyTo()返回到我们的主视图。

以下是相机模式功能:

// Create a follow camera by tracking the drone entity
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');

// Create a follow camera by tracking the drone entity
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);

最后,当用户双击它们时,会自动跟踪实体。如果用户通过点击开始跟踪无人机,我们可以添加一些处理来自动更新UI:

viewer.trackedEntityChanged.addEventListener(function() {
    if (viewer.trackedEntity === drone) {
        freeModeElement.checked = false;
        droneModeElement.checked = true;
    }
});

这就是我们的两种相机模式 – 我们现在可以自由切换到无人机相机视图,如下所示:

无人机模式

附加功能

其余的代码只是增加了一些额外的可视化选项。与我们先前与HTML元素的交互类似,我们可以附加侦听器函数以切换阴影和邻域多边形可见性。

我们首先创建一个切换邻域多边形的简单方法。一般来说,我们可以通过设置可见性来隐藏实体。Entity.show 但是,这只会为单个实体设置可见性,并且我们想要一次隐藏或显示所有邻居实体。

我们可以通过将所有邻居实体添加到父实体来完成此操作,如此示例中所示, 或通过简单使用show属性来完成EntityCollection。然后,我们可以通过更改以下值来设置所有子实体的可见性neighborhoods.show

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可能不会立即加载,因此我们也可以添加一个加载指示符,该指示符仅在tileset加载(并且因此承诺已解决)时才会被删除。

// Finally, wait for the initial city to be ready before removing the loading indicator.
var loadingIndicator = document.getElementById('loadingIndicator');
loadingIndicator.style.display = 'block';
city.readyPromise.then(function () {
    loadingIndicator.style.display = 'none';
});

下一步

恭喜!您已成功完成您的CesiumJS应用程序!随意探索并试验我们在这里提供的代码,以进一步开展您的Cesium教育。我们很高兴地欢迎您加入Cesium社区,并期待看到您使用CesiumJS库构建的惊人应用程序。

下一个是什么?

开发资源

对于本教程以及您在Cesium开发事业的其余部分,我们鼓励您依靠以下资源:

  • 参考文档:包含许多代码片段的Cesium API的完整指南。
  • Sandcastle:一个带有大量代码示例的实时编码环境。
  • 教程:铯开发领域的详细介绍。
  • 铯论坛:提出和回答铯相关问题的资源。

任何时候你陷入困境,赔率是其中的一个资源会有你正在寻找的答案。

展示您在cesiumjs.org上的工作

我们喜欢共享Cesium社区构建的所有令人难以置信的应用程序。世界各地的开发者创造出比我们想象中更有趣的应用程序!一旦您的应用程序准备好与世界分享,请联系我们,在CesiumJS演示页面上展示您的应用程序。 有关提交您的应用程序展示的信息,请参阅此博客文章

发现和处理铯离子的内容

铯离子是由Web服务和工具组成的全新商业平台,以补充CesiumJS的可视化,创建完整的3D地图绘制平台。Cesium团队对ion的特别兴奋,因为它是我们的第一款围绕CesiumJS构建的产品,用于支持开源CesiumJS。

我们对ion的愿景包括订阅

  • 从开放数据和商业数据提供商策划的3D内容,如图像,地形,3D tilesets和glTF模型;
  • 3D拼贴和托管自己的海量数据集,如图像,地形,摄影测量,点云,BIM,CAD,3D建筑物和矢量数据;
  • 分析,例如测量工具,体积和可视性计算以及地形轮廓; 和
  • 制作和分享制作3D地图的工作流程,无需编码。

cesium.com上了解更多信息

快乐发展!


分类目录