原文地址:https://cesiumjs.org/tutorials/Cesium-Workshop/
概述
我們很高興歡迎你加入Cesium社區!為了讓你能基於Cesium開發自己的3d 地圖項目,這個教程將從頭到尾講解一個基礎的Cesium程序的開發過程。這個教程將用到很多重要的CesiumAPI,但是並不是所有的(CesiumJS有很多很多功能)。我們目標是教會你基於Cesium做開發的基本原則和工具,在你的項目里能舉一反三,解決其他問題。
我們創建一個簡單的程序去可視化紐約市的一些地理位置。我們將加載各種類型各種樣式的二維和三維數據,並且創建若干個相機位置,並且展示一些用戶交互的UI。最后,做為一個高科技地圖,我們加載了一個無人機三維模型,充分利用3d可視化的優勢去觀察一些地理位置。
在完成教程后,你對Cesium的功能會有幾個基本概念,包括配置viewer、加載數據、創建各種樣式的幾何體、使用3d tiles(三維模型切片)、控制相機、增加鼠標交互事件。

步驟
再開發前的幾個必備步驟:
- 訪問這個頁面確認你的電腦環境適合Cesium Cesium Viewer. 如果沒有看到地球? 點這個鏈接 Troubleshooting.
- 安裝Node.js.
- 下載教程代碼 workshop code。使用git clone 或者手動下載zip並解壓縮。
- 在cmd命令行下,使用cd命令定位到
cesium-workshop
目錄下. - 運行
npm install
。 - 運行
npm start
。
控制台應該輸出下面信息:

Cesium development server running locally. Connect to http://localhost:8080
注意不能關閉控制台窗口,開發中需要保證這個進程運行着。
下一步, 在瀏覽器里打開 localhost:8080
。你應該能看到我們的程序已經運行了。
注意
這個教程里提到的workshop是基於cesium1.45開發的,里面的地形服務器已經失效了,導致cesium加載並不成功,使用這個代碼看不到效果。

解決方法也很簡單,我們使用Cesium最新版1.51里的文件替換到如下目錄

再次刷新頁面,就可以了,效果如下:

程序目錄
在程序根目錄下,有如下文件和文件夾. 這個程序已經被設計為盡可能的簡單,只包含cesiumjs的庫。
-
Source/
: 我們項目的代碼。 -
ThirdParty/
: 外部js庫,目前只包含cesium。 -
LICENSE.md
: 我們項目的說明條款。 -
index.html
: 主頁,包含項目程序代碼和頁面結構。 -
server.js
: 簡單的基於nodejs的http服務器。
CesiumJS是完全兼容現代javascript 庫和框架,所以放心大但的使用。
下面是一些示例:
- Cesium and webpack 教程展示了使用webpack集成cesium去更高效的開發web項目。
- React集成
- CesiumJS和Threejs集成
頁面結構
下來我們看看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默認的控件樣式。
工作流程
步驟如下:
- 使用你最擅長的文本編輯器(推薦sublime)打開
Source/App.js
,並且清空里面內容。 - 把文件
Source/AppSkeleton.js
的內容拷貝到Source/App.js
。 - 確認你的http服務還在
cesium-workshop
目錄運行着。 - 使用你的瀏覽器打開 localhost:8080.推薦使用chrome,但是現在瀏覽器都可以. 你應該能看到一個黑色背景。
- 在代碼里去掉注釋,保存
Source/App.js
,刷新瀏覽器,應該有些效果改變了。
還有問題? 那你先跟着sandcastle去做一個沒有UI的簡單程序:
下來我們真正開始。
創建Viewer
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提供了多種影像數據來源 多用影像數據源 。
支持的格式:
- 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需要在服務端預先把地形數據處理為切片形式,在客戶端基於當前相機位置去請求和渲染地形切片。
下面是一些示例和地形數據集以及配置選項:
- ArcticDEM : 高精度北極地形。
- PAMAP Terrain : 高精度賓夕法尼亞州地形
- 地形配置 : 地形配置和格式
- 地形誇張 : 使地形起伏差異更大
支持的格式:
- 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 });
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
:
在ENU(East-North-Up)坐標系中,相對坐標軸的旋轉(弧度)。Heading 相對負z軸(垂直向下). Pitch 相對負y軸. Roll相對正x軸. -
Quaternion
:使用四維坐標描述的三維旋轉。
這是在Cesium的scene中擺放對象的基本類型,Cesium提供了一系列的方便的轉換函數。具體請查看cesium文檔。
現在,我們把相機定位到我們數據所在的位置--紐約。
相機控制
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)
: 繞着任意軸旋轉相機。
更詳細的可以去學習下面兩個示例:
- Camera API示例
- 自定義相機控制
我們測試一個方法,把相機位置放置到紐約。分別使用一個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); // 設置時間的可見范圍
上述代碼設定了場景動畫播放速率,開始和結束時間,並且設置為循環播放。並且設置了時間線控件在合適的時間范圍。使用這個 示例 去試驗更多時間設置
初始化配置完成了,當你運行代碼,能看到如下效果

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,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 });

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
: 返回一條射線和地形的相交位置點。
這是一些示例:
- 拾取示例
- 3D Tiles 對象拾取
因為我們想實現鼠標滑過的高亮效果,首先需要創建一個鼠標事件處理器。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內置的跟隨函數,確保相機一直居中觀察無人機。這種模式下,即便對象是移動的,相機也能和目標之間保持一個固定的偏移距離。只需要簡單的設置
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開發事業,我們鼓勵你訪問下面的資源:
- 官方文檔 : 完整的CesiumAPI文檔,包含一些示例代碼
- Sandcastle :一個所見即所得的編碼環境,包含大量的代碼示例.
- 官方教程 :詳細描述基於Cesium開發的方方面面。
- 官方論壇 : Cesium相關問題的主要討論平台.
- Cesium實驗室 : 中國最專業的Cesium的討論學習QQ群:595512567。
一旦有問題,上面的資源可能有你的答案。
在cesiumjs.org展示你的項目
我們很樂意去分享所有Cesium社區創建的酷炫項目。遍布世界的的開發者創建了很多有意思的我們從來沒考慮過的項目。一旦你的項目准備分享給全世界,請跟我們聯系放到[CesiumJS示例頁面] (https://cesiumjs.org/demos)。具體請閱讀 這個博客提交你的項目示例。
