原文地址:https://cesiumjs.org/tutorials/Cesium-Workshop/javascript
咱們很高興歡迎你加入Cesium社區!爲了讓你能基於Cesium開發本身的3d 地圖項目,這個教程將從頭至尾講解一個基礎的Cesium程序的開發過程。這個教程將用到不少重要的CesiumAPI,可是並非全部的(CesiumJS有不少不少功能)。咱們目標是教會你基於Cesium作開發的基本原則和工具,在你的項目裏能觸類旁通,解決其餘問題。css
咱們建立一個簡單的程序去可視化紐約市的一些地理位置。咱們將加載各類類型各類樣式的二維和三維數據,而且建立若干個相機位置,而且展現一些用戶交互的UI。最後,作爲一個高科技地圖,咱們加載了一個無人機三維模型,充分利用3d可視化的優點去觀察一些地理位置。html
在完成教程後,你對Cesium的功能會有幾個基本概念,包括配置viewer、加載數據、建立各類樣式的幾何體、使用3d tiles(三維模型切片)、控制相機、增長鼠標交互事件。java
再開發前的幾個必備步驟:node
cesium-workshop
目錄下.npm install
。npm start
。控制檯應該輸出下面信息:react
Cesium development server running locally. Connect to http://localhost:8080
注意不能關閉控制檯窗口,開發中須要保證這個進程運行着。webpack
下一步, 在瀏覽器裏打開 localhost:8080
。你應該能看到咱們的程序已經運行了。git
這個教程裏提到的workshop是基於cesium1.45開發的,裏面的地形服務器已經失效了,致使cesium加載並不成功,使用這個代碼看不到效果。github
解決方法也很簡單,咱們使用Cesium最新版1.51裏的文件替換到以下目錄web
再次刷新頁面,就能夠了,效果以下:
在程序根目錄下,有以下文件和文件夾. 這個程序已經被設計爲儘量的簡單,只包含cesiumjs的庫。
Source/
: 咱們項目的代碼。ThirdParty/
: 外部js庫,目前只包含cesium。LICENSE.md
: 咱們項目的說明條款。index.html
: 主頁,包含項目程序代碼和頁面結構。server.js
: 簡單的基於nodejs的http服務器。CesiumJS是徹底兼容現代javascript 庫和框架,因此放心大但的使用。
下面是一些示例:
下來咱們看看index.html。爲cesium的控件建立div,以及一些輸入元素。咱們注意到,Cesium的控件就是一個普通的div,它能夠被css樣式設置,而且和其餘div交互。
有一些關鍵的行:
受限在html的標籤內引用cesium.js。這個定義了Cesium對象,而且包含整個CesiumJS的庫。
<script src="ThirdParty/Cesium/Cesium.js"></script>
爲了減少開發的項目最終的js文件大小,固然你也能夠包含ThirdParty/Cesium/Source/目錄下的獨立的Cesium源碼模塊。不過咱們爲了簡單的測試API,咱們直接包含了整個CesiumJS庫。
在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默認的控件樣式。
步驟以下:
Source/App.js
,而且清空裏面內容。Source/AppSkeleton.js
的內容拷貝到 Source/App.js
。cesium-workshop
目錄運行着。Source/App.js
,刷新瀏覽器,應該有些效果改變了。還有問題? 那你先跟着sandcastle去作一個沒有UI的簡單程序:
下來咱們真正開始。
Cesium的最基礎對象就是 Viewer
, 一個具備不少功能的3d地球的黑盒子. 使用下面的代碼建立viewer並附着到id爲 "cesiumContainer"`的div上。
var viewer = new Cesium.Viewer('cesiumContainer');
這簡單的一行代碼實際包含了不少內容,成功後你應該能看見基礎的地球,像下面同樣:
默認狀況下這個場景能處理鼠標和觸摸事件。 試下下面的相機控制方法:
左鍵單擊和拖拽
- 沿着地球表面平移(調整相機位置).
右鍵單擊和拖拽
- 相機放大縮小(調整相機距離).
滾輪
- 相機放大縮小(調整相機距離).
中間按下和拖拽
- 圍繞地球表面旋轉相機(調整相機方向)。
除了地球, Viewer還默認包含了一些有用的控件:
Geocoder
: 地理位置查詢定位控件,默認使用bing地圖服務.HomeButton
: 默認相機位置。SceneModePicker
: 3D、2D和哥倫布模式的切換按鈕.BaseLayerPicker
: 選擇地形、影像等圖層。NavigationHelpButton
: 顯示默認的相機控制提示.Animation
: 控制場景動畫的播放速度.CreditsDisplay
: 展現數據版權屬性。Timeline
: 時間滾動條。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提供了多種影像數據來源 多用影像數據源 。
支持的格式:
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須要在服務端預先把地形數據處理爲切片形式,在客戶端基於當前相機位置去請求和渲染地形切片。
下面是一些示例和地形數據集以及配置選項:
支持的格式:
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 });
requestWaterMask
和 requestVertexNormals
兩個選項都是可選的,他們告知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
:Quaternion
:使用四維座標描述的三維旋轉。如今,咱們把相機定位到咱們數據所在的位置--紐約。
Camera
是 viewer.scene
的一個屬性,用來控制當前可見範圍。使用Cesium Camera API 咱們能夠直接設置相機的位置和朝向。
一些最經常使用的方法:
Camera.setView(options)
: 當即設置相機位置和朝向。Camera.zoomIn(amount)
: 沿着相機方向移動相機。Camera.zoomOut(amount)
: 沿着相機方向遠離Camera.flyTo(options)
: 建立從一個位置到另外一個位置的相機飛行動畫。Camera.lookAt(target, offset)
: 依據目標偏移來設置相機位置和朝向。Camera.move(direction, amount)
: 沿着direction方向移動相機。Camera.rotate(axis, angle)
: 繞着任意軸旋轉相機。更詳細的能夠去學習下面兩個示例:
Cartesian3
表示位置,一個HeadingPitchRoll
表示朝向。// 建立相機初始位置和朝向 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); // 設置時間的可見範圍
上述代碼設定了場景動畫播放速率,開始和結束時間,而且設置爲循環播放。而且設置了時間線控件在合適的時間範圍。使用這個 示例 去試驗更多時間設置
初始化配置完成了,當你運行代碼,能看到以下效果
上面咱們程序裏已經添加了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,camera
和 canvas
選項必需要配置。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點默認都是使用 Billboards
和 Labels
顯示, 根據下面的代碼來修改某些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; }
最後效果:
或許把每一個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 });
咱們的團隊有時候描述Cesium像一個使用真實世界數據的三維遊戲引擎。但是,加載真實世界的數據要比遊戲引擎的數據困難不少,主要由於真實數據有很是高得分辨率,並且要求精確得可視化。幸虧,Cesium和開源社區合做開發了3D Tiles格式。它是一個流式載入海量各類類型得空間三維數據的 開放協議 。
使用一種相似Cesium的地形和影像數據切片技術,3d tiles格式使原先那些不可能作可視化交互的大模型數據可以展現出來,包括建築物數據、CAD(或者BIM)模型,點雲,傾斜模型。
這是一些不一樣類型的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
: 返回一條射線和地形的相交位置點。這是一些示例:
ScreenSpaceEventHandler
是能夠處理一系列的用戶輸入事件的處理器. ScreenSpaceEventHandler.setInputAction()``](/Cesium/Build/Documentation/ScreenSpaceEventHandler.html#setInputAction) 監聽某類型的用戶輸入事件 -- [
ScreenSpaceEventType`用戶輸入事件類型,作爲一個參數傳遞過去。這裏咱們設置一個回調函數來接受鼠標移動事件: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.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中,或者經過設置EntityCollection
的 show
屬性來控制。只須要設置一次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開發事業,咱們鼓勵你訪問下面的資源:
咱們很樂意去分享全部Cesium社區建立的酷炫項目。遍及世界的的開發者建立了不少有意思的咱們歷來沒考慮過的項目。一旦你的項目準備分享給全世界,請跟咱們聯繫放到[CesiumJS示例頁面] (https://cesiumjs.org/demos)。具體請閱讀 這個博客提交你的項目示例。