package org.geowebcache.arcgis.compact; import org.geowebcache.io.Resource; import java.io.File; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * Implementation of ArcGIS compact caches for ArcGIS 10.3 * * The compact cache consists of bundle files (*.bundle), that contain an index and the actual * image data. Every .bundle file starts with a 64 byte header. After the header * 128x128 matrix (16384 tiles) of 8 byte words. The first 5 bytes of every word is the offset * that points to the tile image data inside the same .bundle file. The next 3 bytes is the size * of the image data. The size of the image data is repeated at offset-4 in 4 byte word. * Unused index entries use 04|00|00|00|00|00|00|00. If the size is zero than there is no image * data available and the index entry is. If the map cache has more than 128 rows or columns it is * divided into several .bundle files. * * @author Bjoern Saxe */ public class ArcGISCompactCacheV2 extends ArcGISCompactCache { private static final int COMPACT_CACHE_HEADER_LENGTH = 64; private BundlxCache indexCache; /** * Constructs new ArcGIS 10.3 compact cache. * * @param pathToCacheRoot Path to compact cache directory (usually ".../_alllayers/"). Path must contain * directories for zoom levels (named "Lxx"). */ public ArcGISCompactCacheV2(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 pathToBundleFile = basePath + BUNDLE_EXT; if (!(new File(pathToBundleFile)).exists()) return null; entry = createCacheEntry(pathToBundleFile, row, col); if (entry.size > 0) res = new BundleFileResource(pathToBundleFile, entry.offset, entry.size); indexCache.put(key, entry); } return res; } private BundlxCache.CacheEntry createCacheEntry(String bundleFile, int row, int col) { // col and row are inverted for 10.3 caches int index = BUNDLX_MAXIDX * (row % BUNDLX_MAXIDX) + (col % BUNDLX_MAXIDX); // to save one addtional read, we read all 8 bytes in one read ByteBuffer offsetAndSize = readFromLittleEndianFile(bundleFile, (index * 8) + COMPACT_CACHE_HEADER_LENGTH, 8); byte[] offsetBytes = new byte[8]; byte[] sizeBytes = new byte[4]; offsetAndSize.get(offsetBytes, 0, 5); offsetAndSize.get(sizeBytes, 0, 3); long tileOffset = ByteBuffer.wrap(offsetBytes).order(ByteOrder.LITTLE_ENDIAN).getLong(); int tileSize = ByteBuffer.wrap(sizeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); return new BundlxCache.CacheEntry(bundleFile, tileOffset, tileSize); } }