Ceisum官方教程2 -- 項目實例(workshop)

原文地址:https://cesiumjs.org/tutorials/Cesium-Workshop/javascript

概述

咱們很高興歡迎你加入Cesium社區!爲了讓你能基於Cesium開發本身的3d 地圖項目,這個教程將從頭至尾講解一個基礎的Cesium程序的開發過程。這個教程將用到不少重要的CesiumAPI,可是並非全部的(CesiumJS有不少不少功能)。咱們目標是教會你基於Cesium作開發的基本原則和工具,在你的項目裏能觸類旁通,解決其餘問題。css

咱們建立一個簡單的程序去可視化紐約市的一些地理位置。咱們將加載各類類型各類樣式的二維和三維數據,而且建立若干個相機位置,而且展現一些用戶交互的UI。最後,作爲一個高科技地圖,咱們加載了一個無人機三維模型,充分利用3d可視化的優點去觀察一些地理位置。html

在完成教程後,你對Cesium的功能會有幾個基本概念,包括配置viewer、加載數據、建立各類樣式的幾何體、使用3d tiles(三維模型切片)、控制相機、增長鼠標交互事件。java


 
帶交互的可視化紐約城地理位置

步驟

再開發前的幾個必備步驟:node

  1. 訪問這個頁面確認你的電腦環境適合Cesium Cesium Viewer. 若是沒有看到地球? 點這個連接 Troubleshooting.
  2. 安裝Node.js.
  3. 下載教程代碼 workshop code。使用git clone 或者手動下載zip並解壓縮。
  4. 在cmd命令行下,使用cd命令定位到 cesium-workshop目錄下.
  5. 運行 npm install
  6. 運行 npm start

控制檯應該輸出下面信息:react


 
workshop運行
Cesium development server running locally.  Connect to http://localhost:8080 

注意不能關閉控制檯窗口,開發中須要保證這個進程運行着。webpack

下一步, 在瀏覽器裏打開 localhost:8080。你應該能看到咱們的程序已經運行了。git

注意

這個教程裏提到的workshop是基於cesium1.45開發的,裏面的地形服務器已經失效了,致使cesium加載並不成功,使用這個代碼看不到效果。github


 
workshop已經運行不起來了

解決方法也很簡單,咱們使用Cesium最新版1.51裏的文件替換到以下目錄web

 
替換cesium庫

再次刷新頁面,就能夠了,效果以下:

 
替換cesiumjs庫後的加載效果

程序目錄

在程序根目錄下,有以下文件和文件夾. 這個程序已經被設計爲儘量的簡單,只包含cesiumjs的庫。

  • Source/ : 咱們項目的代碼。
  • ThirdParty/ : 外部js庫,目前只包含cesium。
  • LICENSE.md : 咱們項目的說明條款。
  • index.html : 主頁,包含項目程序代碼和頁面結構。
  • server.js : 簡單的基於nodejs的http服務器。

CesiumJS是徹底兼容現代javascript 庫和框架,因此放心大但的使用。
下面是一些示例:

頁面結構

下來咱們看看index.html。爲cesium的控件建立div,以及一些輸入元素。咱們注意到,Cesium的控件就是一個普通的div,它能夠被css樣式設置,而且和其餘div交互。
有一些關鍵的行:

引入CesiumJS

受限在html的標籤內引用cesium.js。這個定義了Cesium對象,而且包含整個CesiumJS的庫。

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

爲了減少開發的項目最終的js文件大小,固然你也能夠包含ThirdParty/Cesium/Source/目錄下的獨立的Cesium源碼模塊。不過咱們爲了簡單的測試API,咱們直接包含了整個CesiumJS庫。

HTML結構

在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默認的控件樣式。

工做流程

步驟以下:

  1. 使用你最擅長的文本編輯器(推薦sublime)打開 Source/App.js,而且清空裏面內容。
  2. 把文件Source/AppSkeleton.js的內容拷貝到 Source/App.js
  3. 確認你的http服務還在 cesium-workshop 目錄運行着。
  4. 使用你的瀏覽器打開 localhost:8080.推薦使用chrome,可是如今瀏覽器均可以. 你應該能看到一個黑色背景。
  5. 在代碼裏去掉註釋,保存 Source/App.js,刷新瀏覽器,應該有些效果改變了。

還有問題? 那你先跟着sandcastle去作一個沒有UI的簡單程序:

下來咱們真正開始。

建立Viewer

Cesium的最基礎對象就是 Viewer, 一個具備不少功能的3d地球的黑盒子. 使用下面的代碼建立viewer並附着到id爲 "cesiumContainer"`的div上。

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

這簡單的一行代碼實際包含了不少內容,成功後你應該能看見基礎的地球,像下面同樣:


 
基礎地球

默認狀況下這個場景能處理鼠標和觸摸事件。 試下下面的相機控制方法:

左鍵單擊和拖拽 - 沿着地球表面平移(調整相機位置).
右鍵單擊和拖拽 - 相機放大縮小(調整相機距離).
滾輪 - 相機放大縮小(調整相機距離).
中間按下和拖拽 - 圍繞地球表面旋轉相機(調整相機方向)。

除了地球, Viewer還默認包含了一些有用的控件:


 
Cesium控件
  1. Geocoder : 地理位置查詢定位控件,默認使用bing地圖服務.
  2. HomeButton : 默認相機位置。
  3. SceneModePicker : 3D、2D和哥倫布模式的切換按鈕.
  4. BaseLayerPicker : 選擇地形、影像等圖層。
  5. NavigationHelpButton : 顯示默認的相機控制提示.
  6. Animation : 控制場景動畫的播放速度.
  7. CreditsDisplay : 展現數據版權屬性。
  8. Timeline : 時間滾動條。
  9. 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提供了多種影像數據來源 多用影像數據源

支持的格式:

  • WMS
  • TMS
  • WMTS (with time dynamic imagery)
  • ArcGIS
  • Bing Maps
  • Google Earth
  • Mapbox
  • Open Street Map

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須要在服務端預先把地形數據處理爲切片形式,在客戶端基於當前相機位置去請求和渲染地形切片。
下面是一些示例和地形數據集以及配置選項:

支持的格式:

  • Quantized-mesh, Cesium團隊定義的不規則地形三角網格式。
  • Heightmap
  • Google Earth Enterprise
    爲了增長一個地形數據,咱們須要建立一個 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 }); 

requestWaterMaskrequestVertexNormals 兩個選項都是可選的,他們告知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 :
    在ENU(East-North-Up)座標系中,相對座標軸的旋轉(弧度)。Heading 相對負z軸(垂直向下). Pitch 相對負y軸. Roll相對正x軸.
  • Quaternion :使用四維座標描述的三維旋轉。
    這是在Cesium的scene中擺放對象的基本類型,Cesium提供了一系列的方便的轉換函數。具體請查看cesium文檔。

如今,咱們把相機定位到咱們數據所在的位置--紐約。

相機控制

Cameraviewer.scene的一個屬性,用來控制當前可見範圍。使用Cesium Camera API 咱們能夠直接設置相機的位置和朝向。

一些最經常使用的方法:

更詳細的能夠去學習下面兩個示例:

// 建立相機初始位置和朝向 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); // 設置時間的可見範圍 

上述代碼設定了場景動畫播放速率,開始和結束時間,而且設置爲循環播放。而且設置了時間線控件在合適的時間範圍。使用這個 示例 去試驗更多時間設置
初始化配置完成了,當你運行代碼,能看到以下效果

 
初始化程序

Entities加載和樣式配置

上面咱們程序裏已經添加了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,cameracanvas 選項必需要配置。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點默認都是使用 BillboardsLabels 顯示, 根據下面的代碼來修改某些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; } 

最後效果:


 
修改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 }); 
 
平滑的飛行路徑

3D Tiles

咱們的團隊有時候描述Cesium像一個使用真實世界數據的三維遊戲引擎。但是,加載真實世界的數據要比遊戲引擎的數據困難不少,主要由於真實數據有很是高得分辨率,並且要求精確得可視化。幸虧,Cesium和開源社區合做開發了3D Tiles格式。它是一個流式載入海量各類類型得空間三維數據的 開放協議

使用一種相似Cesium的地形和影像數據切片技術,3d tiles格式使原先那些不可能作可視化交互的大模型數據可以展現出來,包括建築物數據、CAD(或者BIM)模型,點雲,傾斜模型。

  • 3D Tiles 調試器 ,它是一個可以查看各類3d tile後臺信息的調試工具。

這是一些不一樣類型的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 : 返回一條射線和地形的相交位置點。

這是一些示例:

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內置的跟隨函數,確保相機一直居中觀察無人機。這種模式下,即使對象是移動的,相機也能和目標之間保持一個固定的偏移距離。只須要簡單的設置
    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中,或者經過設置EntityCollectionshow 屬性來控制。只須要設置一次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開發事業,咱們鼓勵你訪問下面的資源:

  • 官方文檔 : 完整的CesiumAPI文檔,包含一些示例代碼
  • Sandcastle :一個所見即所得的編碼環境,包含大量的代碼示例.
  • 官方教程 :詳細描述基於Cesium開發的方方面面。
  • 官方論壇 : Cesium相關問題的主要討論平臺.
  • Cesium實驗室 : 中國最專業的Cesium的討論學習QQ羣:595512567。
    一旦有問題,上面的資源可能有你的答案。

在cesiumjs.org展現你的項目

咱們很樂意去分享全部Cesium社區建立的酷炫項目。遍及世界的的開發者建立了不少有意思的咱們歷來沒考慮過的項目。一旦你的項目準備分享給全世界,請跟咱們聯繫放到[CesiumJS示例頁面] (https://cesiumjs.org/demos)。具體請閱讀 這個博客提交你的項目示例。

 
中國最專業的Cesium開發者社區
相關文章
相關標籤/搜索