前言
Cesium官方教程中有一篇叫《空間數據可視化》(Visualizing Spatial Data)。該文文末簡單提到了Cesium的Property機制,然后話鋒一轉,宣告此教程的第二部分將重點講解Property機制。但是呢,第二部分還沒有寫好,說在等待的過程中,可以先看下Cesium對影像和地形的支持。。
可以看官方教程中的說法,如下圖所示:

於是,我苦等了一年啦。。官方教程的第二部分還是沒能看到。。畢竟這是Cesium官方推薦使用的Entity API中最重要的部分之一。。居然這么久了也不給更新下。。
我想還是自己總結一下得好。。
為什么要用Property?
還是舉個例子來說吧。
比如我想在地球上的某個位置加一個盒子,可以這樣寫代碼:
// 創建盒子 var blueBox = viewer.entities.add({ name : 'Blue box', position: Cesium.Cartesian3.fromDegrees(-114.0, 40.0, 300000.0), box : { dimensions : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0), material : Cesium.Color.BLUE, outline: true, } });
最終的效果如圖所示:

但是呢,如果我想讓這個盒子逐漸變長,該怎么操作呢?如下圖所示:

方法是有的,就是可以不停地去修改blueBox.position,類似這樣:
setInterval(function(){ blueBox.box.dimensions = xxx; }, 3000);
如果場景中有很多物體,在不同的時間段要發生各種走走停停地運動時,這樣操作可能會很累人。那么Cesium就提供一種機制,讓dimensions可以隨時間自動發生變化,自動賦予不同的數值(位置)。這也就是property的作用了。以下代碼的加入,就可以讓盒子如上圖所示做線性運動了。
var property = new Cesium.SampledProperty(Cesium.Cartesian3); property.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), new Cesium.Cartesian3(400000.0, 300000.0, 200000.0)); property.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), new Cesium.Cartesian3(400000.0, 300000.0, 700000.0)); blueBox.box.dimensions = property;
以上代碼的意思就是在兩個不同的時間點分別賦予不同的位置,用SampledProperty包裝成一個property,最后賦給blueBox.box.dimensions。
由此可見,Property最大的特點是和時間相互關聯,在不同的時間可以動態地返回不同的屬性值。而Entity則可以感知這些Property的變化,在不同的時間驅動物體進行動態展示。
Cesium宣稱自己是數據驅動和time-dynamic visualization,這些可都是仰仗Property系統來實現的。
當然,Property可不只是這么簡單,以下再詳細論述。
Property的分類
Cesium的Property不止有剛才示例代碼中的SampleProperty,還有很多其他的類型。如果搜索一下Cesium的API文檔,會有很多。。如下圖所示:

我們簡單分類一下

Property虛基類
Property是所有Property類型的虛基類。它定義了以下接口。

getValue 是一個方法,用來獲取某個時間點的特定屬性值。它有兩個參數:第一個是time,用來傳遞一個時間點;第二個是result,用來存儲屬性值,當然也可以是undefined。這個result是Cesium的scratch機制,主要是用來避免頻繁創建和銷毀對象而導致內存碎片。Cesium就是通過調用getValue類似的一些函數來感知Property的變化的,當然這個方法我們在外部也是可以使用的。
isConstant 用來判斷該屬性是否會隨時間變化,是一個布爾值。Cesium會通過這個變量來決定是否需要在場景更新的每一幀中都獲取該屬性的數值,從而來更新三維場景中的物體。如果isConstant為true,則只會獲取一次數值,除非definitionChanged事件被觸發。
definitionChanged 是一個事件,可以通過該事件,來監聽該Property自身所發生的變化,比如數值發生修改。
equals 是一個方法,用來檢測屬性值是否相等。
基本Property類型
SampleProperty
我們最早在上述示例中使用的就是它,用來通過給定多個不同時間點的Sample,然后在每兩個時間點之間進行線性插值的一種Property。代碼寫法如下:
var property = new Cesium.SampledProperty(Cesium.Cartesian3); property.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), new Cesium.Cartesian3(400000.0, 300000.0, 200000.0)); property.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), new Cesium.Cartesian3(400000.0, 300000.0, 700000.0)); blueBox.box.dimensions = property;
效果如下所示:

TimeIntervalCollectionProperty
該Property用來指定各個具體的時間段的屬性值,每個時間段內的屬性值是恆定的,並不會發生變化,除非已經進入到下一個時間段。拿創建的盒子示例來說,表現出來的特點就是盒子尺寸的變化時跳躍式的。效果如下:

代碼如下:
var property = new Cesium.TimeIntervalCollectionProperty(Cesium.Cartesian3); property.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-01T00:00:00.00Z/2019-01-01T12:00:00.00Z', isStartIncluded : true, isStopIncluded : false, data : new Cesium.Cartesian3(400000.0, 300000.0, 200000.0) })); property.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-01T12:00:01.00Z/2019-01-02T00:00:00.00Z', isStartIncluded : true, isStopIncluded : false, data : new Cesium.Cartesian3(400000.0, 300000.0, 400000.0) })); property.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-02T00:00:01.00Z/2019-01-02T12:00:00.00Z', isStartIncluded : true, isStopIncluded : false, data : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0) })); property.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-02T12:00:01.00Z/2019-01-03T00:00:00.00Z', isStartIncluded : true, isStopIncluded : true, data : new Cesium.Cartesian3(400000.0, 300000.0, 700000.0) })); blueBox.box.dimensions = property;
ConstantProperty
通過對TimeIntervalCollectionProperty和SampleProperty的描述,讀者應該基本了解Property的特點。我們回過頭來說下ConstantProperty,其實這才是最常用的Property。
示例代碼如下:
blueBox.box.dimensions = new Cesium.Cartesian3(400000.0, 300000.0, 200000.0);
以上代碼貌似沒有使用ConstantProperty,實際上他是等同於:
blueBox.box.dimensions = new ConstantProperty(new Cesium.Cartesian3(400000.0, 300000.0, 200000.0));
也就是Entity的box.dimensions類型並不是Cartesian3,而是一個Property。雖然我們賦值了一個Cartesian3,但是Cesium內部會隱晦地轉化成了一個ConstantProperty。注意只會隱晦地轉化成ConstantProperty,而不是SampleProperty,更不是TimeIntervalCollectionProperty。
雖然叫ConstantProperty,但是,這里Constant的意思並不是說這個Property不可改變,而是說它不會隨時間發生變化。
舉個例子,我們可以通過 property.getValue(viewer.clock.currentTime) 方法來獲取某個時間點property的屬性值。如果property是SampleProperty或者TimeIntervalCollectionProperty的話,不同的時間點,可能getValue出不同的數值。但是如果這個property是ConstantProperty,那么無論什么時間(getValue的第一個參數不起作用),最后返回的數值都是一樣的。
但是不會隨時間變化,並不代表不可改變。ConstantProperty還有一個setValue的方法,開發者可以通過調用它,來在適當的時候改變property的值。
比如,我可以通過點擊按鈕來修改ConstantProperty,代碼如下:
blueBox.box.dimensions.setValue(new Cesium.Cartesian3(400000.0, 300000.0, 700000.0));
需要注意的是,雖然最終效果一樣,但是以下兩種寫法的意義是不一樣的。
blueBox.box.dimensions = new Cesium.Cartesian3(400000.0, 300000.0, 200000.0);
blueBox.box.dimensions.setValue(new Cesium.Cartesian3(400000.0, 300000.0, 700000.0));
前者會創建一個新的ConstantProperty,后者則會修改原有的ConstantProperty的值。
CompositeProperty
CompositeProperty的意思是組合的Property,可以把多種不同類型的ConstantProperty、SampleProperty、TimeIntervalCollectionProperty等Property組合在一起來操作。比如前一個時間段需要線性運動,后一段時間再跳躍式運動。則可以使用類似下面這段代碼來實現。
// 1 sampledProperty var sampledProperty = new Cesium.SampledProperty(Cesium.Cartesian3); sampledProperty.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), new Cesium.Cartesian3(400000.0, 300000.0, 200000.0)); sampledProperty.addSample(Cesium.JulianDate.fromIso8601('2019-01-02T00:00:00.00Z'), new Cesium.Cartesian3(400000.0, 300000.0, 400000.0)); // 2 ticProperty var ticProperty = new Cesium.TimeIntervalCollectionProperty(); ticProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-02T00:00:00.00Z/2019-01-02T06:00:00.00Z', isStartIncluded : true, isStopIncluded : false, data : new Cesium.Cartesian3(400000.0, 300000.0, 400000.0) })); ticProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-02T06:00:00.00Z/2019-01-02T12:00:00.00Z', isStartIncluded : true, isStopIncluded : false, data : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0) })); ticProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-02T12:00:00.00Z/2019-01-02T18:00:00.00Z', isStartIncluded : true, isStopIncluded : false, data : new Cesium.Cartesian3(400000.0, 300000.0, 600000.0) })); ticProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-02T18:00:00.00Z/2019-01-03T23:00:00.00Z', isStartIncluded : true, isStopIncluded : true, data : new Cesium.Cartesian3(400000.0, 300000.0, 700000.0) })); // 3 compositeProperty var compositeProperty = new Cesium.CompositeProperty(); compositeProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-01T00:00:00.00Z/2019-01-02T00:00:00.00Z', data : sampledProperty })); compositeProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({ iso8601 : '2019-01-02T00:00:00.00Z/2019-01-03T00:00:00.00Z', isStartIncluded : false, isStopIncluded : false, data : ticProperty })); // 4 設置position blueBox.box.dimensions = compositeProperty;
最終實現的效果如下:

PositionProperty
以上示例可以看到,我們一直在用SampledProperty、ConstantProperty等來修改Entity的box.dimensions屬性。基本上可以得出結論:大部分Property都是可以賦值給Entity的box.dimensions的。
PositionProperty和Property一樣,是一個虛類,並不能直接實例化,他擴展了Property的接口,增加了referenceFrame,同時只能用來表示position。

referenceFrame是用來表示position的參考架。目前Cesium有以下兩種參考架。

我們常用的是FIXED這種默認類型,它相當於以地球的中心作為坐標系的原點,x軸正向指向赤道和本初子午線的交點。(可能描述不准確。。)這樣我們給定一個笛卡爾坐標(x, y, z),它在地球上的位置是固定的。
而INERTIAL這種類型,則相當於以太陽系的質心為原點的坐標架偏移到地球的中心來,如果給定一個笛卡爾坐標(x, y, z),那么它在不同的時間表示的是地球上的不同位置。。(我的理解,可能有誤。。)
一般情況下,我們用不上INERTIAL。但是如果真的給定了INERTIAL下的坐標點,Cesium內部會通過PositionProperty,把它轉成同一個FIXED下的坐標點來使用,這些不需要我們操作。
但是,因為普通的Property是沒有辦法進行這種參考架的自動轉換的,所以Cesium派生了一批PositionProperty類型。
基於PositionProperty的類型有以下幾種:
CompositePositionProperty
ConstantPositionProperty
PositionProperty
PositionPropertyArray
SampledPositionProperty
TimeIntervalCollectionPositionProperty
稍加留意,就會發現,和普通的Property相比,只是多了一個Position,所以用法上也大同小異,只不過他們是用來專門表示位置的。
SampledPositionProperty
SampledPositionProperty的用法,不多解釋,直接看代碼吧:
var property = new Cesium.SampledPositionProperty(); property.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), Cesium.Cartesian3.fromDegrees(-114.0, 40.0, 300000.0)); property.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), Cesium.Cartesian3.fromDegrees(-114.0, 45.0, 300000.0)); blueBox.position = property;
效果如下:

SampleProperty和SampledPositionProperty有一個特有的方法:setInterpolationOptions,用來修改不同的插值方式。以下是以Cesium的Interpolation示例中的截圖來說明他們的不同之處。
線性插值

代碼寫法如下:
entity.position.setInterpolationOptions({ interpolationDegree : 1, interpolationAlgorithm : Cesium.LinearApproximation });
Lagrange插值

entity.position.setInterpolationOptions({ interpolationDegree : 5, interpolationAlgorithm : Cesium.LagrangePolynomialApproximation });
Hermite插值

entity.position.setInterpolationOptions({ interpolationDegree : 2, interpolationAlgorithm : Cesium.HermitePolynomialApproximation });
MaterialProperty
MaterialProperty是用來專門表示材質的Property,它對Property進行了擴展,增加了getType方法,用來獲取材質類型。

MaterialProperty也是一個虛基類,派生類有:
CheckerboardMaterialProperty
ColorMaterialProperty
CompositeMaterialProperty
GridMaterialProperty
ImageMaterialProperty
MaterialProperty
PolylineArrowMaterialProperty
PolylineDashMaterialProperty
PolylineGlowMaterialProperty
PolylineOutlineMaterialProperty
StripeMaterialProperty
使用上大同小異,我們以ColorMaterialProperty來說明一下。
ColorMaterialProperty
blueBox.box.material = new Cesium.ColorMaterialProperty(new Cesium.Color(0, 1, 0)); // 以上代碼等同於 // blueBox.box.material = new Cesium.Color(0, 1, 0);
效果如下:

ColorMaterialProperty的動態變化
如果希望Color動起來的話,也是可以的。ColorMaterialProperty的內部有一個color屬性,可以賦予一個SampledProperty來實現動態效果。
var colorProperty = new Cesium.SampledProperty(Cesium.Color); colorProperty.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), new Cesium.Color(0, 1, 0)); colorProperty.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), new Cesium.Color(0, 0, 1)); blueBox.box.material = new Cesium.ColorMaterialProperty(colorProperty);
效果如下:

其他類型的Property
CallbackProperty
CallbackProperty是自由度最高的一種Property,讓用戶通過自定義,回調函數,來返回需要的值。回調函數中,用戶可以使用time來給定value,也可以以自己的方式給給定。
以下代碼就是不通過time,自己手動調整dimension的示例。
var l = 200000.0; var property = new Cesium.CallbackProperty(function (time, result) { result = result || new Cesium.Cartesian3(0, 0, 0); l += 10000.0; if (l > 700000.0) { l = 200000.0; } result.x = 400000.0; result.y = 300000.0; result.z = l; return result; }, false); blueBox.box.dimensions = property;
效果如下:

ReferenceProperty
該Property可以直接鏈接到別的對象的Property上,相當於引用,省得自己構建了。比如這里我創建了一個紅色的盒子redBox,希望它和之前的藍色盒子一起變大。那么可以使用以下代碼:
var collection = viewer.entities; redBox.box.dimensions = new Cesium.ReferenceProperty(collection, blueBox.id, ['box', 'dimensions']);
效果如下:


ReferenceProperty構造函數的參數有三個。第一個參數用來指定需要引用的對象所屬的collection,如果沒有自己專門創建EntityCollection的話,可以直接使用viewer.entities。第二個參數傳遞所指對象的id。第三個參數指定屬性的位置的數組,如果是有層級的屬性,可以依次寫入。比如 ['billboard', 'scale']
指定的是entity.billboard.scale 屬性。當然還有其他設置方式,可以參見Cesium的api文檔。
PropertyBag
PropertyBag雖然不是以Property結尾,但實際上也是一個Property。它的特點是可以包裝一個對象(JS中的對象概念),該對象的每一個屬性(JS中的屬性概念),都可以作為一個動態的Property。
比如之前修改dimensions的話,dimensions是作為一個Cartesian3類型變量整體封裝到Property中去的,如果我們只想修改dimensions的x。則可以使用PropertyBag來實現,代碼如下:
var zp = new Cesium.SampledProperty(Number); zp.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), 200000.0); zp.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), 700000.0); blueBox.box.dimensions = new Cesium.PropertyBag({ x: 400000.0, y: 300000.0, z: zp });

效果和sampleProperty類似,但是修改的只是dimensions的x。
PropertyArray
PropertyArray和上述的PropertyBag類似,只是其內部封裝了一個數組而已。這里不再贅述。
VelocityOrientationProperty
該Property用來Entity的position的位置變化,來計算出移動的方向,最后把速度方向輸出成Orientation。Cesium自帶的示例中有一個Interpolation中有其用法,不再贅述。
VelocityVectorProperty
與上面的Property類似,把速度方向轉成Vector。使用示例如下:
blueBox.box.show = false; blueBox.billboard = { scale: 0.05, image : 'https://upload-images.jianshu.io/upload_images/80648-5dfe8a3ea2c250be.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/540/format/webp', alignedAxis : new Cesium.VelocityVectorProperty(blueBox.position, true) // alignedAxis must be a unit vector };
可見圖像的擺放方向和位置移動的方向保持一致。效果如下:

附錄
歡迎關注 Cesium實驗室 ,QQ群號:595512567。
