package org.geowebcache.arcgis.compact; import org.geowebcache.io.Resource; import java.io.File; import java.nio.ByteBuffer; /** * Implementation of ArcGIS compact caches for ArcGIS 10.0 - 10.2 * * The compact cache consists of bundle index files (*.bundlx) and bundle files (*.bundle), that contain * the actual image data. * Every .bundlx file contains a 16 byte header and 16 byte footer. Between header and footer is * 128x128 matrix (16384 tiles) of 5 byte offsets. Every offset points to a 4 byte word in the * corresponding .bundle file which contains the size of the tile image data. The actual image data * starts at offset+4. If the size is zero there is no image data available and the index entry is * not used. If the map cache has more than 128 rows or columns it is divided into several .bundlx * and .bundle files. * * @author Bjoern Saxe */ public class ArcGISCompactCacheV1 extends ArcGISCompactCache { private static final int COMPACT_CACHE_HEADER_LENGTH = 16; private BundlxCache indexCache; /** * Constructs new ArcGIS 10.0-10.2 compact cache. * * @param pathToCacheRoot Path to compact cache directory (usually ".../_alllayers/"). Path must contain * directories for zoom levels (named "Lxx"). */ public ArcGISCompactCacheV1(String pathToCacheRoot) { if (pathToCacheRoot.endsWith("" + File.separatorChar)) this.pathToCacheRoot = pathToCacheRoot; else this.pathToCacheRoot = pathToCacheRoot + File.separatorChar; indexCache = new BundlxCache(10000); } @Override public Resource getBundleFileResource(int zoom, int row, int col) { if (zoom < 0 || col < 0 || row < 0) return null; BundlxCache.CacheKey key = new BundlxCache.CacheKey(zoom, row, col); BundlxCache.CacheEntry entry = null; Resource res = null; if ((entry = indexCache.get(key)) != null) { if (entry.size > 0) res = new BundleFileResource(entry.pathToBundleFile, entry.offset, entry.size); } else { String basePath = buildBundleFilePath(zoom, row, col); String pathToBundlxFile = basePath + BUNDLX_EXT; String pathToBundleFile = basePath + BUNDLE_EXT; if (!(new File(pathToBundleFile)).exists() || !(new File(pathToBundlxFile)).exists()) return null; long tileOffset = readTileStartOffset(pathToBundlxFile, row, col); int tileSize = readTileSize(pathToBundleFile, tileOffset); tileOffset += 4; if (tileSize > 0) res = new BundleFileResource(pathToBundleFile, tileOffset, tileSize); entry = new BundlxCache.CacheEntry(pathToBundleFile, tileOffset, tileSize); indexCache.put(key, entry); } return res; } private long readTileStartOffset(String bundlxFile, int row, int col) { int index = BUNDLX_MAXIDX * (col % BUNDLX_MAXIDX) + (row % BUNDLX_MAXIDX); ByteBuffer idxBytes = readFromLittleEndianFile(bundlxFile, (index * 5) + COMPACT_CACHE_HEADER_LENGTH, 5); return idxBytes.getLong(); } private int readTileSize(String bundlxFile, long offset) { ByteBuffer tileSize = readFromLittleEndianFile(bundlxFile, offset, 4); return tileSize.getInt(); } }