package com.mutu.mapapi.tileprovider.modules; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.UnknownHostException; import java.util.concurrent.atomic.AtomicReference; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mutu.mapapi.http.HttpClientFactory; import com.mutu.mapapi.tileprovider.BitmapPool; import com.mutu.mapapi.tileprovider.MapTile; import com.mutu.mapapi.tileprovider.MapTileRequestState; import com.mutu.mapapi.tileprovider.ReusableBitmapDrawable; import com.mutu.mapapi.tileprovider.tilesource.CompositeTileSource; import com.mutu.mapapi.tileprovider.tilesource.ITileSource; import com.mutu.mapapi.tileprovider.tilesource.OnlineTileSourceBase; import com.mutu.mapapi.tileprovider.tilesource.BitmapTileSourceBase.LowMemoryException; import com.mutu.mapapi.tileprovider.util.StreamUtils; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.text.TextUtils; /** * The {@link MapTileDownloader} loads tiles from an HTTP server. It saves downloaded tiles to an * IFilesystemCache if available. * * @author Marc Kurtz * @author Nicolas Gramlich * @author Manuel Stahl * */ public class MapTileDownloader extends MapTileModuleProviderBase { // =========================================================== // Constants // =========================================================== private static final Logger logger = LoggerFactory.getLogger(MapTileDownloader.class); // =========================================================== // Fields // =========================================================== private final IFilesystemCache mFilesystemCache; private final AtomicReference<ITileSource> mTileSource = new AtomicReference<ITileSource>(); private final INetworkAvailablityCheck mNetworkAvailablityCheck; // =========================================================== // Constructors // =========================================================== public MapTileDownloader(final ITileSource pTileSource) { this(pTileSource, null, null); } public MapTileDownloader(final ITileSource pTileSource, final IFilesystemCache pFilesystemCache) { this(pTileSource, pFilesystemCache, null); } public MapTileDownloader(final ITileSource pTileSource, final IFilesystemCache pFilesystemCache, final INetworkAvailablityCheck pNetworkAvailablityCheck) { this(pTileSource, pFilesystemCache, pNetworkAvailablityCheck, NUMBER_OF_TILE_DOWNLOAD_THREADS, TILE_DOWNLOAD_MAXIMUM_QUEUE_SIZE); } public MapTileDownloader(final ITileSource pTileSource, final IFilesystemCache pFilesystemCache, final INetworkAvailablityCheck pNetworkAvailablityCheck, int pThreadPoolSize, int pPendingQueueSize) { super(pThreadPoolSize, pPendingQueueSize); mFilesystemCache = pFilesystemCache; mNetworkAvailablityCheck = pNetworkAvailablityCheck; setTileSource(pTileSource); } // =========================================================== // Getter & Setter // =========================================================== public ITileSource getTileSource() { return mTileSource.get(); } // =========================================================== // Methods from SuperClass/Interfaces // =========================================================== @Override public boolean getUsesDataConnection() { return true; } @Override protected String getName() { return "Online Tile Download Provider"; } @Override protected String getThreadGroupName() { return "downloader"; } @Override protected Runnable getTileLoader() { ITileSource tileSource = mTileSource.get(); if (tileSource instanceof OnlineTileSourceBase) { return new TileLoader(); }else if (tileSource instanceof CompositeTileSource) { return new CompositeTileLoader(); } return null; } @Override public int getMinimumZoomLevel() { ITileSource tileSource = mTileSource.get(); return (tileSource != null ? tileSource.getMinimumZoomLevel() : MINIMUM_ZOOMLEVEL); } @Override public int getMaximumZoomLevel() { ITileSource tileSource = mTileSource.get(); return (tileSource != null ? tileSource.getMaximumZoomLevel() : MAXIMUM_ZOOMLEVEL); } @Override public void setTileSource(final ITileSource tileSource) { // We are only interested in OnlineTileSourceBase tile sources // if (tileSource instanceof OnlineTileSourceBase) { // mTileSource.set((OnlineTileSourceBase) tileSource); // } else { // // Otherwise shut down the tile downloader // mTileSource.set(null); // } mTileSource.set(tileSource); } // =========================================================== // Inner and Anonymous Classes // =========================================================== protected class TileLoader extends MapTileModuleProviderBase.TileLoader { @Override public Drawable loadTile(final MapTileRequestState aState) throws CantContinueException { OnlineTileSourceBase tileSource = (OnlineTileSourceBase)mTileSource.get(); if (tileSource == null) { return null; } InputStream in = null; OutputStream out = null; final MapTile tile = aState.getMapTile(); try { if (mNetworkAvailablityCheck != null && !mNetworkAvailablityCheck.getNetworkAvailable()) { if (DEBUGMODE) { logger.debug("Skipping " + getName() + " due to NetworkAvailabliltyCheck."); } return null; } final String tileURLString = tileSource.getTileURLString(tile); if (DEBUGMODE) { logger.debug("Downloading Maptile from url: " + tileURLString); } if (TextUtils.isEmpty(tileURLString)) { return null; } final HttpClient client = HttpClientFactory.createHttpClient(); final HttpUriRequest head = new HttpGet(tileURLString); final HttpResponse response = client.execute(head); // Check to see if we got success final org.apache.http.StatusLine line = response.getStatusLine(); final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); out = new BufferedOutputStream(dataStream, StreamUtils.IO_BUFFER_SIZE); if (line.getStatusCode() == 200) { final HttpEntity entity = response.getEntity(); if (entity == null) { logger.warn("No content downloading MapTile: " + tile); return null; } in = entity.getContent(); StreamUtils.copy(in, out); }else if(line.getStatusCode() == 404){ Bitmap bitmap = Bitmap.createBitmap(tileSource.getTileSizePixels(), tileSource.getTileSizePixels(), Config.ARGB_8888); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); //return new ExpirableBitmapDrawable(bitmap); }else{ logger.warn("Problem downloading MapTile: " + tile + " HTTP response: " + line); return null; } out.flush(); final byte[] data = dataStream.toByteArray(); final ByteArrayInputStream byteStream = new ByteArrayInputStream(data); // Save the data to the filesystem cache if (mFilesystemCache != null) { mFilesystemCache.saveFile(tileSource, tile, byteStream); byteStream.reset(); } final Drawable result = tileSource.getDrawable(byteStream); return result; } catch (final UnknownHostException e) { // no network connection so empty the queue logger.warn("UnknownHostException downloading MapTile: " + tile + " : " + e); throw new CantContinueException(e); } catch (final LowMemoryException e) { // low memory so empty the queue logger.warn("LowMemoryException downloading MapTile: " + tile + " : " + e); throw new CantContinueException(e); } catch (final FileNotFoundException e) { logger.warn("Tile not found: " + tile + " : " + e); } catch (final IOException e) { logger.warn("IOException downloading MapTile: " + tile + " : " + e); } catch (final Throwable e) { logger.error("Error downloading MapTile: " + tile, e); } finally { StreamUtils.closeStream(in); StreamUtils.closeStream(out); } return null; } @Override protected void tileLoaded(final MapTileRequestState pState, final Drawable pDrawable) { removeTileFromQueues(pState.getMapTile()); // don't return the tile because we'll wait for the fs provider to ask for it // this prevent flickering when a load of delayed downloads complete for tiles // that we might not even be interested in any more pState.getCallback().mapTileRequestCompleted(pState, null); // We want to return the Bitmap to the BitmapPool if applicable if (pDrawable instanceof ReusableBitmapDrawable) BitmapPool.getInstance().returnDrawableToPool((ReusableBitmapDrawable) pDrawable); } } private class CompositeTileLoader extends MapTileModuleProviderBase.TileLoader { @Override public Drawable loadTile(final MapTileRequestState aState) throws CantContinueException { CompositeTileSource tileSource = (CompositeTileSource)mTileSource.get(); if (tileSource == null) { return null; } InputStream in = null; OutputStream out = null; final MapTile tile = aState.getMapTile(); try { if (mNetworkAvailablityCheck != null && !mNetworkAvailablityCheck.getNetworkAvailable()) { if (DEBUGMODE) { logger.debug("Skipping " + getName() + " due to NetworkAvailabliltyCheck."); } return null; } Bitmap bitmap = Bitmap.createBitmap(tileSource.getTileSizePixels(), tileSource.getTileSizePixels(), Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); final String[] tileURLStrings = tileSource.getTileURLString(tile); for(String tileURLString : tileURLStrings){ if (DEBUGMODE) { logger.debug("Downloading Maptile from url: " + tileURLString); } if (TextUtils.isEmpty(tileURLString)) { continue; } final HttpClient client = HttpClientFactory.createHttpClient(); final HttpUriRequest head = new HttpGet(tileURLString); final HttpResponse response = client.execute(head); // Check to see if we got success final org.apache.http.StatusLine line = response.getStatusLine(); if (line.getStatusCode() == 200) { final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); out = new BufferedOutputStream(dataStream, StreamUtils.IO_BUFFER_SIZE); final HttpEntity entity = response.getEntity(); if (entity == null) { logger.warn("No content downloading MapTile: " + tile); return null; } in = entity.getContent(); StreamUtils.copy(in, out); out.flush(); final byte[] data = dataStream.toByteArray(); final ByteArrayInputStream byteStream = new ByteArrayInputStream(data); final Drawable result = tileSource.getDrawable(byteStream); if(result != null){ result.setBounds(0, 0, tileSource.getTileSizePixels(), tileSource.getTileSizePixels()); result.draw(canvas); } StreamUtils.closeStream(in); StreamUtils.closeStream(out); // break; }else{ logger.warn("Problem downloading MapTile: " + tile + " HTTP response: " + line); } } final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); out = new BufferedOutputStream(dataStream, StreamUtils.IO_BUFFER_SIZE); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); final byte[] data = dataStream.toByteArray(); final ByteArrayInputStream byteStream = new ByteArrayInputStream(data); // Save the data to the filesystem cache if (mFilesystemCache != null) { mFilesystemCache.saveFile(tileSource, tile, byteStream); byteStream.reset(); } final Drawable result = tileSource.getDrawable(byteStream); return result; } catch (final UnknownHostException e) { // no network connection so empty the queue logger.warn("UnknownHostException downloading MapTile: " + tile + " : " + e); throw new CantContinueException(e); } catch (final LowMemoryException e) { // low memory so empty the queue logger.warn("LowMemoryException downloading MapTile: " + tile + " : " + e); throw new CantContinueException(e); } catch (final FileNotFoundException e) { logger.warn("Tile not found: " + tile + " : " + e); } catch (final IOException e) { logger.warn("IOException downloading MapTile: " + tile + " : " + e); } catch (final Throwable e) { logger.error("Error downloading MapTile: " + tile, e); } finally { StreamUtils.closeStream(in); StreamUtils.closeStream(out); } return null; } @Override protected void tileLoaded(final MapTileRequestState pState, final Drawable pDrawable) { removeTileFromQueues(pState.getMapTile()); // don't return the tile because we'll wait for the fs provider to ask for it // this prevent flickering when a load of delayed downloads complete for tiles // that we might not even be interested in any more pState.getCallback().mapTileRequestCompleted(pState, null); // We want to return the Bitmap to the BitmapPool if applicable if (pDrawable instanceof ReusableBitmapDrawable) BitmapPool.getInstance().returnDrawableToPool((ReusableBitmapDrawable) pDrawable); } } }