package tim.prune.gui.map; import java.awt.Image; import java.awt.image.ImageObserver; import java.net.MalformedURLException; import java.net.URL; import tim.prune.config.Config; /** * Class responsible for managing the map tiles, * including invoking the correct memory cacher(s) and/or disk cacher(s) */ public class MapTileManager implements ImageObserver { /** Consumer object to inform when tiles received */ private TileConsumer _consumer = null; /** Current map source */ private MapSource _mapSource = null; /** Array of tile caches, one per layer */ private MemTileCacher[] _tempCaches = null; /** Flag for whether to download any tiles or just pull from disk */ private boolean _downloadTiles = true; /** Flag for whether to return incomplete images or just pass to tile cache until they're done */ private boolean _returnIncompleteImages = false; /** Number of layers */ private int _numLayers = -1; /** Current zoom level */ private int _zoom = 0; /** Number of tiles in each direction for this zoom level */ private int _numTileIndices = 1; /** * Constructor * @param inConsumer consumer object to be notified */ public MapTileManager(TileConsumer inConsumer) { _consumer = inConsumer; } /** * Recentre the map * @param inZoom zoom level * @param inTileX x coord of central tile * @param inTileY y coord of central tile */ public void centreMap(int inZoom, int inTileX, int inTileY) { setZoom(inZoom); // Pass params onto all memory cachers if (_tempCaches != null) { for (int i=0; i<_tempCaches.length; i++) { _tempCaches[i].centreMap(inZoom, inTileX, inTileY); } } } /** @param inZoom zoom level to set */ public void setZoom(int inZoom) { _zoom = inZoom; // Calculate number of tiles = 2^^zoom _numTileIndices = 1 << _zoom; } /** * @return true if zoom is too high for tiles */ public boolean isOverzoomed() { // Ask current map source what maximum zoom is int maxZoom = (_mapSource == null?0:_mapSource.getMaxZoomLevel()); return (_zoom > maxZoom); } /** * Enable or disable tile downloading * @param inEnabled true to enable downloading, false to just get tiles from disk */ public void enableTileDownloading(boolean inEnabled) { _downloadTiles = inEnabled; } /** Configure to return incomplete images instead of going via caches (and another call) */ public void setReturnIncompleteImages() { _returnIncompleteImages = true; } /** * Clear all the memory caches due to changed config / zoom */ public void clearMemoryCaches() { int numLayers = _mapSource.getNumLayers(); if (_tempCaches == null || _tempCaches.length != numLayers) { // Cachers don't match, so need to create the right number of them _tempCaches = new MemTileCacher[numLayers]; for (int i=0; i= _numTileIndices) return null; // Wrap tile indices which are too big or too small inX = ((inX % _numTileIndices) + _numTileIndices) % _numTileIndices; // Check first in memory cache for tile Image tile = null; MemTileCacher tempCache = null; if (_tempCaches != null) { tempCache = _tempCaches[inLayer]; // Should probably guard array indexes here tile = tempCache.getTile(inX, inY); if (tile != null) { return tile; } } // Tile wasn't in memory, but maybe it's in disk cache (if there is one) String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE); boolean useDisk = (diskCachePath != null); boolean onlineMode = Config.getConfigBoolean(Config.KEY_ONLINE_MODE); if (useDisk) { tile = DiskTileCacher.getTile(diskCachePath, _mapSource.makeFilePath(inLayer, _zoom, inX, inY), onlineMode); if (tile != null) { if (_returnIncompleteImages) {return tile;} // Pass tile to memory cache if (tempCache != null) { tempCache.setTile(tile, inX, inY, _zoom); } if (tile.getWidth(this) > 0) {return tile;} return null; } // else System.out.println("DTC gave null tile for " + _zoom + ", " + inX + ", " + inY); } // Tile wasn't in memory or on disk, so if online let's get it if (onlineMode && _downloadTiles && inDownloadIfNecessary) { try { URL tileUrl = new URL(_mapSource.makeURL(inLayer, _zoom, inX, inY)); // System.out.println("Going to fetch: " + tileUrl); if (useDisk && DiskTileCacher.saveTile(tileUrl, diskCachePath, _mapSource.makeFilePath(inLayer, _zoom, inX, inY), this)) { // Image now copied directly from URL stream to disk cache } else { // Load image asynchronously, using observer // In order to set the http user agent, need to use a TileDownloader instead TileDownloader.triggerLoad(this, tileUrl, inLayer, inX, inY, _zoom); } } catch (MalformedURLException urle) {} // ignore } return null; } /** * Method called by image loader to inform of updates to the tiles * @param img the image * @param infoflags flags describing how much of the image is known * @param x ignored * @param y ignored * @param width ignored * @param height ignored * @return false to carry on loading, true to stop */ public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { boolean loaded = (infoflags & ImageObserver.ALLBITS) > 0; boolean error = (infoflags & ImageObserver.ERROR) > 0; if (loaded || error) { _consumer.tilesUpdated(loaded); } return !loaded; } /** * Callback method from TileDownloader to let us know that an image has been loaded * @param inTile Loaded Image object * @param inLayer layer index from 0 * @param inX x coordinate of tile * @param inY y coordinate of tile * @param inZoom zoom level of loaded image */ public void notifyImageLoaded(Image inTile, int inLayer, int inX, int inY, int inZoom) { if (inTile != null && _tempCaches != null) { MemTileCacher tempCache = _tempCaches[inLayer]; // Should probably guard against nulls and array indexes here if (tempCache.getTile(inX, inY) == null) { // Check with cache that the zoom level is still valid tempCache.setTile(inTile, inX, inY, inZoom); inTile.getWidth(this); // trigger imageUpdate when image is ready } } else if (inTile != null) { inTile.getWidth(this); } } }