ArcGIS 切片的三種存儲形式
松散型
也就是我們常見的文件式的切片管理方式,將 Arcgis Server 切出來的切片圖片
按照行列號的規范,存儲在相應的文件夾
中。
早期緊湊型
將切好的切片轉化成.bundle
和.bundlex
的兩種文件格式存儲。
-
其中bundle文件用以存儲切片數據,bundlx 是 bundle 文件中切片數據的
索引文件
; -
一個 bundle 文件中最多可以存儲128×128(16384)個切片;
-
在 bundlx 文件中用
固定的字節數量
(5字節)標識一個切片在 bundle 文件中的狀態(偏移量);每個 bundlx 文件都是一樣的大小:81952字節,起始16字節和文件結束16字節與索引無關,剩余的81920字節數據以5個字節
的頻率重復,構成了一個對bundle文件的索引【注:16384 * 5 = 81920】;這5個字節標示了切片數據的偏移量。 -
在 bundle 文件中,每2個切片數據之間相隔了4個字節;這4個字節正好是以低位到高位的方式標示了后續這個切片數據的長度。
-
切片數據的長度(4字節)和數據偏移(5字節)是無符號的整數。
-
bundlx 文件的文件名,包含了切片的行列信息,而它所在的文件夾名稱(目錄名稱),則包含切片的級別信息。
因此,我們如果知道了一個切片的級別、行號、列號,就可以找到相應的 bundlx 文件,並通過 bundlx 首先找到bundle中切片內容的偏移,然后從bundle文件中取出4個字節的長度數據,再隨后根據這個長度讀取真實的切片數據。
假設已知切片的級別、行號和列號,求對應的 bundlx 文件:
目錄名:L開頭,並加上級別,級別不足2位的,高位補0,例如:L01,L19等。
文件名:R開頭,加上4位16進制數(行號組最小行號),
再加上字母C,最后加上4位16進制數(列號組最小列號),
例如:R0080C0080.bundlx
行號組最小行號、列號組最小列號 的計算:
(1)因為一個 bundle 文件中最多可以存儲 128×128 個切片,所以,
行號/128,向下取整,得到切片所在的“行號組的序數 r”(即第 r 組);
列號/128,向下取整,得到切片所在的“列號組的序數 c”(即第 c 組);
r * 128,得到所在行組的最小行號 rrrr;
c * 128,得到所在列組的最小列號 cccc;
(2)將 rrrr 和 cccc 轉成 16 進制數,並轉化為長度為4的字符串(不足4位時,高位補0);
找到 bundlx 文件:
(3)拼接出文件名:R{rrrr}C{cccc}.bundlx
(4)拼接出路徑:_alllayers\level\R{rrrr}C{cccc}.bundlx
--------------------------------
假設已知切片的級別、行號和列號,求對應的切片數據:
(1)求切片的序數:
行號 - 行號組最小行號(即:行號-rrrr),得到切片在當前行號組的序數 m;
列號 - 列號組最小列號(即:列號-cccc),得到切片在當前列號組的序數 n;
128 * m + n,得到切片在 bundle 文件中的序數 index(即:切片是 bundle 中總共的16384 張切片中的第 index 張切片);
(2)求切片的位置(偏移量):
因為在 bundlx 文件中,每張切片的位置信息用5字節表示,且頭部有16個起始字節,所以,前(16 + 5 * index)個字節需要忽略;之后的5字節是切片的位置信息,如下解析切片的位置信息:
偏移量 offset = 第0字節 + 第1字節 * 256 + 第2字節 * 256 * 256 + 第3字節 * 256 * 256 * 256 + 第4字節 * 256 * 256 * 256 * 256。
(3)求切片的長度:
因為切片之前4個字節,是切片的長度信息,所以 用 offset 之后的四個字節來計算切片的長度,如下:
切片長度 length = 第0字節 + 第1字節 * 256 + 第2字節 * 65536 + 第3字節 * 16777216
(4)讀取切片
offset 之后 length 個字節,就是切片的圖片流。
示例
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
private string _layerPath;
public void Draw()
{
_layerPath = @"D:\arcgisserver\directories\arcgiscache\ZJXZQ\圖層\_alllayers\L00";
if (!Directory.Exists(_layerPath))
{
throw new DrawWatermarkException("路徑不存在或無效:" + _layerPath);
}
//http://localhost:6080/arcgis/rest/services/ZJXZQ/MapServer/tile/0/201/168
//http://localhost:6080/arcgis/rest/services/ZJXZQ/MapServer/tile/0/203/170
int level = 0;
int startTileRow = 168;
int endTileRow = 170;
int startTileCol = 201;
int endTileCol = 203;
string levelStr = level.ToString();
if(levelStr.Length ==1)
{
levelStr = "0" + levelStr;
}
levelStr = "L" + levelStr;
string targetPath = @"D:\Targets\" + levelStr;
if (!Directory.Exists(targetPath))
{
Directory.CreateDirectory(targetPath);
}
int rowLimit = endTileRow + 1, colLimit = endTileCol + 1;
for (int row = startTileRow; row < rowLimit; row++)
{
for (int col = startTileCol; col < colLimit; col++)
{
int r = (int)(row / 128);
int c = (int)(col / 128);
int rrrr = r * 128;
int cccc = c * 128;
string rHex = rrrr.ToString("x");
if (rHex.Length < 4)
{
int temp = 4 - rHex.Length;
for (int i = 0; i < temp; i++)
{
rHex = "0" + rHex;
}
}
string cHex = cccc.ToString("x");
if (cHex.Length < 4)
{
int temp = 4 - cHex.Length;
for (int i = 0; i < temp; i++)
{
cHex = "0" + cHex;
}
}
FileStream bundlxFs = GetFile(rHex, cHex, true);
if (bundlxFs != null)
{
int m = row - rrrr;
int n = col - cccc;
int index = m * 128 + n;
long offset1 = 16 + 5 * index;
if (offset1 > bundlxFs.Length)
{
continue;
}
byte[] bytesIndex = new byte[5];
bundlxFs.Seek(offset1, SeekOrigin.Begin);
bundlxFs.Read(bytesIndex, 0, 5);
long offset = 0;
for (int i = 0; i < bytesIndex.Length; i++)
{
long temp = bytesIndex[i] & 0xff;
if (temp != 0)
{
for (int j = 0; j < i; j++)
{
temp *= 256;
}
offset += temp;
}
}
FileStream bundleFs = GetFile(rHex, cHex, false);
byte[] bytesLength = new byte[4];
bundleFs.Seek(offset, SeekOrigin.Begin);
bundleFs.Read(bytesLength, 0, 4);
int length = 0;
for (int i = 0; i < bytesLength.Length; i++)
{
int temp = bytesLength[i] & 0xff;
if (temp != 0)
{
for (int j = 0; j < i; j++)
{
temp *= 256;
}
length += temp;
}
}
if (length > 0)
{
byte[] bytesImage = new byte[length];
bundleFs.Read(bytesImage, 0, length);
MemoryStream ms = new MemoryStream(bytesImage);
Image image = Image.FromStream(ms);
image.Save(targetPath + "\\" + row + col + ".png", ImageFormat.Png);
image.Dispose();
ms.Close();
}
}
}
}
}
private Dictionary<string, FileStream> _bundlxFiles = new Dictionary<string, FileStream>();
private Dictionary<string, FileStream> _bundleFiles = new Dictionary<string, FileStream>();
private FileStream GetFile(string rHex, string cHex, bool isBundlx)
{
string extension = isBundlx ? "bundlx" : "bundle";
Dictionary<string, FileStream> temp = isBundlx ? _bundlxFiles : _bundleFiles;
string fileName = string.Format("R{0}C{1}.{2}", rHex, cHex, extension);
if (temp.ContainsKey(fileName))
{
return temp[fileName];
}
string file = string.Format("{0}\\{1}", _layerPath, fileName);
if (File.Exists(file))
{
FileStream fileStream = new FileStream(file, FileMode.Open);
temp.Add(fileName, fileStream);
return fileStream;
}
return null;
}
10.3以后的緊湊型
將切好的切片轉化成.bundle
的格式來存儲。
- 切片的索引、切片的偏移和切片的圖片流都必然包含在這一
.bundle
文件中; - 頭信息:
.bundle
文件起始 64 字節 是 bundle 的文件頭信息; - 位置信息:頭信息之后,記錄了 16384 張切片的位置;每個位置信息,用8個字節表示;僅前4字節有用,從低位到高位;后4字節可忽略;
- 圖片流信息:位置信息之后,是圖片流信息;每個切片圖,先以4字節記錄切片的長度,而后緊跟圖片的流信息。
.bundle
文件的文件名以及所在文件夾的名稱,包含切片的級別、行列信息。
假設已知切片的級別、行號和列號,求對應的 bundle 文件:
求取方式同 “假設已知切片的級別、行號和列號,求對應的 bundlx 文件”。
--------------------------------
假設已知切片的級別、行號和列號,求對應的切片數據:
(1)求切片的序數:
行號 - 行號組最小行號(即:行號-rrrr),得到切片在當前行號組的序數 m;
列號 - 列號組最小列號(即:列號-cccc),得到切片在當前列號組的序數 n;
128 * m + n,得到切片在 bundle 文件中的序數 index(即:切片是 bundle 中總共的16384 張切片中的第 index 張切片);
(2)求切片的位置(偏移量):
因為每張切片的位置用8字節表示,且頭信息占64字節,所以,前(64 + 8 * index)個字節需要忽略;之后的8字節是切片的位置信息(僅前4字節有用),如下解析切片的位置信息:
偏移量 offset = 第1字節 + 第2字節 * 256 + 第3字節 * 65536 + 第4字節 * 16777216。
(3)求切片的長度:
因為切片之前4個字節,是切片的長度信息,所以 用(offset-4)之后的四個字節來計算切片的長度,如下:
切片長度 length = 第0字節 + 第1字節 * 256 + 第2字節 * 65536 + 第3字節 * 16777216
(4)讀取切片
offset 之后 length 個字節,就是切片的圖片流。