ArcGIS 切片的三种存储形式

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 个字节,就是切片的图片流。
posted @ 2022-03-30 19:13  误会馋  阅读(1356)  评论(0编辑  收藏  举报