基于ArcSDE的影像数据管理-解决篇(转载)
疑惑篇中簡單介紹了基于ArcSDE的影像數據管理的基本方法、策略及其缺陷。那么要想基于ArcSDE的Raster Catalog實現對影像數據的任意范圍查詢,并且在跨圖幅的情況下做到無縫拼接該怎么實現呢?
我是這樣做的。
首先說說邏輯上的思路。
問題的輸入和輸出都是很明確的。
輸入:BBox, w
說明一下,BBox是Bounding Box的縮寫,是以地理坐標表示的查詢范圍,由left, right, top和bottom四個參數組成;w為視口的大小,即最終顯示在Web上(我說過的哦,這個影像數據源包裝器是一個WebGIS項目服務器端的一部分)給用戶看的地圖窗口的大小,由width和height兩個參數組成。
輸出:與查詢范圍精確匹配的一幅w大小的jpeg圖像文件
再說明一下,jpeg圖像是經過有損壓縮的,數據量比較小,我程序中每次得到的圖像文件數據量一般在50~100K左右,在Internet上傳輸是可以接受的。這種圖像不帶坐標信息,也無法進行二次處理,就是純粹的一張圖片,很純的那種。這不太符合OGC的WCS規范,因為我現在只能提供jpeg圖像;但卻有點像WMS,如果把接口改改的話就算勉強符合。
那么根據ArcSDE中影像數據的管理和存儲方式,從邏輯上我按照以下步驟完成功能:
第一步:根據BBox與w計算顯示比例尺s;
第二步:根據s計算提取數據所在的金字塔級別l;
第三步:通過一次散列,求出與BBox相交的圖幅序列ImageList(I1, I2, I3…);
第四步:將BBox分解到ImageList中的每一個圖幅元素上成為子查詢序列subBBox(sb1, sb2, sb3…);
第五步:在l級金字塔上,通過第二次散列,在Ii上求出與sbi相交的圖塊序列TileListi(B1, B2, B3…);
第六步:提取第三步得到的全部圖塊的數據;
第七步:將第四步得到的數據還原成位圖文件
嗯,總體上就是這個樣子。下面逐步細說。
第一、二步挺簡單不用解釋,第三步開始就有問題了。
在疑惑篇中我說了,SDE認為Raster Catalog就是一“相冊”,其中存儲的影像是沒有什么關系的,更不會為它們建立什么索引結構。這樣要想實現第三步中的散列就要先為Raster Catalog中的圖幅建立索引表,這個表我建在了影像數據所在的表空間里,名字就叫IMAGEINDEX,里面為所有圖幅按照它們之間的拓撲關系建立了格網索引——每個圖幅一個行號、一個列號。但這時就又有問題了,基礎數據一共460個圖幅,全拼起來并不是一個大長方形,而是基本與目標區域邊界吻合的鋸齒形。那么若建立格網索引就必然會有一些空的地方,就是說會有一些格網號并沒有實際的圖幅與之對應。那么對于這些空的地方,我是將它們也寫入索引表,但標識其目標為0呢還是直接跳過它們不管呢?我選擇了前者。這樣,一個如下面示意圖所示的格網索引就建起來了,第一次散列的問題也就可以解決了。而第五步中的第二次散列由于SDE對每個圖幅中的圖塊是做了格網索引的(疑惑篇中有介紹),所以易于實現。
下面是最關鍵的第三到第五步,在這三步中,我要完成圖幅的去零,以支持跨幅查詢時的無縫拼接。因為我可以通過SDE API得到每個圖幅原來的大小,即未補零時的大小,還可以得到圖塊的大小,通過它們就可以算出圖幅邊緣補零部分的寬度和高度,當查詢涉及到那些帶有零元的圖塊時,我就能知道其中有效信息部分的大小,進而只讀取它們,跳過零元。但這帶來一個小問題,就是圖塊的大小出現了不均勻。為此,我采用了一種比較笨的辦法,就是讓ImageList中的每個圖幅Ii都自己記錄自己的TileListi中每個圖塊的大小。這樣在第六步提取數據和第七步恢復圖像的時候就可以按照每個圖塊的實際大小來讀寫,而那些補的零就都可以去死了。這就是我去零的基本思路。
提取每個圖塊數據時,外層循環對ImageList的遍歷順序是Z序的,而內層循環在每個圖幅內部對TileList的訪問順序也是Z序的。與之對應的,在恢復圖像的時候,在每個圖塊內部繪制像素的順序、在每個圖幅內繪制圖塊的順序、繪制圖幅的順序也都是Z序的。如下圖所示:
最后一步得到位圖還沒完,需要再稍做加工,因為它的地理范圍比BBox要大(因為提取數據的最小單位是圖塊而不是像素,這個應該好理解吧),所以要將它中間BBox對應的那一部分切出來。這還沒完,切出來的部分可能不是w大小(這是因為金字塔索引是離散的,按比例尺拿數據只能就近取金字塔中的某一級),但不會差很遠,所以還要稍微拉伸才行。這樣兩步加工之后得到的才是最終產品。
以上就是我整個思路的要點概況,其中關鍵的就在去零實現無縫拼接,不知道說清楚了沒有。其實辦法也挺初級的,疑惑篇的評論中bluntsword一下就給出解決思路了。本人比較笨,唉~~
下面再說說具體實現吧。事先說明一下,我的OOA/D經驗相當匱乏,具體解決方案給出之后大家可能會覺得十分齷齪而不堪如目,其中可能會有不計其數的違背OO原則的地方,但請大家相信這也絕非我本意。諸位如果實在看不下去想罵兩句的話就不用忍了。如果有一天我能看到某介紹設計方案的文獻上有我的設計——作為反面教材,那我也無怨無悔。不過雖然它很丑,但它在我們整個WebGIS系統中還算是比較穩定的一塊。
SDE C-API是C寫的(廢話!),其中使用了少量C++的特性,不支持我相對比較熟悉的.net,所以我選擇了VC6來實現。
這個影像數據包裝器只負責從影像數據源中抽取數據,形成圖片,并傳回給GIS服務器,所以其實它連界面都不需要,但我建了個對話框應用,上面擺一個Edit,用于輸出運行時的一些狀態信息。
為實現功能,我建了一個CMyRaster類,它的內部完成了我想要的全部事情(這是不是一個典型的“全能類”啊,好怕怕~~)。其結構如下:(由于不能貼C++代碼,所以這里暫且用C#格式的,下同)
class?CMyRaster??
{
public:
????static?WCHAR*?ToWChar(char?*?str);?//?在GDI+中,有關字符的參數類型全部都是WCHAR類型,該函數是將傳統字符串進行轉換
????static?int?DisconnectSDE(SE_CONNECTION?*connection);?//?斷開與SDE的連接
????static?int?ConnectSDE(char?*SDE_servername,?char?*SDE_service,?char?*SDE_instance,?char?*SDE_user,?char?*SDE_password,?SE_CONNECTION?*connection);?//?連接SDE
//????這里應該提供一個ConnectSDE的重載版本用于連接SQLServer服務器
????int?GetRaster(const?SE_CONNECTION?connection,?const?_ConnectionPtr?ADOConn,?double?left,?double?right,?double?top,?double?bottom,?long?userScale,?CString?filename);?//?提取影像數據并生成圖片的核心方法
????CMyRaster();
????virtual?~CMyRaster();
private:
????int?m_QueryRasterRight;?//?與查詢范圍相交的圖幅序列的格網索引范圍
????int?m_QueryRasterLeft;
????int?m_QueryRasterTop;
????int?m_QueryRasterBottom;
????RASTER_METADATA?*m_RastersMetadata;?//?圖幅元數據數組,核心變量????
????char?strTableName[SE_QUALIFIED_TABLE_NAME];?//?Raster?Catalog的表名????
????char?strColName[SE_MAX_COLUMN_LEN];?//?數據的列名,一般就是"IMAGE"????
????int????m_QueryPyramidLevel;?//?查詢所在的金字塔級別????
????SE_INTERPOLATION_TYPE?m_Interpolation;?//?插值的方法????
????long?*m_ScaleByLevel;?//?金字塔該級別上的比例尺????
????long?m_NumOfBands;?//?波段數
????long?m_PyramidHeight;?//?金字塔高度
????IMAGE_EXTENT?m_DefaultTileSize;?//?tile大小的預設值????
????SE_ENVELOPE?m_WholeExtent;?//?最大的全圖范圍
????int?m_UserMapWidth;?//?用戶地圖區的寬度
????int?m_UserMapHeight;?//?用戶地圖區的高度
????int?Get_RasterMetadata_by_BBox(const?SE_CONNECTION?connection,?const?_ConnectionPtr?ADOConn,?double?left,?double?right,?double?top,?double?bottom,?long?userScale);?//?獲取SDE中raster圖層的元數據
int?Get_RasterData_from_SDE(const?SE_CONNECTION?connection,?const?_ConnectionPtr?ADOConn);?//?從數據源中提取影像數據
int?Write_to_BMP_file(double?left,?double?right,?double?top,?double?bottom,?CString?filename);?//?將影像數據恢復成位圖文件
LONG?GetScale(LFLOAT?ras_xLength,?LONG?rasWidth,?BOOL?isBigFont);?//?計算比例尺
????int?Get_RasterMetadata_by_GridIndex(int?rasrownbr,?int?rascolnbr,?int?*rasIndex);?//?根據格網索引取圖幅
????int?GetImageCLSID(const?WCHAR*?format,?CLSID*?pCLSID);?//?得到格式為format的圖像文件的編碼值,訪問該格式圖像的COM組件的GUID值保存在pCLSID中
????int?Get_PyramidLevel_by_scale(long?userScale,?int?*pyramidLevel);?//?根據比例尺獲取金字塔索引的級別
int?Get_Result_extent(SE_ENVELOPE?*resultExtent);?//?獲取結果位圖的地理范圍
};
從CMyRaster類的結構可以看出我將圍繞
RASTER_METADATA *m_RastersMetadata;
這個動態數組來做文章。RASTER_METADATA是一個定義在CMyRaster外面的結構體,細節如下:
typedef?struct?raster_metadata
{
????//?下面是每個raster對應的唯一屬性
????int????m_RasterID;????//?Raster_ID
????int????m_RasterRowNum;????//?圖幅格網索引行標號
????int????m_RasterColNum;????//?圖幅格網索引列標號
????int????m_QueryTileLeft;?//?查詢范圍對應的tile最左一列的列號????
????int????m_QueryTileRight;?//?查詢范圍對應的tile最右一列的列號????
????int????m_QueryTileTop;?//?查詢范圍對應的tile最上一行的行號????
????int????m_QueryTileBottom;?//?查詢范圍對應的tile最下一行的行號????
????SE_ENVELOPE????m_RasterExtent;?//?全圖的地理坐標范圍????
????IMAGE_EXTENT?m_ImageSize;?//?全圖的像素范圍????
????IMAGE_EXTENT?m_QueryImageSize;?//?與查詢范圍相交的像素范圍????
????IMAGE_EXTENT?*m_QueryTilesSize;?//?查詢范圍對應的每個tile的大小(不帶零)
????
????//?下面是每個raster中每級金字塔對應的屬性,均為數組,其大小在運行時才能確定????
????IMAGE_EXTENT?*m_ImageSizeByLevel;?//?金字塔該級別上像素范圍????
????IMAGE_EXTENT?*m_ZerosSizeByLevel;?//?金字塔該級別上補零部分的寬與高????
????SE_ENVELOPE????*m_RasterExtentByLevel;?//?金字塔該級別上的地理坐標范圍????
????int????*m_TotalTileColByLevel;?//?金字塔該級別上以tile為單位的列數????
????int????*m_TotalTileRowByLevel;?//?金字塔該級別上以tile為單位的行數
}RASTER_METADATA;
公有方法GetRaster(…)是完成功能的“主”方法,其中就這么三句話:
//?通過查詢范圍獲取與查詢范圍相交的全部圖幅的元數據
????int?ret?=?Get_RasterMetadata_by_BBox(connection,?ADOConn,?left,?right,?top,?bottom,?scale);
????if?(ret?!=?SE_SUCCESS)
????????return?-1;
????
????//?獲取上面求出的圖幅序列的像素數據
????ret?=?Get_RasterData_from_SDE(connection,?ADOConn);
????if?(ret?!=?SE_SUCCESS)
????????return?-1;
????//?將存儲在臨時文件中的像素數據還原成位圖
????ret?=?Write_to_BMP_file(left,?right,?top,?bottom,?filename);
????if?(ret?!=?SE_SUCCESS)
????????return?-1;
邏輯設計中的第一、二步都有與其對應的函數實現。第三至第五步則化為GetRaster(…)中的
//?通過查詢范圍獲取與查詢范圍相交的全部圖幅的元數據
????int?ret?=?Get_RasterMetadata_by_BBox(connection,?ADOConn,?left,?right,?top,?bottom,?scale);
????if?(ret?!=?SE_SUCCESS)
????????return?-1;
Get_RasterMetadata_by_BBox函數用于填充m_RastersMetadata數組,以供后面使用。
第六步提取數據由
//?獲取上面求出的圖幅序列的像素數據
????ret?=?Get_RasterData_from_SDE(connection,?ADOConn);
????if?(ret?!=?SE_SUCCESS)
????????return?-1;
實現,其依據m_RastersMetadata數組中的數據,通過SDE API提供的讀取圖塊的API來按塊提取數據。取出的數據是以字節流的形式存入磁盤臨時文件,因為公有R、G、B三個波段的數據,所以相應的就有三個臨時文件。
第七步重新繪制圖像則由 //?將存儲在臨時文件中的像素數據還原成位圖
????ret?=?Write_to_BMP_file(left,?right,?top,?bottom,?filename);
????if?(ret?!=?SE_SUCCESS)
????????return?-1;
完成,繪制圖像我使用的是GDI+,感覺就是兩個字:簡單,好用。
在這個影像包裝器子系統中,最耗時的環節并不是提取數據和還原圖像,而是連接SDE。也不知是什么原因,當后臺數據庫是Oracle的時候,這個連接動作總是如此如此如~~~此的慢。第一次連接一般要一分鐘左右,以后每次也要幾十秒。如果每次查詢都要連接SDE,那后果簡直不可想象(我曾經做過的一個基于MO的矢量數據發布系統就是每次都要連接,以至于做一次地圖放大或者平移都要等一分多鐘,唉,實在對不起用戶)。所以我把連接SDE的動作放在了InitDialog中,并在整個程序生命期內保持,直到退出時才斷開。
實現方面也就是這樣了。
實測結果,我的影像包裝器響應一次查詢大約要3到6秒,平均不到4秒,不知道一般的指標是多少。我感覺這個速度應該還可以接受吧。
要交代的基本上就是這些了,大家如果看出了我的從設計到實現中什么地方感覺不爽就盡管說,我一定認真聽取,虛心接受,并不斷重構自己的代碼。另外,我不太懂設計模式(那本GoF的書看不下去,不知道還有沒有別的通俗一點的),不知道我做的這個部分從模式的角度看有沒有更好的方案,還請大家指點。
本文方法初級,方案拙劣,代碼不怎么規范,文字也比較晦澀,圖片又比較丑陋,還不會用UML工具展示類圖,浪費各位看官的時間了,罪過罪過。
p.s.項目已經交工幾個月了,我早想借blog總結一下,但苦于博客園是討論.net技術的而一直沒敢post,不過后來幸運天屎從天而降砸到了我的頭上——我發現了WebGIS團隊!這才促使我重新看一遍代碼并總結之,否則好多想法恐怕再過過就隨屎尿而去了……
感謝博客園,感謝WebGIS,感謝CCTV,感謝ChannelV…
轉載于:https://www.cnblogs.com/EggKiller/articles/1079553.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的基于ArcSDE的影像数据管理-解决篇(转载)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 空间直角坐标系右手法则(空间直角坐标系)
- 下一篇: 尸妖1981下载(尸妖国语完整版在线观看