fepk檔案格式說明

經緯視界發表於2023-09-28

 

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

 

 

相關文章