// Created by plusminus on 21:31:36 - 25.09.2008 package de.blau.android.services.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.util.concurrent.Executors; import android.content.Context; import android.os.RemoteException; import android.util.Log; import de.blau.android.App; import de.blau.android.prefs.Preferences; import de.blau.android.resources.TileLayerServer; import de.blau.android.services.IMapTileProviderCallback; import de.blau.android.util.NetworkStatus; /** * The OpenStreetMapTileDownloader loads tiles from a server and passes them to * a OpenStreetMapTileFilesystemProvider.<br/> * This class was taken from OpenStreetMapViewer (original package org.andnav.osm) in 2010-06 * by Marcus Wolschon to be integrated into the de.blau.androin * OSMEditor. * @author Nicolas Gramlich * @author Marcus Wolschon <Marcus@Wolschon.biz * @author Manuel Stahl * */ public class MapTileDownloader extends MapAsyncTileProvider { // =========================================================== // Constants // =========================================================== private static final String DEBUGTAG = "OSM_DOWNLOADER"; // =========================================================== // Fields // =========================================================== private final Context mCtx; private final MapTileFilesystemProvider mMapTileFSProvider; // =========================================================== // Constructors // =========================================================== public MapTileDownloader(final Context ctx, final MapTileFilesystemProvider aMapTileFSProvider){ mCtx = ctx; mMapTileFSProvider = aMapTileFSProvider; mThreadPool = Executors.newFixedThreadPool((new Preferences(ctx)).getMaxTileDownloadThreads()); } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods from SuperClass/Interfaces // =========================================================== @Override protected Runnable getTileLoader(MapTile aTile, IMapTileProviderCallback aCallback) { return new TileLoader(aTile, aCallback); } // =========================================================== // Methodsorg.andnav.osm.services // =========================================================== private String buildURL(final MapTile tile) { TileLayerServer renderer = TileLayerServer.get(mCtx, tile.rendererID, false); // Log.d("OpenStreetMapTileDownloader","metadata loaded "+ renderer.isMetadataLoaded() + " " + renderer.getTileURLString(tile)); return renderer.isMetadataLoaded() ? renderer.getTileURLString(tile) : ""; } // =========================================================== // Inner and Anonymous Classes // =========================================================== private class TileLoader extends MapAsyncTileProvider.TileLoader { public TileLoader(final MapTile aTile, final IMapTileProviderCallback aCallback) { super(aTile, aCallback); } @Override public void run() { if (!NetworkStatus.isConnected(mCtx)) { // fail immediately try { Log.e(DEBUGTAG, "No network"); mCallback.mapTileFailed(mTile.rendererID, mTile.zoomLevel, mTile.x, mTile.y,NONETWORK); } catch (RemoteException re) { Log.e(DEBUGTAG, "Error calling mapTileLoaded for MapTile. Exception: " + re); } return; } InputStream in = null; OutputStream out = null; String tileURLString = buildURL(mTile); try { if (tileURLString.length() > 0) { if(Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Downloading Maptile from url: " + tileURLString); } URLConnection conn = new URL(tileURLString).openConnection(); conn.setRequestProperty("User-Agent", App.userAgent); if ("BING".equals(mTile.rendererID)) { // this is fairly expensive so only do it is we are actually querying bing if ("no-tile".equals(conn.getHeaderField("X-VE-Tile-Info"))) { // handle special Bing header that indicates no tile is available throw new FileNotFoundException("tile not available"); } } in = new BufferedInputStream(conn.getInputStream(), StreamUtils.IO_BUFFER_SIZE); final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); out = new BufferedOutputStream(dataStream, StreamUtils.IO_BUFFER_SIZE); StreamUtils.copy(in, out); out.flush(); final byte[] data = dataStream.toByteArray(); if (data.length == 0) { throw new IOException("no tile data"); } mCallback.mapTileLoaded(mTile.rendererID, mTile.zoomLevel, mTile.x, mTile.y, data); MapTileDownloader.this.mMapTileFSProvider.saveFile(mTile, data); if(Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Maptile " + tileURLString + " saved"); } } } catch (IOException ioe) { try { int reason = ioe instanceof FileNotFoundException ? DOESNOTEXIST : IOERR; if (reason == DOESNOTEXIST) { MapTileDownloader.this.mMapTileFSProvider.markAsInvalid(mTile); } mCallback.mapTileFailed(mTile.rendererID, mTile.zoomLevel, mTile.x, mTile.y,reason); } catch (RemoteException re) { Log.e(DEBUGTAG, "Error calling mCallback for MapTile. Exception: " + ioe.getClass().getSimpleName() + " further mapTileFailed failed " + re, ioe); } catch (NullPointerException npe) { Log.e(DEBUGTAG, "Error calling mCallback for MapTile. Exception: " + ioe.getClass().getSimpleName() + " further mapTileFailed failed " + npe, ioe); } catch (IOException ioe2) { Log.e(DEBUGTAG, "Error calling mCallback for MapTile. Exception: " + ioe.getClass().getSimpleName() + " further mapTileFailed failed " + ioe2, ioe); } if (!(ioe instanceof FileNotFoundException)) { // FileNotFound is an expected exception, any other IOException should be logged if(Log.isLoggable(DEBUGTAG, Log.ERROR)) { Log.e(DEBUGTAG, "Error Downloading MapTile. Exception: " + ioe.getClass().getSimpleName() + " " + tileURLString, ioe); } } /* TODO What to do when downloading tile caused an error? * Also remove it from the mPending? * Doing not blocks it for the whole existence of this TileDownloader. * -> we remove it and the application has to re-request it. */ } catch (RemoteException re) { Log.e(DEBUGTAG, "Error calling mapTileLoaded for MapTile. Exception: " + re); } catch (NullPointerException npe) { Log.e(DEBUGTAG, "Error calling mapTileLoaded for MapTile. Exception: " + npe); } finally { StreamUtils.closeStream(in); StreamUtils.closeStream(out); finished(); } } } }