天地图如何转成 ARCGIS 紧凑型切片
1. 切片方案
切片方案包括缓存的比例级别、切片尺寸和切片原点。这些属性定义缓
存边界的存在位置,在某些客户端中叠加缓存时匹配这些属性十分重要。图
像格式和抗锯齿等其他属性也会写入切片方案,但对于客户端应用程序能否
成功叠加切片没有影响。
3.1 切片方案原点
切片方案原点是指切片方案格网的左上角,默认原点为地图文档定义的
坐标参考的左上点。原点不一定代表创建切片的起始点;只有在达到地图全
图范围时才是这样。进行缓存时使用公用切片方案原点可确保所创建的缓存
能够在 Web 应用程序中相互叠加。注意,切片是从地图的全图开始切的,不
是从切片方案原点(切片方案原点落在地图原点右下方另算)。
另外一点,我经过试验,发现切片的行列号是从 0 开始算起的。
3.2 切片宽度和高度
切片的默认宽度和高度为 256 像素。
3.3 DPI
每英寸点数 (Dot Per Inch) ,是指服务器将生成的缓存切片的分辨
率。即生成的图片每英寸长度内的像素点数。默认为 96。
2. 切片方案缓存文件结构
缓存目录 -> 地图服务名 ->地图数据框名称(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
3. bundle 和 bundlx 文件
bundle 文件中存储的是图片文件,bundlx 文件中则存储了各个图片文
件在 bundle 文件中的偏移量。
3.1 命名规则
bundle 文件的命名都是:R 数字 C 数字。R 代表起始的 Row,C 代表
起始的 Column。数字均为 16 进制。
行和列如果不满 4 位,则前面补 0,位数多了不限。如
R22e80C14400,表示这个 bundle 文件的起始切片是 0x22e80 行
0x14400 列。
3.2 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 列……
3.3 bundle 文件结构
这个文件我比较了好半天才找到这些规则。
首先是文件的开头,从 0x00 到 0x3b 的这些位置都是 bundle 文件的描
述信息:(还是以 R22e80C14380.bundle 为例)
其中: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 个字节之后取这些长度的字节就组合成了一副图像,如果要查找
的话返回即可。
4. 简单写入算法
读取时直接按照 level 和 row、column 计算出 bundle 文件名,按照上
面的计算方法找到对应 Image 返回即可,这里只说明一下我自己的切片的方
法。针对单个 LODInfo
计算 map 的 Tile 范围
int startCol = (int)Math.Floor((double)(((envelope.XMin - x) + (lod.Resolution * 0.5))
/ (resolution * this.TileInfo.Width)));
int startRow = (int)Math.Floor((double)(((y - envelope.YMax) + (lod.Resolution * 0.5))
/ (resolution * this.TileInfo.Height)));
int endCol = (int)Math.Floor((double)(((envelope.XMax - x) - (lod.Resolution * 0.5)) /
(resolution * this.TileInfo.Width)));
int endRow = (int)Math.Floor((double)(((y - envelope.YMin) - (lod.Resolution * 0.5)) /
(resolution * this.TileInfo.Height)));map跨越的bundle文件范围
//左上角的 bundle 文件开始行列号
long ltBundleRow = this.m_packetSize * (startRow /
this.m_packetSize);
long ltBundleCol = this.m_packetSize * (startCol /
this.m_packetSize);
//右下角(最后一个)的 bundle 文件开始行列号(由于上面的第 9.0 行
已经归为第 9 行,所以直接算就是对的)
long rbBundleRow = this.m_packetSize * (startRow /
this.m_packetSize);