1 衛星影像金字塔分塊原理說明
通常我們在工作中使用的衛星影像資料,輕則幾百M,重則幾百個G甚至上TB級。影像資料太大,是大家經常會遇到的一個問題,尤其是想下載一個省以上資料的時候該問題尤為突出。那麼該問題是否有一個比較好的解決方案呢?
以全球為例,我們以19級為例,共有2^18 * 2^17 張瓦片,如此多的瓦片會讓磁碟愈來愈慢,同時也無法維護。
當影像範圍比較大時,我們可以採用金字塔分塊的方式進行管理,系統會自動將大範圍分成若干個塊,且塊與塊之間是可以無縫拼接的。
一般情況我們選擇全球前10級別作為基礎級別,因資料量不大(1G)左右,後續以10級作為基礎級別,全球19級別資料被劃分為 2^8 * 2^7(512 * 256)個塊。每個塊中包含了256 * 256 張小瓦片。
1.1 Fepk檔案命名規則
檔案說明(包含索引與資料兩個檔案,檔案必須都配套才可以正常使用):
*.fepk :檔案中儲存具體的瓦片資料。
*.fepk.idx :檔案中儲存的瓦片的索引資訊,當給定一個瓦片編號後,可以根據編號計算出來瓦片存在索引中的資訊(大小,以及資料在8-174-138.fepk中的位置)。
名稱命名規
以8-174-138.fepk為例:其中8是級別,174是列號,138是行號,檔案中儲存了8-174-138瓦片裂分出來的所有瓦片資料。
2 .fepk檔案格式說明
我們一般不會直接採用瓦片作為管理單元,會把一個塊作為管理單元,把資料劃分為索引檔案與資料檔案,如下所示:
資料檔案:world.fepk
索引檔案:world.fepk.idx
2.1 索引檔案
表 1檔案說明
檔案頭 |
欄位 |
值 |
檔案頭 |
char szMagic[20] |
fe.tile.store.data20位元組 |
uint version |
版本號4位元組 |
|
uint typeId |
資料型別 enum PKType { PK_IMAGE , PK_DEM, PK_VEC, PK_QXSY, PK_USER, };4位元組 |
|
uint wgs84 |
是否是wgs84經緯投影4位元組 |
|
uint flag |
4位元組 |
|
uint64 timestamp |
時間戳8位元組 |
|
real2 vStart |
經緯度最小範圍8*2位元組 |
|
real2 vEnd |
經緯度最大範圍 8*2位元組 |
|
LevSnap levOff[24] |
級別索引,8 * 24 位元組 |
|
char _reserve[240] |
保留 |
|
級別1 |
int2 _start |
2 * 4位元組,瓦片最小行列號 |
int2 _end |
2 * 4位元組,瓦片最大行列號 |
|
uint64 _offset |
8位元組 |
|
uint64 _dataSize |
8位元組 |
|
uint _lev |
4位元組 |
|
char _reserve[216] |
216位元組 |
|
瓦片資料索引矩陣資料PKTLHeader |
N * PKTLHeader N = (_end.x - _start.x + 1) * (_end.y - _start.y + 1) |
|
|
PKTLHeader |
|
級別2 |
|
|
級別3 |
|
|
級別… |
|
|
PKTLHeader定義:
PKTLHeader定義: struct PKTLHeader { /// 有無資料標記,即伺服器上是否有該資料 0,無,1,有 uint64 _data:2; /// 在本檔案中是否已經儲存 0,無,1,有 uint64 _stored:2; /// 狀態, uint64 _state :6; /// 資料地址,使用50個bit最大 2^54 /// 單個檔案最大16 K T uint64 _offset : 54; /// 如果該值!= 0xFFFF,則有效,否則無效, /// 使用該欄位的意義在於解決網路讀取問題,比如在雲盤上 /// 先讀取索引,如果沒有資料大小,或者資料大小儲存在資料檔案中,則需要 /// 再次訪問資料檔案,才可以得大小,增加額外的IO,同時兼顧大小,該變數最大可以儲存64K /// 如果超過了64K,那麼一樣的需要訪問資料檔案獲取大小 ushort _dataSize; };
共計10自位元組
LevSnap定義:
struct LevSnap { uint64 _lev:8; uint64 _offset:56; };
共計8位元組
2.2 資料檔案檔案
檔案頭 |
欄位 |
值 |
檔案頭 |
char szMagic[20] |
fe.tile.store.data20位元組 |
uint version |
版本號4位元組 |
|
uint typeId |
資料型別 enum PKType { PK_IMAGE , PK_DEM, PK_VEC, PK_QXSY, PK_USER, };4位元組 |
|
uint wgs84 |
是否是wgs84經緯投影4位元組 |
|
uint flag |
4位元組 |
|
uint64 timestamp |
時間戳8位元組 |
|
real2 vStart |
經緯度最小範圍8*2位元組 |
|
real2 vEnd |
經緯度最大範圍 8*2位元組 |
|
LevSnap levOff[24] |
級別索引,8 * 24 位元組 |
|
char _reserve[240] |
保留 |
|
資料0 |
Int4 |
4*4位元組,行號,列號,級別,大小 |
資料 |
|
|
資料1 |
Int4 |
4*4位元組,行號,列號,級別,大小 |
資料 |
|
|
資料… |
Int4 |
4*4位元組,行號,列號,級別,大小 |
資料 |
|
3 如何使用資料
3.1 解壓成標註金字塔瓦片
使用者可以透過FEPKUNPack.exe 解壓程式,將資料加壓標準的金字塔瓦片,然後即可使用,匯出後如下所示。
匯出後可以方便的被osgEarth,cesium,argis,fastearth等軟體直接載入。
缺點: 匯出後,佔用磁碟大小比未解壓前大50%。
匯出後,維護困難,因為檔案很多,複製能都受到影響。
資料截圖
3.2 API讀取瓦片
使用SDK/API訪問資料,為了方便大家使用,避免資料解壓,可以使用FEPKReadApi
SDK讀取資料,SDK使用C語言編寫,介面如下所示
extern "C" { /// <summary> /// 開啟檔案函式,可以開啟索引也可以開啟資料檔案 /// </summary> /// <param name = "fileName">檔名稱</param> /// <return>0:失敗,否則成功</return> FEPKFile fepkOpenFile(const char* fileName); /// <summary> /// 讀取索引資料函式 /// </summary> /// <param name = "file">索引檔案指標</param> /// <param name = "x">列號</param> /// <param name = "y">行號</param> /// <param name = "z">級別</param> /// <param name = "header">返回檔案頭資訊</param> /// <return>true:false</return> bool fepkReadHeader(FEPKFile file,int x,int y,int z,FEPHHeader* header); /// <summary> /// 根據檔案頭資訊讀取檔案大小(瓦片資料大小) /// </summary> /// <param name = "file">索引檔案指標</param> /// <param name = "header">檔案頭資訊</param> /// <param name = "pSize">輸出檔案大小</param> /// <return>true:false</return> bool fepkReadDataSize(FEPKFile file,const FEPHHeader* header,uint* pSize); /// <summary> /// 讀取瓦片資料函式 /// </summary> /// <param name = "file">索引檔案指標</param> /// <param name = "header">檔案頭資訊</param> /// <param name = "pBuf">輸入緩衝區大小</param> /// <param name = "nBufLen">緩衝區長度</param> /// <return> -1:失敗,0:無資料,>0 資料的真實大小</return> int fepkReadData(FEPKFile file,const FEPHHeader* header,void* pBuf,uint nBufLen); /// <summary> /// 關閉檔案 /// </summary> void fepkCloseFile(FEPKFile file); /// <summary> /// 從一個資料夾中讀取瓦片的資料頭,以及瓦片的大小 /// </summary> /// <param name = "path">目錄組,以null結束</param> /// <param name = "x">瓦片的編號</param> /// <param name = "y">瓦片的編號</param> /// <param name = "z">瓦片的編號</param> /// <param name = "header">檔案頭資訊</param> /// <param name = "pSize">瓦片大小</param> /// <return>返回開啟的檔案</return> FEPKFile fepkReadTileHeader(const char** path,int x,int y,int z,FEPHHeader* header,uint* pSize); /// <summary> /// 從一個資料夾中讀取 /// </summary> /// <param name = "file">檔案控制程式碼</param> /// <param name = "header">檔案頭資訊</param> /// <param name = "pBuf">輸入/輸出,從fepkReadTileHeader讀取</param> /// <param name = "nBufLen">輸入,從fepkReadTileHeader讀取</param> /// <return>返回讀取的長度-1,失敗,0,檔案內部錯誤,其他讀取的長度</return> int fepkReadTileData(FEPKFile file,const FEPHHeader* header,void* pBuf,uint nBufLen); }
使用說明:
#include "FEPKReaderApi.h" #include <stdio.h> /// 如果是SDK,非原始碼方式,則需要因入庫 /// #pragma comment(lib,"FEPKReader.lib") int main(int, char**) { /// 1. 開發檔案 FEPKFile file = fepkOpenFile("D:\\FE\\data\\fepk\\world.fepk"); if (file == nullptr) { return 0; } FEPHHeader header; uint nSize = 0; /// 2. 讀取給定瓦片編號的檔案頭資訊 /// 如果返回false,說明當前檔案中沒有給定的瓦片資料 if (!fepkReadHeader(file, 0, 0, 0, &header)) { return 0; } /// 3. 讀取資料大小 if (!fepkReadDataSize(file, &header, &nSize)) { return 0; } /// 申請空間 char* pBuf = new char[nSize]; /// 4. 讀取資料 if (!fepkReadData(file, &header, pBuf, nSize)) { printf("read ok!\n"); } /// 5. 釋放記憶體 delete[]pBuf; /// 6. 關閉檔案 fepkCloseFile(file); return 0; }