Ceisum官方教程3 -- 空間資料視覺化
原文地址:https://cesiumjs.org/tutorials/Visualizing-Spatial-Data/
這篇教程教你如何使用Cesium的Entity API去繪製空間資料,如點,圖示,文字標註,折線,模型,圖形和立體圖形。雖然這章不需要什麼前提,但是如果你對Cesium一無所知,最好從第一個教程開始。
Entity API是什麼?
Cesium豐富的空間資料視覺化API分為兩部分:Primitive API 面向三維圖形開發者,更底層一些。Entity API 是資料驅動更高階一些。
Primitive API的主要目的是為了完成(視覺化)任務的最少的抽象需求。他要求我們以一個圖形開發者的方式去思考,並且使用了一些圖形學術語。它是為了最高效最靈活的實現視覺化效果,忽略了API的一致性。比如繪製三維模型和建立Billboard不同,和多邊形繪製更是徹底不同。每種視覺化都有自己鮮明的特色。此外,他們每種都有自己的獨特的效能提升方式,也需要遵守不同的優化原則。雖然它很強大又很靈活,但是大多數專案需要比Primitive API更高層次的抽象。
Entity AP的主要目的是定義一組高階物件,它們把視覺化和資訊儲存到統一的資料結果中,這個物件叫Entity。 它讓我們更加關注我們的資料展示而不是底層的視覺化機制。它提供了很方便的建立複雜的,與靜態資料相匹配的隨時間變化的視覺化效果。Entity API實際內部在使用Primitive API ,它的實現細節,我們無需關心。經過各種資料的測試,Entity API提供靈活的,高層次的視覺化,同時暴露一些一致性的、容易去學習和使用的介面。
第一個 Entity
學習Entity API基本使用的最好方式就是去讀程式碼。簡單其間,我們使用Sandcastle去建立 Hello World 示例。如果你已經建立了本地的cesium專案,那麼使用你自己的專案。
假設,我們需要從經緯度列表中建立美國懷俄明州(選擇懷俄明州Wyoming,是因為它的邊界足夠簡單)的多邊形。把下面的程式碼貼上拷貝到Sandcastle中去:
單機Run 按鈕(或者按下F8)就看到如下圖所示效果:
懷俄明州
第一個 entity.懷俄明州從來沒有讓人如此興奮.
我們盡力使Cesium的程式碼容易理解,上面的程式碼不用解釋也應該明白什麼意思。首先建立Cesium程式的基礎物件 Viewer widget, 然後使用viewer.entities.add新增 Entity。傳給 add 方法的引數一個包含了初始化配置的js 物件. 返回值就是 entity 物件. 最後呼叫 viewer.zoomTo 定位到到這個entity。
Entity 的配置項裡有大量的引數,但是現在我們只是設定了 polygon 的填充面為半透明紅色,邊界線時黑色的。最後把這個entity命名為“Wyoming”。
面和體
學了基礎的新增多邊形知識,多虧Entity API的一致性非常好,我們結合Sandcastle 的示例,就很容易就建立各種圖形。下面是所有支援的面和體的圖形列表:
Box
六面體盒子entity.box
Ellipse
圓和橢圓entity.ellipse
Corridor
Corridor entity.corridor
Cylinder
圓柱和圓錐 entity.cylinder
Polygon
多邊形 entity.polygon
Polyline
折線 entity.polyline
Volume
Polyline Volumes entity.polylineVolume
Rectangle
矩形 entity.rectangle
Ellipsoid
球和橢球 entity.ellipsoid
Wall
牆 entity.wall
材質和邊線
無論他們的幾何體有什麼不同,所有形狀和體都有一系列相同的屬性來控制它們的外觀。fill 為boolean型別,控制表面是否填充。 outline 屬性控制是否有外邊界。
當 fill=true,material屬性決定了用什麼材質填充表面。下個例子,我們建立一個半透明橢圓。預設fill=true, outline=false,所以我們只需要設定material屬性。
半透明橢圓
圖片材質
直接設定一個圖片的url就可以了。
圖片材質
上面兩個示例李, 當設定顏色或者url之後Cesium會自動建立 ColorMaterialProperty 或者ImageMaterialProperty物件。 對於更復雜的材質, 需要手動建立 MaterialProperty物件。 當前, Entity 面和體支援 顏色(colors),紋理圖片( images),棋盤 (checkerboard), 條紋(stripe), 網格(grid)等材質.
網格材質
網格材質
條紋材質
條紋材質
網格材質
網格材質
邊線
和 fill屬性不太一樣,outline沒有對應的材質配置,而是用兩個獨立的屬性outlineColor和outlineWidth。
注意outlineWidth屬性僅僅在非windows系統上有效,比如Android, iOS, Linux, 和OS X。Windows系統上邊線寬度永遠為1。主要是因為三大主流瀏覽器引擎在windows平臺上實現webgl上的技術限制。
邊線
折線
折線是個特例,他沒有填充或者邊線屬性。除了顏色它有專門的材質屬性。由於這種特殊材質,折線寬度和折線的邊線寬度,在所有系統都有效。
折線
折線邊線
折線的邊線
折線輝光
折線輝光
高度和垂直擠壓(Extrusions)
所有的面形狀都是平鋪在地球上,當前 圓(circles)、橢圓(ellipses)、多邊形(polygons)、矩形(rectangles)可以有一個高程屬性 或者 垂直擠壓變成體。這兩種情況種,這些面或者體仍然會貼合地球曲率。
上面我們列出的所有圖形,都是隻需要在圖形物件(graphics )上設定一個高度屬性即可。這裡順便說明下,除非在函式上明確說明,否則Cesium總是使用米、弧度、秒做為標準單位。如 Cartesian3.fromDegrees.
下面這行程式碼把多邊形放到了 250,000米高空。
250,000 米高空的懷俄明
把圖形擠壓為體,也非常簡單。僅僅需要設定 extrudedHeight 屬性。將會建立一個在height 和extrudedHeight之間的體塊。如果 height 沒有定義, 體塊從 0高程開始。下面程式碼建立一個從200,000米到 250,000米的體 。也就是說這個體的高度是50000米。
垂直擠壓對多邊形變成體也非常容易
Viewer中的Entity 元素(feature)
在開始其他視覺化效果學習之前,讓我們先看看 Viewer 中提供的和Entity相關的函式。
選中和描述
除非明確禁用,否則點選Entity將在它的位置會顯示 SelectionIndicator 控制元件,並且在 InfoBox 控制元件裡顯示它的描述資訊。回想我們最開始的示例,我們僅僅為 wyoming entity設定了name屬性,它顯示在 InfoBox標題欄, 也可以通過 Entity.description 設定一段HTML當作infobox的內容。 把下面的程式碼追加到上面的示例裡:
wyoming.description = '\
<img\
width="50%"\
style="float:left; margin: 0 1em 1em 0;"\
src="//cesiumjs.org/tutorials/Visualizing-Spatial-Data/images/Flag_of_Wyoming.svg"/>\
<p>\
Wyoming is a state in the mountain region of the Western \
United States.\
</p>\
<p>\
Wyoming is the 10th most extensive, but the least populous \
and the second least densely populated of the 50 United \
States. The western two thirds of the state is covered mostly \
with the mountain ranges and rangelands in the foothills of \
the eastern Rocky Mountains, while the eastern third of the \
state is high elevation prairie known as the High Plains. \
Cheyenne is the capital and the most populous city in Wyoming, \
with a population estimate of 62,448 in 2013.\
</p>\
<p>\
Source: \
<a style="color: WHITE"\
target="_blank"\
href="http://en.wikipedia.org/wiki/Wyoming">Wikpedia</a>;\
</p>';
設定Entity描述資訊
很多專案都是從服務端返回描述資訊,而不是上面這種硬編碼,不過這種方法是可行的。
預設,在InfoBox 裡所有的HTML是沙盒模式。這個防止外部的資料注入惡意的程式碼。如果你需要在描述資訊裡執行js指令碼或者瀏覽器外掛,可以通過viewer.infoBox.frame屬性來訪問這個iframe。更多關於iframe的沙盒模式,請參考這篇文章 。
相機控制
就像第一個例子中,我們使用 zoomTo 命令去顯示一個特定的entity。雙擊Entity或者點選 InfoBox左上角按鈕,也能達到同樣效果. 還有一個 flyTo 方法,它不是立即定位過去,而是執行一個相機動畫漸變過去。這些方法除了應用在單獨一個entity上,也可以作用在 EntityCollection物件上或者一個普通的js entity陣列,。
預設,這些方法會自動計算一個檢視,確保所有所有傳到方法裡的entity都可見,相機朝向正北,以45°傾斜俯視。可以提供一個自定義的heading, pitch, and range.來修改這個朝向。下面程式碼執行後相機會從東方向下傾斜30°角去看懷俄明的多邊形。因為我們沒有設定range引數,那麼這個引數還是按照預設計算的結果。
自定義視角
zoomTo 和flyTo 都是非同步函式, 也就是說當函式return的時候,並不能保證執行完畢了。一般flyto會在很多個動畫幀裡都運算。這些函式都返回一個 Promises ,我們可以把飛行或者縮放完成後需要制定的程式碼放到 then函式裡。我們把以下程式碼片段裡換成 zoomTo ,並且在飛行完畢後會同時選中這個entity。
這裡回撥函式裡的result引數,true表示飛行正常完成,false 飛行被打斷 或者 使用者開啟了另一個飛行定位函式,再或者目標物件無法被視覺化也就沒辦法去定位了。
有時候,尤其是展示一個隨時間變化的資料,我們希望相機能跟隨這個entity。這個通過設定 viewer.trackedEntity就很容易實現。跟隨一個entity要求position屬性必須存在。還是通過我們的Wyoming 多邊形entity來測試這個模式,我們給它增加個position屬性,程式碼如下:
把 viewer.trackedEntity 設定為undefined 或者點選 InfoBox的左上的取消按鈕都可以停止跟隨模式。 呼叫zoomTo 或者 flyTo 也會取消跟隨模式,並且 把 viewer.trackedEntity 設定為 undefined。
大部分情況下,在 Viewer 中定義的和entity相關的相機函式足夠使用了。但是如果你想在專案更多的自定義相機檢視方式,請檢視 相機教程 。
管理Entity集合
EntityCollection類是一個Entity陣列集合,用來它管理和控制一組entity非常方便。我們已經見過它的一個例項 viewer.entities 屬性。EntityCollection 提供了基本的陣列方法 add, remove, 和 removeAll;同時還有下面我們要討論的一些特有方法或者屬性。
很多專案的資料實際都是存在服務端的,只有客戶端需要的時候才會載入。有時候需要更改一個我們已經建立的entity。所有entity物件都有一個獨一無二的 id 屬性,這種情況情況下就非常有用。前面的示例裡,我們並沒有指定這個id,Cesium會自動生成一個 GUID 類似182bdba4-2b3e-47ae-bf0b-83f6fde285fd 填充到id屬性裡。服務端的資料一般都有自己主鍵id屬性,所以可以在enity建立的時候指定這個id。
隨後,可以通過 getById來獲取Entity物件。如果沒有找到對應的id,那麼該方法返回 undefined。
另一個常見的應用,是如果id不存在就新建,如果id存在就更新。 getOrCreateEntity 總會返回以傳入的引數為id的物件例項, 如果id不存在,那麼會新建一個,並且增加到entity集合裡,然後返回。
最後,簡單的通過 add就可以新建一個Entity例項。這種情況下,add函式會檢測如果傳入了一個已經存在的id,那麼會報異常。
EntityCollection 最強大的功能其實是collectionChanged Event,我們用它來接收集合裡entity被新增、刪除甚至更新的通知。當專案裡的使用者介面或者某個功能需要監控集合裡的物件改變的時候,這個功能非常有用。
為了驗證這點,可以試下Sandcastle的例項 Geometry 示例 。把下面的程式碼拷貝到緊跟viewer 建立的地方。
當執行示例的時候,控制檯輸出了65條訊息。每呼叫一次 viewer.entities.add就會有一條訊息 ( removed 和 changed在這裡沒有提示,因為我們這個專案裡只有add)。為了更新視覺化效果,Cesium內部實際也訂閱了這個事件。當一次性更新的數量過多的時候,先一個個更新,最後統一發訊息效率更高。因為Cesium只處理了一遍變化訊息,所以這個對效能有提升。 在修改之前,我們先呼叫 viewer.entities.suspendEvents,修改完之後再呼叫 viewer.entities.resumeEvents.
我們試下這個。在第一次呼叫 viewer.entities.add 前新增一個suspend呼叫,在最後呼叫一下resume 。再次執行下程式,我們現在只收到一條訊息,但是裡面包含了65條entity新增記錄。 這個函式呼叫有內部計數,所以多重嵌入呼叫suspend 和resume沒有任何問題。可是,如果忘了呼叫resume,那麼在處理完之後會獲取不了任何資訊。因為resume只有在對應層次的suspend下才會發出訊息(也就是suspend和resume必須是匹配的)。
拾取
拾取,也就是返回特定螢幕座標(通常是滑鼠位置)的物件,這也是這部分唯一需要和Primitive API打交道的功能。這部分未來在講Cesium的Entity拾取技術功能的時候會再次討論。 現在我們使用一些低層次的方法 scene.pick 和scene.drillPick 。下面程式碼是拾取部分的一個基本實現,基本上可以直接在專案裡使用 。
來解釋下。 場景的拾取函式返回的是圖元資訊而不是entity物件,但是Entity API的結構限定每一個圖元會對應到一個entity實體上,通過他們的 id 屬性來區分。所以我們只需要檢測拾取的物件id是否是一個 Entity. 這些函式是不重要的(trivial),它還沒有被當作Cesium的正式部分,我們有一些更加穩定的函式計劃(more robust functionality planned) 。
點(Points),公告牌( Billboards), 標註(Labels)
別考慮面和體了,我們來學下在Cesium上如何展示POI點。 建立一個點或者標註非常簡單,只需要設定entity 的 position 屬性,以及point 或者label 視覺化物件。比如,我想在我最喜歡的球隊主場放一個點。
點和標註
上面的示例裡,我們精確指定了公告牌的寬度和高度,但其實是不需要的,如果沒有指定,那麼將使用圖片的高度和寬度。
標註和公告板有大量的選項,我們就不深入講解了。具體可以檢視Sandcastle裡的對應示例: 標註, 公告板。
三維模型
Cesium通過 glTF格式支援三維模型,glTF是 WebGL, OpenGL ES, and OpenGL的實時載入模型(the runtime asset format)。Cesium包含了一些可以使用的glTF模型 : 帶螺旋槳動畫的飛機,帶輪子動畫的汽車模型,帶行走動畫的人物模型。在Sandcastle 示例裡可以看到他們 三維模型 。
載入三維模型和前面其他的可視資料區別不大。只需要entity帶position屬性和一個指向glTF模型資源的uri路徑。
卡車模型
你可以配置一個 scale 屬性,它將等比例縮放模型。也可以配置一個 minimumPixelSize 屬性,它保證距離模型很遠的時候,模型不會小於設定的大小。
預設,模型向右朝向東方。可以通過 Entity.orientation 的屬性設定一個 四元數Quaternion。這個比前面只用位置的示例更麻煩一些,讓我們設定一下模型的 heading, pitch, roll。把下面程式碼拷貝到 Sandcastle,修改一下值 可以檢視具體的效果。
var viewer = new Cesium.Viewer('cesiumContainer');
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706);
var heading = Cesium.Math.toRadians(45.0);
var pitch = Cesium.Math.toRadians(15.0);
var roll = Cesium.Math.toRadians(0.0);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(heading, pitch, roll));
var entity = viewer.entities.add({
position : position,
orientation : orientation,
model : {
uri : '../../../../Apps/SampleData/models/GroundVehicle/GroundVehicle.glb'
}
});
viewer.trackedEntity = entity;
因此模型需要轉為glTF格式才能在Cesium中使用。我們提供了一個 線上的轉換工具 ,你可以上傳COLLADA (dae)模型就會下載到glTF格式的。
當前Entity API還不支援模型的高階使用場景,比如模型節點的拾取或者動畫控制,不過可以使用Primitive API 實現。我們有一個單獨的教程來實現這些功能 三維模型高階教程 。未來我們肯定會增強 Entity API包含這些功能。這個高階教程包含了如何在Cesium下除錯模型顯示的異常效果,所以一定要去學習它。如果你設計了自己的模型,一定要去看看我們的 建模人員 glTF 貼士.
屬性系統
到目前,我們都是設定了entity的圖形物件屬性,還沒有實際讀取過屬性。事實上,我們可能會對返回的結果感覺驚訝。回想我們第一個多邊形示例裡,我們把outline屬性設定為 true 。直覺告訴我們,如果我們用日誌輸出(console.log)獲取wyoming.polygon.outline的型別,將輸出 boolean。
可是上述程式碼的輸出實際是 object。因為 outline 不是一個簡單的布林型別,而是一個ConstantProperty類的例項。實時上,這個教程整個使用的一種叫隱形屬性轉換的簡略形式來設定屬性,它會自動的使用原始值建立一個對應的 ConstantProperty 類例項。如果沒有這種簡略形式,我們就不得不去寫一個更長的初始化示例程式碼:
var wyoming = new Cesium.Entity();
wyoming.name = 'Wyoming';
var polygon = new Cesium.PolygonGraphics();
polygon.material = new Cesium.ColorMaterialProperty(Cesium.Color.RED.withAlpha(0.5));
polygon.outline = new Cesium.ConstantProperty(true);
polygon.outlineColor = new Cesium.ConstantProperty(Cesium.Color.BLACK);
wyoming.polygon = polygon;
viewer.entities.add(wyoming);
為什麼 屬性是這種形式?原因很簡單,整個Entity API的屬性設計是不僅僅考慮是一個常量值,而需要設定一些隨時間變換的值。
所有的屬性類實現 Property 介面, Cesium中定義了很多種屬性類。本教程的第二部分將重點關注屬性系統,使用它去建立一個時間變化的動態視覺化效果。 現在,我們唯一需要知道的是:為了讀取屬性的值,我們需要呼叫 getValue函式。所以為了獲得多邊形的outline屬性,應該寫類似下面的程式碼,時間引數傳當前場景時間即可。
嚴格來說,如果我們明確知道正在讀取一個 ConstantProperty的值,那麼可以不需要傳遞時間引數。但是明確指定時間引數是個慣例。
接下來幹什麼
我們勉強學習了Cesium載入空間資料視覺化的一點皮毛,但是我們已經解鎖了一個巨大的可能性。等待這個教程第二部分的同時,或許可以學習下Cesium對 影像圖層 或者 地形和水面的支援。也可以看下所有教程列表 看看有沒有感興趣的。