ArcGIS for Server 10.3.X 新型紧凑型缓存的解读和应用

本文详细解析了ArcGIS 10.3中新推出的紧凑型缓存格式,包括缓存文件的结构、切片索引及图片流存储规律等关键技术细节。
早在2010年年底, 牛魔王中王在其博客空间 牛魔王的作坊中对ArcGIS 10中推出的紧凑型缓存格式进行了详细的解读,详见《 ArcGIS 切片缓存紧凑文件格式分析与使用》。紧随着的4年时间里,ArcGIS for Server本身经历了10、10.1.X和10.2.X各版本的逐级更替,特别是软件架构发生了显著的变化。然而,就紧凑型缓存本身而言,牛魔王中王的解读一直都是适用的。衷心地向我们的大牛致敬!

  直到2014年年底ArcGIS 10.3正式发布,Esri才推出了新的紧凑型缓存格式以增强用户的访问体验。新的缓存格式下,关键的差别在于Esri将缓存的索引信息.bundlx包含在了缓存的切片文件.bundle中。

  接下来,我们就简单解读一下这一新型的紧凑型缓存格式。俗语说,万变不离其宗。既然缓存文件夹下仅包含了bundle文件,可以想见,切片的索引,切片的偏移和切片的图片流都必然包含在这一文件中。根据经验,缓存本身遵循的是16进制的形式。依照这一思路,利用UltraEdit打开bundle文件并以16进制格式进行查看。

 为了便于分析,我们先创建一个在L00级只包含一个切片的缓存服务,并在UltraEdit中以16进制格式查看L00级下的R0000C0000.bundle文件。

  通过对这一文件中信息存储规律的分析,可初步得出如下结论:(1) 文件中包含大量04 00 00 00 00 00 00 00的16进制字节组,共计16893组;(2) 文件中仅包含一个PNG24的文件头字节组89504E47,即第一行第一列的切片,bundle文件中唯一的一张图片。图片流紧随(1)中所提到的字节组之后,但偏移4个字节;(3) (2)中所述的4字节偏移量的数值恰等于图片流的长度;(4) 文件第5行的起始4个字节44 00 02 00按照低位到高位换算出的数值等于131140,这一值与(2)中所述的PNG文件头位置恰恰吻合。

  综上分析:(1)起始4行是bundle的文件头信息,可忽略;(2)bundle的文件头之后记录了16384张切片的切片位置,仅4字节,从低位到高位,后4字节可忽略;(3)位置信息之后,对于切片的记录,先以4字节记录切片的长度,而后紧跟图片流信息。到此,bundle结束。

  下一步呢,我们将选择一个狭长的矩形面要素发布服务并切图,以分析行列切片在bundle文件中的具体存储规律。

  通过对bundle文件和对应的松散缓存在L02级别上的对比,可推断:(1)bundle中索引的存储是按行依次存储,即第1行的1至128,第2行的1至128,以此类推,直至最后一张切片即第128行128列;(2)bundle中图片流的存储仅包含非空切片。此外,通过对这一更复杂的地图缓存的分析,再次论证了前面的推论。

  既然上述的分析完毕,接下来就要对上述的分析进行一番验证啦。这里呢,我会利用ArcGIS Runtime SDK for Android实现抽象理论的实践工作。本次验证的核心在于,通过对TiledServiceLayer进行扩展,按照上面的存储推论覆写getTile(int mLevel, int mColumn, int mRow)方法。

  第一步,根据参数中的比例级别、列号和行号定位到Bundle文件。

 1     String level = Integer.toString(mLevel);
 2     int levelLength = level.length();    
 3     if(levelLength == 1){                        
 4         level = "0" + level;
 5     }
 6     level = "L" + level;
 7             
 8     int rowGroup = 128*(mRow/128);
 9     String row = Integer.toHexString(rowGroup);    
10     int rowLength = row.length();
11     if(rowLength < 4){
12         for(int i=0; i<4-rowLength; i++){
13             row = "0" + row;    
14         }
15     }
16     row = "R" + row;
17             
18     int columnGroup = 128*(mColumn/128);
19     String column = Integer.toHexString(columnGroup);
20     int columnLength = column.length();
21     if(columnLength < 4) {
22         for(int i=0; i<4-columnLength; i++){
23             column = "0" + column;
24         }
25     }
26     column = "C" + column;
27     
28     String bundleName = String.format("%s/%s/%s%s", compactTileLoc, level, row, column) + ".bundle";
View Code

  第二步,读取bundle文件,根据前面分析中所推断出的切片的起始位置和切片的长度获取对应的切片并返回。

 1     int index = 128*(mRow - rowGroup) + (mColumn-columnGroup);
 2     
 3     RandomAccessFile isBundle = new RandomAccessFile(bundleFileName, "r");
 4     isBundle.skipBytes(64 + 8*index);
 5     
 6     //获取位置索引并计算切片位置偏移量
 7     byte[] indexBytes = new byte[4];
 8     isBundle.read(indexBytes, 0, 4);    
 9     long offset = (long)(indexBytes[0]&0xff) +(long)(indexBytes[1]&0xff)*256 + (long)(indexBytes[2]&0xff)*65536 
10                     + (long)(indexBytes[3]&0xff)*16777216; 
11                     
12     //获取切片长度索引并计算切片长度
13     long startOffset = offset - 4; 
14     isBundle.seek(startOffset);
15     byte[] lengthBytes = new byte[4];
16     isBundle.read(lengthBytes, 0, 4);
17     int length = (int)(lengthBytes[0] & 0xff) + (int)(lengthBytes[1] & 0xff)*256 + (int)(lengthBytes[2] & 0xff) * 65536
18                 + (int)(lengthBytes[3] & 0xff) * 16777216;
19     
20   //根据切片位置和切片长度获取切片
21     ByteArrayOutputStream bos = new ByteArrayOutputStream();
22     
23     byte[] tileBytes = new byte[length];
24     int bytesRead = 0;
25     if(length > 0){
26         bytesRead = isBundle.read(tileBytes, 0, tileBytes.length);
27         if(bytesRead > 0){
28             bos.write(tileBytes, 0, bytesRead);
29         }
30     }
31             
32     tile = bos.toByteArray();
View Code

  呵呵,成功实现。直接奉上Android端的显示效果吧。

  

Tips:

  关于紧凑型切片,唠唠叨叨的我还是忍不住要嘱咐几句:(1)新型的紧凑型切片无法被直接用于先前版本的ArcGIS for Server;(2)新型的紧凑型切片可通过导出切片即export tiles获取先前格式的缓存;(3)老版本的紧凑型切片可直接在新版本的ArcGIS for Server中复用,但可通过升级存储格式即Upgrade Storage Format更新为新型紧凑型切片格式。

<think>好的,用户想了解如何使用Java读取ArcGISServer10.3紧凑型缓存.bundle文件。之前已经给出过示例代码,但用户现在提供了新的引用内容,需要结合这些引用进步优化回答。首先,查看用户提供的引用[1]引用[2]。引用[1]提到了Android SDK中使用自定义方式加载Bundle数据,可能涉及到ArcGISLocalTiledLayer自定义的BundleLayer。但用户的问题是关于Java开发,可能不限于Android,所以需要区分开。引用[2]指出10.3版本的.bundle文件整合了之前的两个文件,结构有所变化,需要解析单个文件。之前的回答中已经有个Java代码示例,但需要确认是否适用于10.3版本的结构。根据引用[2],10.3.bundle文件不再需要.bundlx索引,而是整合到个文件中,所以之前的代码可能需要调整,比如文件头的解析方式。接下来,需要确定.bundle文件的具体结构。可能需要更详细的结构分析,比如文件头中的瓦片数量、偏移量信息。例如,每个.bundle文件可能包含多个瓦片块,每个块前面有长度信息,然后跟着图像数据。需要正确解析这些长度字段,通常是小端序存储。 用户可能希望了解如何定位具体的瓦片,比如通过行、列、层级来找到对应的.bundle文件,并从中提取正确的瓦片数据。这涉及到文件名解析内部偏移计算。例如,文件名中的R后面的数字表示行号,每个文件可能包含定数量的瓦片(如128x128),需要根据列号计算在文件中的位置。此外,需要注意图像格式,可能是PNG或JPEG,需要正确读取字节数据并通过ImageIO处理。同时,处理大文件时可能需要使用RandomAccessFile来高效跳转,避免内存溢出。还需要考虑异常处理,比如文件不存在、格式错误等情况,确保代码的健壮性。可能还需要参考Esri的官方文档或相关技术资料,确认结构细节,但用户没有提供,所以可能需要基于常见做法进行假设。 最后,生成相关问题时,要结合用户之前的问题,比如不同版本的差异、转换缓存格式、性能优化等,保持问题的连贯性相关性。</think>### Java读取ArcGIS 10.3紧凑型缓存(.bundle文件)方法解析 #### 、文件结构分析 ArcGIS 10.3紧凑型缓存将瓦片数据与索引信息整合到单个`.bundle`文件中[^2]。其核心结构包括: 1. **文件头**:包含瓦片数量、偏移量等元数据(通常为前$8$字节) - 前$4$字节:当前文件包含的瓦片数量(小端序存储) - 后$4$字节:保留字段 2. **瓦片块序列**:每个瓦片由$4$字节长度字段图像数据组成 - 长度字段:图像数据的字节数(小端序) - 图像数据:PNG/JPEG格式的二进制流 #### 二、Java实现步骤 ##### 1. 文件名解析规则 文件名格式为`L<level>R<row>.bundle`,例如`L03R1234.bundle`表示: - `level=3`:地图层级 - `row=1234`:行号 - 列号通过瓦片索引计算得出(每个文件包含固定$128 \times 128$瓦片) ##### 2. 关键代码实现 ```java import java.io.*; import java.nio.*; import javax.imageio.ImageIO; public class BundleReaderV2 { // 解析单个.bundle文件 public static void parseBundle(File bundleFile, String outputDir) { try (RandomAccessFile raf = new RandomAccessFile(bundleFile, "r")) { // 读取文件头 byte[] header = new byte[8]; raf.readFully(header); int tileCount = ByteBuffer.wrap(header, 0, 4) .order(ByteOrder.LITTLE_ENDIAN).getInt(); // 遍历瓦片块 for (int i = 0; i < tileCount; i++) { // 读取长度字段 byte[] lenBytes = new byte[4]; raf.readFully(lenBytes); int dataLength = ByteBuffer.wrap(lenBytes) .order(ByteOrder.LITTLE_ENDIAN).getInt(); // 读取图像数据 byte[] tileData = new byte[dataLength]; raf.readFully(tileData); // 保存为PNG文件 saveTile(tileData, outputDir + "/tile_" + i + ".png"); } } catch (IOException e) { e.printStackTrace(); } } private static void saveTile(byte[] data, String path) { try (InputStream is = new ByteArrayInputStream(data)) { BufferedImage img = ImageIO.read(is); ImageIO.write(img, "png", new File(path)); } catch (IOException e) { System.err.println("无效的瓦片数据: " + e.getMessage()); } } public static void main(String[] args) { parseBundle(new File("L03R1234.bundle"), "./tiles_output"); } } ``` #### 三、关键技术点 1. **小端序处理**:使用`ByteBuffer`配合`ByteOrder.LITTLE_ENDIAN`解析数值 2. **随机访问优化**:`RandomAccessFile`支持快速跳转,适合处理大文件 3. **异常处理**:捕获`EOFException`防止文件格式错误导致崩溃 4. **性能优化**:批量读取减少IO操作次数(可通过缓冲区扩展) #### 四、扩展应用场景 ```mermaid graph TD A[读取.bundle文件] --> B{解析层级/行列号} B -->|匹配请求| C[定位目标瓦片] B -->|未匹配| D[返回空瓦片] C --> E[生成地图服务] ``` #### 五、注意事项 - 不同层级的`.bundle`文件需单独处理 - 实际列号计算需满足:$column = startColumn + (offset / 256)$(假设每文件包含$128 \times 128$瓦片)[^2] - 瓦片尺寸需与地图服务配置致(通常为$256 \times 256$像素) ---
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值