首先介紹一下ArcGIS10.0的緩存機制:
切片方案
切片方案包括緩存的比例級別、切片尺寸和切片原點。這些屬性定義緩存邊界的存在位置,在某些客戶端中疊加緩存時匹配這些屬性十分重要。圖像格式和抗鋸齒等其他屬性也會寫入切片方案,但對於客戶端應用程序能否成功疊加切片沒有影響。
切片方案原點
切片方案原點是指切片方案格網的左上角,默認原點為地圖文檔定義的坐標參考的左上點。原點不一定代表創建切片的起始點;只有在達到地圖全圖范圍時才是這樣。進行緩存時使用公用切片方案原點可確保所創建的緩存能夠在 Web 應用程序中相互疊加。注意,切片是從地圖的全圖開始切的,不是從切片方案原點(切片方案原點落在地圖原點右下方另算)。
另外一點,我經過試驗,發現切片的行列號是從0開始算起的。
![]() |
切片寬度和高度
切片的默認寬度和高度為 256 像素。

DPI
每英寸點數 (Dot Per Inch) ,是指服務器將生成的緩存切片的分辨率。即生成的圖片每英寸長度內的像素點數。默認為96。
切片方案緩存文件結構
緩存目錄 -> 地圖服務名 ->地圖數據框名稱(DataFrame) ->如果是所有圖層一起切割就是_alllayers,否則就是各個圖層的名稱 -> 各比例尺等級文件夾。如下圖

地圖數據框文件夾里放着conf.cdi和conf.xml兩個標識緩存范圍、以及切片方案的配置信息:

conf.cdi存儲了切片的范圍:

conf.xml存儲了切片方案配置信息:
TileOrigin表示切片方案原點。
TileCols和TileRows表示單張切片所占的像素長度。
DPI表示生成切片的一英寸長度的像素數。
LODInfos里則存儲了切片的各級信息。
PacketSize表示單個bundle文件(下一小節將介紹)里存儲的行/列數。
LODInfo的Resolution表示的是地圖上每個像素表示的實際長度(地圖單位)。比如說50萬的比例尺,96的dpi,可以這么計算:
500000 / 100 * 2.54 / 96 = 132.2913125052919

bundle和bundlx文件
bundle文件中存儲的是圖片文件,bundlx文件中則存儲了各個圖片文件在bundle文件中的偏移量。
命名規則
bundle文件的命名都是:R數字C數字。R代表起始的Row,C代表起始的Column。數字均為16進制。
行和列如果不滿4位,則前面補0,位數多了不限。如R22e80C14400,表示這個bundle文件的起始切片是0x22e80行0x14400列。
bundlx文件結構
每個bundlx文件的大小都是81,952 字節(我這里PacketSize是128),前面16字節 + 每個圖片偏移量5字節 * (128 * 128)個切片 + 結尾16字節。
這里注意一下,不管是bundlx還是bundle文件里寫位置信息都是從低位到高位寫的,比如說你看見這樣一段代表位置的字節:10 32 a8 d7 54,這個代表的就是16進制的0x54d7a83210
我比較了一下各個bundlx文件,發現起止16字節都一樣,開始和結束字節分別是:
byte[] bdxBts = new byte[16] { 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00 };
byte[] bdlxEndBts = new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
bundlx文件切片的寫入順序是按列寫入的。也就是說,先是1列1行、1列2行……2行1行、2列2行……這樣寫的位置。還拿R22e80C14400來說:
先是開始16字節,然后5個字節22e80行14400列、5個字節的22e81行14400列……
bundle文件結構
這個文件我比較了好半天才找到這些規則。
首先是文件的開頭,從0x00到0x3b的這些位置都是bundle文件的描述信息:(還是以R22e80C14380.bundle為例)

這里多謝不做懶人的改正:
“
為了讓arcgis能讀取我們生成的緊湊型格式,bundle文件的前60個字節的數據,還需要完善。
其中:
8-11位,表示第一個非空,非全透明圖片的大小
16-19位,表示非空文件個數*4
”
開頭以后緊接着從0x3c開始就定入圖像文件。
但如果某一處不在地圖的全圖范圍內,也就是說我這個bundle文件中是包含很多塊切片的,其中有好多是正好是空的,沒有圖像。這時會怎么辦呢,難不成全寫成空Image存進去?
開始我是這么寫的,但寫了一個就感覺不對了,一級的切片里一個bundle幾十兆大小了,后來我比對發現,所有bundle文件從0x3c到0x1003c都是空的,正好=4*16384個字節。然后找到arcgis生成的bundlx文件驗證一下,第一級的開始一列切片正好都是空的,bundlx里的偏移量是從0x3c開始的。也就是說,bundle文件里從0x3c->0x1003c這些位置是為空切片預留了偏移位置,依次按照bundlx里的位置順序開始寫切片,如果遇到空的,就指向這里的空位置段,比如第r行第c列切片是空的,對應的位置就是 0x3c + ((c - colStart) * this.m_packetSize + (r - rowStart)) * 4。
非空切片在bundle文件里是從0x1003c開始寫的。
找到非空切片存儲的偏移位置,接下來的4個字節是該image的長度,然后在這4個字節之后取這些長度的字節就組合成了一副圖像,如果要查找的話返回即可。
簡單寫入算法
讀取時直接按照level和row、column計算出bundle文件名,按照上面的計算方法找到對應Image返回即可,這里只說明一下我自己的切片的方法。針對單個LODInfo
- 計算map的Tile范圍
long ltRow = (int)((this.m_tilingOriginY - m_mapMaxY) / (info.Resolution * this.m_tileHeight));
long ltCol = (int)((m_mapMinX - this.m_tilingOriginX) / (info.Resolution * this.m_tileWidth));
//右下角的切片行列號 9.0 = 第9個是結束
long rbRow = (int)((this.m_tilingOriginY - m_mapMinY) / (info.Resolution * this.m_tileHeight));
long rbCol = (int)((m_mapMaxX - this.m_tilingOriginX) / (info.Resolution * this.m_tileWidth)); - map跨越的bundle文件范圍
//左上角的bundle文件開始行列號
long ltBundleRow = this.m_packetSize * (ltRow / this.m_packetSize);
long ltBundleCol = this.m_packetSize * (ltCol / this.m_packetSize);//右下角(最后一個)的bundle文件開始行列號(由於上面的第9.0行已經歸為第9行,所以直接算就是對的)
long rbBundleRow = this.m_packetSize * (rbRow / this.m_packetSize);
long rbBundleCol = this.m_packetSize * (rbCol / this.m_packetSize); - 寫單個bundle文件
針對單個bundle文件,先寫好bundle和bundlx的起始段字節,找好四角對應的實際坐標點
按列寫切片
如果切片在圖外,寫到0x3c-0x1003c的位置,否則把地圖控件調整到單個切片的尺寸,Map縮放到該范圍,導出圖片
把此時的偏移量寫入bundlx文件,img寫入bundle - 寫完結束部分后修改bundle中的0x18--0x1b的文件大小信息。
經測試,我用DotSpatial的DotSpatial.Controls.Map控件 按照已經用ArcGIS發布好的服務的參數來配置 來切割一副地圖,切割好后刪除ArcGIS的切片並替換成我自己切割的,展現效果完全相同。
切片多時速度還是比較慢的。為了提速,可以對每級切片都新建一個線程,為了監視進度也可以自定義一個進度變化的事件,每次寫入一個切片就計算進度並觸發事件。為了提高IO效率還可以用Buffer等,這里測試讀寫文本文檔的速度還是不怎么慢的。。。

