CZML介紹

CZML是一種用來描述動態場景的JSON架構的語言,主要用於Cesium在瀏覽器中的展示。它可以用來描述點、線、佈告板、模型以及其他的圖元,同時定義他們是怎樣隨時間變化的。Cesium擁有一套富客戶端API,通過CZML採用數據驅動的方式,不用寫代碼我就可以使用通用的Cesium viewer構建出豐富的場景。Cesium與CZML的關係就如同Google Earth和KML的關係。CZML和KML都是用來描述場景的數據格式,可以通到很多其他的程序自動生成,或者手寫也可以。CZML擁有很多的特性,其中有一些區別於KML的:

  1. CZML 是基於JSON的。
  2. CZML可以準確的描述值隨時間變化的屬性。理由,一條在某一時間內是紅色的而在另一時間內是藍色的。同時客戶端可以根據時間戳進行差值。加入有一輛車,分別定義了兩個不同時間的位置,通過CZML定義的差值算法,客戶端可以準確的顯示在這兩個時間點之間車的位置。所有屬性都可以是隨時間變化的。
  3. CZML通過增量流的方式傳送到客戶端。在場景顯示之前,整個CZML文檔需要首先被下載到客戶端。在某些情況下,個別客戶端可能會加入或離開正在傳輸的流。(這段翻譯的不好)
  4. CZML高度優化,旨在解析時更緊湊也更容易,讓人工的讀寫更容易。
  5. CZML可擴展,儘管CZML的主要作用在與虛擬地球客戶端程序與場景的交流,但它可以很容易的通過擴展來滿足其他一些輔助的程序對靜態或動態數據的需求。例如,隨時間動態變化在數據就可以用在某些2D的圖表程序中。
  6. CZML是一個開放的格式。我們希望有更多的程序能使用CZML,同時期待有一天它也能成爲OGC一樣的標準。
  7. 可以通過czml-writer來生成CZML,這個程序維護在Github上。

我們將CZML標準以及它的相應實現分爲4個部分:

CZML Structure --  CZML文檔的整體結構

CZML Content --內容

CZML in Cesium -- Cesium中解析和顯示CZML的流程

czml-writer-Architecture –czml-writer的架構

CZML Structure

CZML是json的一個子集,也就是說一個有效的CZML文檔同時也是一個有效的JSON文檔。特別的,一個CZML文檔包含一個JSON數組,數組中個每一個對象都是一個CZML數據包(packet),一個packet對應一個場景中的對象,例如一個飛機。

注:在下面的例子中我們使用javascript方式的註釋來幫助理解CZML,但在真實場景中是不允許的。

[
    // packet one
    {
        "id":"GroundControlStation"
        "position":{"cartographicDegrees":[-75.5,40.0,0.0]},
        "point":{
            "color":{"rgba":[0,0,255,255]},
        }
    },
    // packet two
    {
        "id":"PredatorUAV",
    // ...
    }
]

每個packet都有一個id屬性用來標示我們當前描述的對象。id在同一個CZML以及與它載入同一個範圍(scope)內的其他CZML文件中必須是唯一的。隨後我們將討論scope的含義。

假如沒有指定id,那麼客戶端將自動生成一個唯一的id。但是這樣的話在隨後的包中我們就沒有辦法引用它了,例如我們不能再往它裏面添加數據。

除了id以外,一個包通常還包含0到多個(正常情況下是1到多個)定義對象圖形特徵的屬性。正如上面的例子,我們定義了一個“GroundControlStation”對象,它擁有一個固定的經度-75.0維度40.0的WGS84座標,高度爲0,隨後一個藍色的點將會繪製在他的座標位置處。

CZML還有很多標準的屬性,包括用來添加點、佈告板、模型、線以及其他圖形到場景的屬性。所有這些屬性將在CZML Content這節討論。在這裏我們主要討論這些數據是怎樣組織的。例如,我們怎樣定義一個屬性,使它在兩個不同的時間擁有兩個不同的值。

時間間隔Intervals

通常情況下CZML的屬性值是一個數組,數組中的每個元素對應每一個不同的時間,屬性的值。時間間隔的定義使用interval 屬性,通過ISO8601 interval格式的字面值表示。

{
    "id":"myObject",
    "someProperty":[{
        "interval":"2012-04-30T12:00:00Z/13:00:00Z",
        "number":5
    },
    {
        "interval":"2012-04-30T13:00:00Z/14:00:00Z",
        "number":6
    }]
}

這裏我們定義了一個someProperty屬性,它有兩個時間間隔,第一個是從中午到下午一點,屬性值爲5,第二個是從下午一點到下午兩點,屬性值爲6,在時間由第一個間隔變化到第二個間隔的時候,屬性值會瞬間從5變到6。注意,這裏的時間爲UTC(協調世界時),而我們國家通常使用的是UTC+8也就是北京時間,所以要注意。

我們使用number來表示屬性是一個數字類型的屬性。值得注意的是有些屬性允許通過不同的格式來定義,例如表示位置的屬性可以通過笛卡兒座標的X、Y、Z表示,也可以通過經緯度和高程來表示。CZML Content一節將會詳細討論。

Interval屬性是可選的,你可以定義也可以不定義,如果沒有定義,默認爲整個時間。通常定義多個無限的時間間隔或者間隔之間有重疊是沒什麼意思的,如果你非要這麼做,那麼在CZML中最後定義的時間間隔將擁有較高的優先級。

通常情況下屬性值只跨越一個時間間隔,這時候你間隔列表可以省略。

{
    "id":"myObject",
    "someProperty":{
        "interval":"2012-04-30T12:00:00Z/14:00:00Z",
        "number":5
    }
}

和之前的一樣,加入interval跨越整個時間,那麼你可以省略掉它。對於一些簡單的值,例如上面我們使用的數字類型,加入它對於整個時間來說都不變,那麼我還可以定義的更簡單一些。

{
    "id":"myObject",
    "someProperty":5
}

這種簡略的表示法,適用於所有值是簡單的json數據類型(string、number、boolean)的的屬性。

複合值Composite Values

一些複雜的值複合值例如笛卡爾座標位置或顏色,是通過json數組的形式來表示。例如座標位置,所使用的數組有三個元素,分別對應於座標的x、y和z。

{
    "id":"myObject",
    "someComplexProperty":{
        "cartesian":[1.0,2.0,3.0]
    }
}

合成值必須定義在間隔中,即使他的時間間隔是無限的也是一樣,不能採用簡略的寫法。假如允許值[1.0,2.0,3.0]直接作爲someComplexProperty的屬性值,那麼客戶端代碼就需要解析CZML並且判斷數組裏面的值到底是時間間隔列表(intervals list)還是說是簡單的值。爲了簡單起見,我們不允許這樣的寫法。

屬性值採樣Sampled Property Values

到目前爲止我們討論的了怎樣爲那些橫跨整個時間的的屬性定義單一的值,以及針對離散的時間間隔定義不同的值。一些屬性(CZML Content一節將告訴你是哪些)允許你定義時間戳採樣,客戶端通過給定的時間差值計算出屬性的值。時間的定義是採用ISO8601標準字符串。

{
    // ...  
    "someInterpolatableProperty":{
        "cartesian":[
            "2012-04-30T12:00Z",1.0,2.0,3.0,
            "2012-04-30T12:01Z",4.0,5.0,6.0,
            "2012-04-30T12:02Z",7.0,8.0,9.0
        ]
    }
}

在這裏我們指定在中午的時候屬性值爲[1.0,2.0,3.0],一分鐘後爲[4.0,5.0,6.0],再過一分鐘後變爲[7.0,8.0,9.0]。假如客戶端當前的時間是12點0分30秒,也就是離開始時間過去了30秒,那當前的屬性值將通過在[1.0, 2.0, 3.0]和[4.0, 5.0, 6.0]線性差值得出,爲[2.5, 3.5, 4.5]。

簡單起見,時間使用距離起始時間(epoch)的秒數來表示。與每個時間都通過ISO8601字符串表示相比這樣不是很精確,不過對於採樣間隔在一天以內,或者偏移值時整數的時候這樣的精度還是綽綽有餘。

{
    // ...  
    "someInterpolatableProperty":{
        "epoch":"2012-04-30T12:00Z",
        "cartesian":[
            0.0,1.0,2.0,3.0,
            60.0,4.0,5.0,6.0,
            120.0,7.0,8.0,9.0
        ]
    }
}

最後,使用時間戳採樣的屬性還有一些附加的可選的子屬性,用來控制採樣的方式。

{
    // ...  
    "someInterpolatableProperty":{
        "epoch":"2012-04-30T12:00Z",
        "cartesian":[
            0.0,1.0,2.0,3.0,
            60.0,4.0,5.0,6.0,
            120.0,7.0,8.0,9.0
        ],
        "interpolationAlgorithm":"LAGRANGE",
        "interpolationDegree":5
    },
}

interpolationAlgorithm定義了採樣使用的算法,下面的表格中列出了可能的值。interpolationDegree定義了用來插值所使用的多項式的次數,1表示線性差值,2表示二次插值法,默認爲1.

每個採樣值的時間沒有必要落在包含它的時間間隔內。但是這些採樣值在他們的時間間隔內不會被使用。這樣的好處就是對於多次差值有更好的精度。

下表總結了可插值屬性的一些子屬性:

名稱 Scope 範圍 JSON類型 說明
epoch Packet string 使用ISO8601規範來表示日期和時間
nextTime Packet string 在時間間隔內下一個採樣的時間,可以通過ISO8061方式,也可以通過與epoch秒數來定義。它決定了不同packet之間的採樣是否有停頓。
previousTime Packet string 在時間間隔內前一個採樣的時間,可以通過ISO8061方式,也可以通過與epoch秒數來定義,它決定了不同packet之間的採樣是否有停頓。
InterpolationAlgorithm Interval string 用於插值的算法,有LAGTANGE,HERMITE和GEODESIC。默認是LAGRANGE。如果位置不在該採樣區間,那麼這個屬性值會被忽略。
interpolationDegree Interval Number

定義了用來插值所使用的多項式的次數,1表示線性差值,2表示二次插值法,默認爲1。如果使用GEODESIC插值算法,那麼這個屬性將被忽略。 

EventSource and Streaming

如果將整個CZML文件安排在一個大的JSON數組中,這使增量加載變得很困難。雖然瀏覽器允許我們訪問沒有讀取完的流數據,但是解析不完整的數據需要漫長而繁瑣的字符串操作。

爲了高效,CZML使用瀏覽器的server-sent events(EventSource)API來處理流數據。在實際操作中,每一個CZML的packet包會被作爲單獨的一個事件傳輸到客戶端。

 

event: czml
data: { 
    // packet one 
} 
event: czml
data: { 
    // packet two 
}

當瀏覽器接收到一個packet後就會發出一個事件,事件中會包含剛剛接收到了數據。這樣我們就可以通過增量的方式高效的處理CZML數據。

目前爲止,我們都是使用一個packet包來描述一個對象,這個packet包含了所有這個對象的圖形屬性。我們還可以使用其他的方式,例如一個CZML文件或流可以包含多個packet,每個packet都有相同的id,分別描述同一個對象的不同方面的屬性。

事實上在大多數情況下我們使用兩個packet來描述一個對象。當對象屬性跨越多個時間間隔,或者一個時間間隔有很多個時間戳採樣時,這樣做就很有用了。通過將一個屬性定義打包進多個packet,我們可以使數據更快的傳輸到Cesium中,減少用戶等待的時間。

當客戶端接收到一個packet,它會遍歷packet中的每一個屬性。對於每個屬性,它會遍歷屬性定義的每個時間間隔。對於每個時間間隔,它會判斷這個時間間隔是否已經定義,假如這個間隔已經定義,將更新已經存在的間隔,如果沒有定義,那麼就根據這個間隔創建一個新的。

當更新一個已存在的時間間隔時,假如有子屬性,那麼子屬性將覆蓋原有的值。有一個例外,就是當已有的屬性和新接收到的屬性都包含時間戳採樣時,新接收到的採樣不會覆蓋已有的,而是加到已有的採樣列表中。

當新的時間間隔與已有的發生重疊時,新的間隔擁有較高優先級,原有的間隔將被截斷或者整個移除。這點必須要牢記。

在同一個packet中的時間間隔的時間必須以增序排列,不同packet之間就沒有要求。但是對於不連續的採樣還是應該考慮合理的插值順序。

考慮一下,我們有一個需要插值的屬性,時間是0到10秒,間隔爲1.0秒。第一個packet包含0到3秒,第二個包含8到10秒。在客戶端還沒有接收到包含4到7秒的packet時,我們可以渲染時間爲5的場景嗎??

一種方式是我們就是使用已經接收到的兩個packet來插值。這可能不太好,因爲即使我們使用高次插值,在這個兩個packet的間隙中得出的值可能也是錯的。所以,我們最好還是先暫停等待中間的那個packet趕快到來。但是我們是怎麼知道兩個包之間有間隙的呢??我們可以猜想,剛開始的時候間隔是1秒,後來變成了5秒,再後來又變回了1秒,所以中間應該還有一個包。這個猜想只是逗你玩的,萬一人家中間就是想少採樣幾個點呢。

 這都不是事兒,CZML提供了previousTime和nextTime子屬性,用來處理這種情況。

{ 
    // ... 
    "someInterpolatableProperty": { 
        "epoch": "2012-04-30T12:00:00Z", 
        "cartesian": [ 
            0.0, 1.0, 2.0, 3.0, 
            1.0, 4.0, 5.0, 6.0, 
            2.0, 7.0, 8.0, 9.0,
            3.0, 10.0, 11.0, 12.0
        ], 
        "previousTime": -1.0, 
        "nextTime": 4.0 
    } 
}

它的作用是告訴客戶端3.0後下一個時間是4.0,就像我們上面舉的那個例子,3的後面是8,根據nextTime我們就知道3和8之間肯定還有一段數據沒有接收到,所以在開始插值之前我們就需要先等待數據讀取完成。

沒有必要同時設置previousTime和nextTime,在不同的情況下選擇使用其中最方便的一個就可以了。只要定義其中的一個,在進行插值前Cesium就會首先對數據進行完整性檢查。

Availability屬性

除了id屬性外,CZML的packet還有一個特別的額外屬性availability。

{ 
    "id": "PredatorUAV", 
    "availability": "2012-04-30T12:00:00Z/14:00:00Z", 
    // ... 
}

它用來標示一個對象的數據在什麼時候是可用的。假如一個對象在當前的動畫時間內是可用,但是客戶端現在還沒有獲取到相應的數據(可能在下一packet裏面,但現在還沒有獲取到),那麼Cesium就會先暫停,直到獲取到數據爲止。這個屬性的值可以一個字符串表示的一個時間段,也可以是一個字符串數組表示的多個時間段。

假如availability變化了或者被發現是不正確的,那麼隨後的packet將會更新它的值。例如,一個SGP4 propagator可能總是可用的,但是隨後他發出了一個異常,所以他的值需要調整。如果availability屬性沒有定義,那麼默認是全部時間內都可用的。Availability的範圍被限定到一個特定的CZML流中,所以對同一個對象在兩個不同的流中可以有不同的availability。在一個流中,只有定義在最後的那個availability起作用,其他的都會被忽略。在某一時刻,如果一個對象是可用的,那麼這個對象至少要有一個可用的屬性並且在此時間段內需要的屬性都要有定義(也就是獲取到了數據),不然Cesium就會等待數據直到接收到數據爲止。

擴展Extending CZML

可以給CZML增加自定義屬性,但是爲避免衝突,我們強烈建議你給你的自定義屬性加上你特有的前綴。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章