Back to project page SmartNavi.
The source code is released under:
Apache License
If you think the Android project SmartNavi listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package org.osmdroid.bonuspack.cachemanager; /*from w w w . java2 s . c o m*/ import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.UnknownHostException; 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.osmdroid.bonuspack.utils.BonusPackHelper; import org.osmdroid.http.HttpClientFactory; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.MapTileProviderBase; import org.osmdroid.tileprovider.constants.OpenStreetMapTileProviderConstants; import org.osmdroid.tileprovider.modules.TileWriter; import org.osmdroid.tileprovider.tilesource.ITileSource; import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase; import org.osmdroid.tileprovider.util.StreamUtils; import org.osmdroid.util.BoundingBoxE6; import org.osmdroid.util.MyMath; import org.osmdroid.views.MapView; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.graphics.Point; import android.os.AsyncTask; import android.util.Log; import android.widget.Toast; /** * Provides various methods for managing the local filesystem cache of osmdroid tiles: <br> * - Dowloading of tiles inside a specified area, <br> * - Cleaning of tiles inside a specified area,<br> * - Information about cache capacity and current cache usage. <br> * * Important note 1: <br> * These methods only make sense for a MapView using an OnlineTileSourceBase: * bitmap tiles downloaded from urls. <br> * * Important note 2 - about Bulk Downloading:<br> * When using OSM Mapnik tile server as the tile source, take care about OSM Tile usage policy * (http://wiki.openstreetmap.org/wiki/Tile_usage_policy). * Do not let to end-users the ability to download significant areas of tiles. <br> * * @author M.Kergall * */ public class CacheManager { protected final MapTileProviderBase mTileProvider; protected final TileWriter mTileWriter; protected final MapView mMapView; public CacheManager(final MapView mapView){ mTileProvider = mapView.getTileProvider(); mTileWriter = new TileWriter(); mMapView = mapView; } public Point getMapTileFromCoordinates(final double aLat, final double aLon, final int zoom){ final int y = (int) Math.floor((1 - Math.log(Math.tan(aLat * Math.PI / 180) + 1 / Math.cos(aLat * Math.PI / 180)) / Math.PI) / 2 * (1 << zoom)); final int x = (int) Math.floor((aLon + 180) / 360 * (1 << zoom)); return new Point(x, y); } public File getFileName(ITileSource tileSource, MapTile tile){ final File file = new File(OpenStreetMapTileProviderConstants.TILE_PATH_BASE, tileSource.getTileRelativeFilenameString(tile) + OpenStreetMapTileProviderConstants.TILE_PATH_EXTENSION); return file; } /** * @return true if success, false if error */ public boolean loadTile(OnlineTileSourceBase tileSource, MapTile tile){ //check if file is already downloaded: File file = getFileName(tileSource, tile); if (file.exists()){ return true; } InputStream in = null; OutputStream out = null; try { final String tileURLString = tileSource.getTileURLString(tile); 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) { Log.w(BonusPackHelper.LOG_TAG, "Problem downloading MapTile: " + tile + " HTTP response: " + line); return false; } final HttpEntity entity = response.getEntity(); if (entity == null) { Log.w(BonusPackHelper.LOG_TAG, "No content downloading MapTile: " + tile); return false; } in = entity.getContent(); final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); out = new BufferedOutputStream(dataStream, StreamUtils.IO_BUFFER_SIZE); StreamUtils.copy(in, out); out.flush(); final byte[] data = dataStream.toByteArray(); final ByteArrayInputStream byteStream = new ByteArrayInputStream(data); // Save the data to the filesystem cache mTileWriter.saveFile(tileSource, tile, byteStream); byteStream.reset(); //final Drawable result = tileSource.getDrawable(byteStream); return true; } catch (final UnknownHostException e) { // no network connection Log.w(BonusPackHelper.LOG_TAG, "UnknownHostException downloading MapTile: " + tile + " : " + e); } catch (final FileNotFoundException e) { Log.w(BonusPackHelper.LOG_TAG, "Tile not found: " + tile + " : " + e); } catch (final IOException e) { Log.w(BonusPackHelper.LOG_TAG, "IOException downloading MapTile: " + tile + " : " + e); } catch (final Throwable e) { Log.e(BonusPackHelper.LOG_TAG, "Error downloading MapTile: " + tile, e); } finally { StreamUtils.closeStream(in); StreamUtils.closeStream(out); } return false; } /** @return the theoretical number of tiles in the specified area */ public int possibleTilesInArea(BoundingBoxE6 bb, final int zoomMin, final int zoomMax){ int total = 0; for (int zoomLevel=zoomMin; zoomLevel<=zoomMax; zoomLevel++){ Point mLowerRight = getMapTileFromCoordinates(bb.getLatSouthE6()*1E-6, bb.getLonEastE6()*1E-6, zoomLevel); Point mUpperLeft = getMapTileFromCoordinates(bb.getLatNorthE6()*1E-6, bb.getLonWestE6()*1E-6, zoomLevel); int y = mLowerRight.y - mUpperLeft.y + 1; int x = mLowerRight.x - mUpperLeft.x +1; int nbTilesForZoomLevel = x * y; total += nbTilesForZoomLevel; } return total; } protected String zoomMessage(int zoomLevel, int zoomMin, int zoomMax){ return "Handling zoom level: "+zoomLevel+" (from "+zoomMin+" to "+zoomMax+")"; } /** * Download in background all tiles of the specified area in osmdroid cache. * @param ctx * @param bb * @param zoomMin * @param zoomMax */ public void downloadAreaAsync(Context ctx, BoundingBoxE6 bb, final int zoomMin, final int zoomMax){ new DownloadingTask(ctx, bb, zoomMin, zoomMax).execute(); } /** generic class for common code related to AsyncTask management */ protected abstract class CacheManagerTask extends AsyncTask<Object, Integer, Integer>{ ProgressDialog mProgressDialog; int mZoomMin, mZoomMax; BoundingBoxE6 mBB; Context mCtx; public CacheManagerTask(Context pCtx, BoundingBoxE6 pBB, final int pZoomMin, final int pZoomMax) { mCtx = pCtx; mProgressDialog = createProgressDialog(pCtx); mBB = pBB; mZoomMin = Math.max(pZoomMin, mMapView.getMinZoomLevel()); mZoomMax = Math.min(pZoomMax, mMapView.getMaxZoomLevel()); } protected ProgressDialog createProgressDialog(Context pCtx){ ProgressDialog pd = new ProgressDialog(pCtx); pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setCancelable(true); pd.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { cancel(true); } }); return pd; } @Override protected void onProgressUpdate(final Integer... count){ //count[0] = tile counter, count[1] = current zoom level mProgressDialog.setProgress(count[0]); mProgressDialog.setMessage(zoomMessage(count[1], mZoomMin, mZoomMax)); } } protected class DownloadingTask extends CacheManagerTask { public DownloadingTask(Context pCtx, BoundingBoxE6 pBB, final int pZoomMin, final int pZoomMax) { super(pCtx, pBB, pZoomMin, pZoomMax); } @Override protected void onPreExecute(){ mProgressDialog.setTitle("Downloading tiles"); mProgressDialog.setMessage(zoomMessage(mZoomMin, mZoomMin, mZoomMax)); int total = possibleTilesInArea(mBB, mZoomMin, mZoomMax); mProgressDialog.setMax(total); mProgressDialog.show(); } @Override protected void onPostExecute(final Integer errors) { if (errors != 0) Toast.makeText(mCtx, "Loading completed with " + errors + " errors.", Toast.LENGTH_SHORT).show(); if (mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } } @Override protected Integer doInBackground(Object... params) { int errors = downloadArea(); return errors; } /** Do the job. No attempt to * @return the number of loading errors. */ protected int downloadArea(){ OnlineTileSourceBase tileSource; if (mTileProvider.getTileSource() instanceof OnlineTileSourceBase) tileSource = (OnlineTileSourceBase)mTileProvider.getTileSource(); else { Log.e(BonusPackHelper.LOG_TAG, "TileSource is not an online tile source"); return 0; } int tileCounter = 0; int errors = 0; for (int zoomLevel=mZoomMin; zoomLevel<=mZoomMax; zoomLevel++){ Point mLowerRight = getMapTileFromCoordinates(mBB.getLatSouthE6()*1E-6, mBB.getLonEastE6()*1E-6, zoomLevel); Point mUpperLeft = getMapTileFromCoordinates(mBB.getLatNorthE6()*1E-6, mBB.getLonWestE6()*1E-6, zoomLevel); final int mapTileUpperBound = 1 << zoomLevel; //Get all the MapTiles from the upper left to the lower right: for (int y = mUpperLeft.y; y <= mLowerRight.y; y++) { for (int x = mUpperLeft.x; x <= mLowerRight.x; x++) { final int tileY = MyMath.mod(y, mapTileUpperBound); final int tileX = MyMath.mod(x, mapTileUpperBound); final MapTile tile = new MapTile(zoomLevel, tileX, tileY); //Drawable currentMapTile = mTileProvider.getMapTile(tile); boolean ok = loadTile(tileSource, tile); if (!ok) errors++; tileCounter++; if (tileCounter % 20 == 0){ if (isCancelled()) return errors; publishProgress(tileCounter, zoomLevel); } } } } return errors; } } //DownloadingTask /** * Remove all cached tiles in the specified area. * @param bb * @param zoomMin * @param zoomMax * @param tileSource */ public void cleanAreaAsync(Context ctx, BoundingBoxE6 bb, int zoomMin, int zoomMax){ new CleaningTask(ctx, bb, zoomMin, zoomMax).execute(); } protected class CleaningTask extends CacheManagerTask { public CleaningTask(Context pCtx, BoundingBoxE6 pBB, final int pZoomMin, final int pZoomMax) { super(pCtx, pBB, pZoomMin, pZoomMax); } @Override protected void onPreExecute(){ mProgressDialog.setTitle("Cleaning tiles"); mProgressDialog.setMessage(zoomMessage(mZoomMin, mZoomMin, mZoomMax)); int total = possibleTilesInArea(mBB, mZoomMin, mZoomMax); mProgressDialog.setMax(total); mProgressDialog.show(); } @Override protected void onPostExecute(final Integer deleted) { Toast.makeText(mCtx, "Cleaning completed, " + deleted + " tiles deleted.", Toast.LENGTH_SHORT).show(); if (mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } } @Override protected Integer doInBackground(Object... params) { int errors = cleanArea(); return errors; } /** Do the job. * @return the number of tiles deleted. */ protected int cleanArea(){ ITileSource tileSource = mTileProvider.getTileSource(); int deleted = 0; int tileCounter = 0; for (int zoomLevel=mZoomMin; zoomLevel<=mZoomMax; zoomLevel++){ Point mLowerRight = getMapTileFromCoordinates(mBB.getLatSouthE6()*1E-6, mBB.getLonEastE6()*1E-6, zoomLevel); Point mUpperLeft = getMapTileFromCoordinates(mBB.getLatNorthE6()*1E-6, mBB.getLonWestE6()*1E-6, zoomLevel); final int mapTileUpperBound = 1 << zoomLevel; //Get all the MapTiles from the upper left to the lower right: for (int y = mUpperLeft.y; y <= mLowerRight.y; y++) { for (int x = mUpperLeft.x; x <= mLowerRight.x; x++) { final int tileY = MyMath.mod(y, mapTileUpperBound); final int tileX = MyMath.mod(x, mapTileUpperBound); final MapTile tile = new MapTile(zoomLevel, tileX, tileY); File file = getFileName(tileSource, tile); if (file.exists()){ file.delete(); deleted++; } tileCounter++; if (tileCounter % 1000 == 0){ if (isCancelled()) return deleted; publishProgress(tileCounter, zoomLevel); } } } } return deleted; } } //CleaningTask /** @return volume currently use in the osmdroid local filesystem cache, in bytes. * Unfortunately, due to lack of visibility in osmdroid classes, we cannot reuse the TileWriter.mUsedCacheSpace * Note that this method currently takes a while. * */ public long currentCacheUsage(){ return directorySize(OpenStreetMapTileProviderConstants.TILE_PATH_BASE); } /** @return the capacity of the osmdroid local filesystem cache, in bytes. * This capacity is currently a hard-coded constant inside osmdroid. */ public long cacheCapacity(){ return OpenStreetMapTileProviderConstants.TILE_MAX_CACHE_SIZE_BYTES; } /** @return the total size of a directory and of its whole content, recursively */ public long directorySize(final File pDirectory) { long usedCacheSpace = 0; final File[] z = pDirectory.listFiles(); if (z != null) { for (final File file : z) { if (file.isFile()) { usedCacheSpace += file.length(); } else if (file.isDirectory()) { usedCacheSpace += directorySize(file); } } } return usedCacheSpace; } }