package org.osmdroid.tileprovider.cachemanager; 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.text.TextUtils; import android.util.Log; import android.widget.Toast; import org.osmdroid.api.IMapView; import org.osmdroid.config.Configuration; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.MapTileProviderBase; import org.osmdroid.tileprovider.constants.OpenStreetMapTileProviderConstants; import org.osmdroid.tileprovider.modules.IFilesystemCache; import org.osmdroid.tileprovider.tilesource.ITileSource; import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase; import org.osmdroid.tileprovider.util.Counters; import org.osmdroid.tileprovider.util.StreamUtils; import org.osmdroid.util.BoundingBox; import org.osmdroid.util.GeoPoint; import org.osmdroid.util.MyMath; import org.osmdroid.util.TileSystem; import org.osmdroid.util.constants.GeoConstants; import org.osmdroid.views.MapView; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Set; ; /** * 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> * <p></p> * Important note 1: <br> * These methods only make sense for a MapView using an OnlineTileSourceBase: * bitmap tiles downloaded from urls. <br> * <p></p> * 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 * @author Alex * @author 2ndGAB */ public class CacheManager { protected final MapTileProviderBase mTileProvider; protected final IFilesystemCache mTileWriter; protected final MapView mMapView; protected Set<CacheManagerTask> mPendingTasks = new HashSet<>(); public CacheManager(final MapView mapView) { mTileProvider = mapView.getTileProvider(); mTileWriter = mapView.getTileProvider().getTileWriter(); mMapView = mapView; } public CacheManager(final MapView mapView, IFilesystemCache writer) { mTileProvider = mapView.getTileProvider(); mTileWriter = writer; mMapView = mapView; } /** * @since 5.6.3 * @return */ public int getPendingJobs(){ return mPendingTasks.size(); } public static 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 static GeoPoint getCoordinatesFromMapTile(final int x, final int y, final int zoom) { double n = Math.PI - 2 * Math.PI * y / (1 << zoom); final double lat = (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)))); final double lon = (360.0 * x / (1 << zoom)) - 180.0; return new GeoPoint(lat, lon); } public static File getFileName(ITileSource tileSource, MapTile tile) { final File file = new File(Configuration.getInstance().getOsmdroidTileCache(), tileSource.getTileRelativeFilenameString(tile) + OpenStreetMapTileProviderConstants.TILE_PATH_EXTENSION); return file; } /** * @return true if success, false if error */ public boolean loadTile(final OnlineTileSourceBase tileSource, final MapTile tile) { //check if file is already downloaded: File file = getFileName(tileSource, tile); if (file.exists()) { return true; } //check if the destination already has the file if (mTileWriter.exists(tileSource,tile)){ return true; } InputStream in = null; HttpURLConnection c=null; try { final String tileURLString = tileSource.getTileURLString(tile); if (Configuration.getInstance().isDebugMode()) { Log.d(IMapView.LOGTAG,"Downloading Maptile from url: " + tileURLString); } if (TextUtils.isEmpty(tileURLString)) { return false; } c = (HttpURLConnection) new URL(tileURLString).openConnection(); c.setUseCaches(true); c.setRequestProperty(Configuration.getInstance().getUserAgentHttpHeader(),Configuration.getInstance().getUserAgentValue()); c.connect(); // Check to see if we got success if (c.getResponseCode() != 200) { Log.w(IMapView.LOGTAG, "Problem downloading MapTile: " + tile + " HTTP response: " + c.getResponseMessage()); Counters.tileDownloadErrors++; return false; } in = c.getInputStream(); final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); //default is 1 week from now Date dateExpires; Long override=Configuration.getInstance().getExpirationOverrideDuration(); if (override!=null) { dateExpires= new Date(System.currentTimeMillis() + override); } else { dateExpires = new Date(System.currentTimeMillis() + OpenStreetMapTileProviderConstants.DEFAULT_MAXIMUM_CACHED_FILE_AGE + Configuration.getInstance().getExpirationExtendedDuration()); final String expires = c.getHeaderField(OpenStreetMapTileProviderConstants.HTTP_EXPIRES_HEADER); if (expires != null && expires.length() > 0) { try { dateExpires = Configuration.getInstance().getHttpHeaderDateTimeFormat().parse(expires); dateExpires.setTime(dateExpires.getTime() + Configuration.getInstance().getExpirationExtendedDuration()); } catch (Exception ex) { if (Configuration.getInstance().isDebugMapTileDownloader()) Log.d(IMapView.LOGTAG, "Unable to parse expiration tag for tile, using default, server returned " + expires, ex); } } } tile.setExpires(dateExpires); // Save the data to the filesystem cache mTileWriter.saveFile(tileSource, tile, in); return true; } catch (final UnknownHostException e) { // no network connection so empty the queue Log.w(IMapView.LOGTAG,"UnknownHostException downloading MapTile: " + tile + " : " + e); Counters.tileDownloadErrors++; return false; } catch (final FileNotFoundException e) { Counters.tileDownloadErrors++; Log.w(IMapView.LOGTAG,"Tile not found: " + tile + " : " + e); } catch (final IOException e) { Counters.tileDownloadErrors++; Log.w(IMapView.LOGTAG,"IOException downloading MapTile: " + tile + " : " + e); } catch (final Throwable e) { Counters.tileDownloadErrors++; Log.e(IMapView.LOGTAG,"Error downloading MapTile: " + tile, e); } finally { StreamUtils.closeStream(in); try{ c.disconnect(); } catch (Exception ex){} } return false; } /** * @return the theoretical number of tiles in the specified area */ public int possibleTilesInArea(BoundingBox bb, final int zoomMin, final int zoomMax) { int total = 0; for (int zoomLevel = zoomMin; zoomLevel <= zoomMax; zoomLevel++) { Point mLowerRight = getMapTileFromCoordinates(bb.getLatSouth(), bb.getLonEast(), zoomLevel); Point mUpperLeft = getMapTileFromCoordinates(bb.getLatNorth() , bb.getLonWest(), zoomLevel); int y = mLowerRight.y - mUpperLeft.y + 1; int x = mLowerRight.x - mUpperLeft.x + 1; int nbTilesForZoomLevel = x * y; total += nbTilesForZoomLevel; } return total; } /** * @return the theoretical number of tiles covered by the list of points * Calculation done based on http://www.movable-type.co.uk/scripts/latlong.html */ public int possibleTilesCovered(ArrayList<GeoPoint> geoPoints, final int zoomMin, final int zoomMax) { ArrayList<Point> tilePoints = new ArrayList<>(); boolean foundTilePoint; GeoPoint prevPoint = null, wayPoint; double d, leadCoef, brng, latRad, lonRad, prevLatRad, prevLonRad; Point tile, prevTile = null, lastPoint; for (int zoomLevel = zoomMin; zoomLevel <= zoomMax; zoomLevel++) { for (GeoPoint geoPoint : geoPoints) { d = TileSystem.GroundResolution(geoPoint.getLatitude(), zoomLevel); if (tilePoints.size() != 0) { if (prevPoint != null) { leadCoef = (geoPoint.getLatitude() - prevPoint.getLatitude()) / (geoPoint.getLongitude() - prevPoint.getLongitude()); if (geoPoint.getLongitude() > prevPoint.getLongitude()) { brng = Math.PI / 2 - Math.atan(leadCoef); } else { brng = 3 * Math.PI / 2 - Math.atan(leadCoef); } wayPoint = new GeoPoint(prevPoint.getLatitude(), prevPoint.getLongitude()); while ((((geoPoint.getLatitude() > prevPoint.getLatitude()) && (wayPoint.getLatitude() < geoPoint.getLatitude())) || (geoPoint.getLatitude() < prevPoint.getLatitude()) && (wayPoint.getLatitude() > geoPoint.getLatitude())) && (((geoPoint.getLongitude() > prevPoint.getLongitude()) && (wayPoint.getLongitude() < geoPoint.getLongitude())) || ((geoPoint.getLongitude() < prevPoint.getLongitude()) && (wayPoint.getLongitude() > geoPoint.getLongitude())))) { lastPoint = new Point(); TileSystem.LatLongToPixelXY(geoPoint.getLatitude(), geoPoint.getLongitude(), zoomLevel, lastPoint); prevLatRad = wayPoint.getLatitude() * Math.PI / 180.0; prevLonRad = wayPoint.getLongitude() * Math.PI / 180.0; latRad = Math.asin(Math.sin(prevLatRad) * Math.cos(d / GeoConstants.RADIUS_EARTH_METERS) + Math.cos(prevLatRad) * Math.sin(d / GeoConstants.RADIUS_EARTH_METERS) * Math.cos(brng)); lonRad = prevLonRad + Math.atan2(Math.sin(brng) * Math.sin(d / GeoConstants.RADIUS_EARTH_METERS) * Math.cos(prevLatRad), Math.cos(d / GeoConstants.RADIUS_EARTH_METERS) - Math.sin(prevLatRad) * Math.sin(latRad)); wayPoint.setLatitude(((latRad * 180.0 / Math.PI))); wayPoint.setLongitude(((lonRad * 180.0 / Math.PI))); tile = getMapTileFromCoordinates(wayPoint.getLatitude(), wayPoint.getLongitude(), zoomLevel); if (!tile.equals(prevTile)) { //Log.d(Constants.APP_TAG, "New Tile lat " + tile.x + " lon " + tile.y); int ofsx = tile.x >= 0 ? 0 : -tile.x; int ofsy = tile.y >= 0 ? 0 : -tile.y; for (int xAround = tile.x + ofsx; xAround <= tile.x + 1 + ofsx; xAround++) { for (int yAround = tile.y + ofsy; yAround <= tile.y + 1 + ofsy; yAround++) { Point tileAround = new Point(xAround, yAround); foundTilePoint = false; for (Point inList : tilePoints) { if (tileAround.equals(inList.x, inList.y)) { foundTilePoint = true; break; } } if (!foundTilePoint) { tilePoints.add(0, tileAround); } } } prevTile = tile; } } } } else { tile = getMapTileFromCoordinates(geoPoint.getLatitude(), geoPoint.getLongitude(), zoomLevel); prevTile = tile; int ofsx = tile.x >= 0 ? 0 : -tile.x; int ofsy = tile.y >= 0 ? 0 : -tile.y; for (int xAround = tile.x + ofsx; xAround <= tile.x + 1 + ofsx; xAround++) { for (int yAround = tile.y + ofsy; yAround <= tile.y + 1 + ofsy; yAround++) { Point tileAround = new Point(xAround, yAround); tilePoints.add(0, tileAround); } } } prevPoint = geoPoint; } } Log.d(IMapView.LOGTAG, "need " + tilePoints.size() + " Tiles"); return tilePoints.size(); } 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 DownloadingTask downloadAreaAsync(Context ctx, BoundingBox bb, final int zoomMin, final int zoomMax) { DownloadingTask task=new DownloadingTask(ctx, bb, zoomMin, zoomMax, null, true); task.execute(); mPendingTasks.add(task); return task; } /** * Download in background all tiles of the specified area in osmdroid cache. * * @param ctx * @param geoPoints * @param zoomMin * @param zoomMax */ public DownloadingTask downloadAreaAsync(Context ctx, ArrayList<GeoPoint> geoPoints, final int zoomMin, final int zoomMax) { DownloadingTask task=new DownloadingTask(ctx, geoPoints, zoomMin, zoomMax, null, true); task.execute(); mPendingTasks.add(task); return task; } /** * Download in background all tiles of the specified area in osmdroid cache. * * @param ctx * @param bb * @param zoomMin * @param zoomMax */ public DownloadingTask downloadAreaAsync(Context ctx, BoundingBox bb, final int zoomMin, final int zoomMax, final CacheManagerCallback callback) { DownloadingTask task =new DownloadingTask(ctx, bb, zoomMin, zoomMax, callback, true); task.execute(); mPendingTasks.add(task); return task; } /** * Download in background all tiles covered by the GePoints list in osmdroid cache. * * @param ctx * @param geoPoints * @param zoomMin * @param zoomMax */ public DownloadingTask downloadAreaAsync(Context ctx, ArrayList<GeoPoint> geoPoints, final int zoomMin, final int zoomMax, final CacheManagerCallback callback) { DownloadingTask task = new DownloadingTask(ctx, geoPoints, zoomMin, zoomMax, callback, true); task.execute(); mPendingTasks.add(task); return task; } /** * Download in background all tiles covered by the GeoPoints list in osmdroid cache without a user interface. * * @param ctx * @param geoPoints * @param zoomMin * @param zoomMax * @since */ public DownloadingTask downloadAreaAsyncNoUI(Context ctx, ArrayList<GeoPoint> geoPoints, final int zoomMin, final int zoomMax, final CacheManagerCallback callback) { DownloadingTask task=new DownloadingTask(ctx, geoPoints, zoomMin, zoomMax, callback, false); task.execute(); mPendingTasks.add(task); return task; } /** * Download in background all tiles of the specified area in osmdroid cache without a user interface. * * @param ctx * @param bb * @param zoomMin * @param zoomMax * @since 5.3 */ public DownloadingTask downloadAreaAsyncNoUI(Context ctx, BoundingBox bb, final int zoomMin, final int zoomMax, final CacheManagerCallback callback) { DownloadingTask task=new DownloadingTask(ctx, bb, zoomMin, zoomMax, callback, false); task.execute(); mPendingTasks.add(task); return task; } /** * cancels all tasks * @since 5.6.3 */ public void cancelAllJobs(){ Iterator<CacheManagerTask> iterator = mPendingTasks.iterator(); while (iterator.hasNext()) { CacheManagerTask next = iterator.next(); next.cancel(true); } mPendingTasks.clear(); } /** * */ public interface CacheManagerCallback { /** * fired when the download job is done. */ public void onTaskComplete(); /** * this is fired periodically, useful for updating dialogs, progress bars, etc * * @param progress * @param currentZoomLevel * @param zoomMin * @param zoomMax */ public void updateProgress(int progress, int currentZoomLevel, int zoomMin, int zoomMax); /** * as soon as the download is started, this is fired */ public void downloadStarted(); /** * this is fired right before the download starts * * @param total */ public void setPossibleTilesInArea(int total); /** * this is fired when the task has been completed but had at least one download error. * @param errors */ public void onTaskFailed(int errors); } /** * generic class for common code related to AsyncTask management */ public abstract class CacheManagerTask extends AsyncTask<Object, Integer, Integer> { ProgressDialog mProgressDialog=null; boolean showUI = true; int mZoomMin, mZoomMax; BoundingBox mBB; ArrayList<GeoPoint> mGeoPoints; Context mCtx; CacheManagerCallback callback = null; public CacheManagerTask(Context pCtx, BoundingBox pBB, final int pZoomMin, final int pZoomMax, final CacheManagerCallback callback, final boolean showUI) { this(pCtx, pBB, pZoomMin, pZoomMax); this.callback = callback; this.showUI = showUI; } public CacheManagerTask(Context pCtx, ArrayList<GeoPoint> geoPoints, final int pZoomMin, final int pZoomMax, final CacheManagerCallback callback, final boolean showUI) { this(pCtx, geoPoints, pZoomMin, pZoomMax); this.callback = callback; this.showUI = showUI; this.mGeoPoints = geoPoints; } public CacheManagerTask(Context pCtx, ArrayList<GeoPoint> pGeoPoints, final int pZoomMin, final int pZoomMax) { mCtx = pCtx; mGeoPoints = pGeoPoints; mZoomMin = Math.max(pZoomMin, mMapView.getMinZoomLevel()); mZoomMax = Math.min(pZoomMax, mMapView.getMaxZoomLevel()); } public CacheManagerTask(Context pCtx, BoundingBox pBB, final int pZoomMin, final int pZoomMax) { mCtx = pCtx; mBB = pBB; mZoomMin = Math.max(pZoomMin, mMapView.getMinZoomLevel()); mZoomMax = Math.min(pZoomMax, mMapView.getMaxZoomLevel()); } @Override protected void onPreExecute(){ if (showUI) { mProgressDialog = createProgressDialog(mCtx); } } 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 if (showUI) { mProgressDialog.setProgress(count[0]); mProgressDialog.setMessage(zoomMessage(count[1], mZoomMin, mZoomMax)); } if (callback != null) { try { callback.updateProgress(count[0], count[1], mZoomMin, mZoomMax); } catch (Throwable t) { Log.w(IMapView.LOGTAG, "Error caught processing cachemanager callback, your implementation is faulty", t); } } } @Override protected void onCancelled(){ super.onCancelled(); mPendingTasks.remove(this); } } public class DownloadingTask extends CacheManagerTask { public DownloadingTask(Context pCtx, BoundingBox pBB, final int pZoomMin, final int pZoomMax, final CacheManagerCallback callback, final boolean showUI) { super(pCtx, pBB, pZoomMin, pZoomMax, callback, showUI); } public DownloadingTask(Context pCtx, ArrayList<GeoPoint> pPoints, final int pZoomMin, final int pZoomMax, final CacheManagerCallback callback, final boolean showUI) { super(pCtx, pPoints, pZoomMin, pZoomMax, callback, showUI); } @Override protected void onPreExecute() { super.onPreExecute(); int total = 0; if (mBB != null) { total = possibleTilesInArea(mBB, mZoomMin, mZoomMax); } else if (mGeoPoints != null) { total = possibleTilesCovered(mGeoPoints, mZoomMin, mZoomMax); } if (showUI) { mProgressDialog.setTitle("Downloading tiles"); mProgressDialog.setMessage(zoomMessage(mZoomMin, mZoomMin, mZoomMax)); mProgressDialog.setMax(total); mProgressDialog.show(); } if (callback != null) { try { callback.setPossibleTilesInArea(total); callback.downloadStarted(); } catch (Throwable t) { Log.w(IMapView.LOGTAG, "Error caught processing cachemanager callback, your implementation is faulty", t); } } } @Override protected void onPostExecute(final Integer errors) { if (showUI) { if (errors != 0) { Toast.makeText(mCtx, "Loading completed with " + errors + " errors.", Toast.LENGTH_SHORT).show(); } if (mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } } if (callback != null) { try { if (errors == 0) { callback.onTaskComplete(); } else { callback.onTaskFailed(errors); } } catch (Throwable t) { Log.w(IMapView.LOGTAG, "Error caught processing cachemanager callback, your implementation is faulty", t); } } mPendingTasks.remove(this); } @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(IMapView.LOGTAG, "TileSource is not an online tile source"); return 0; } int tileCounter = 0; int errors = 0; if (mBB != null) { for (int zoomLevel = mZoomMin; zoomLevel <= mZoomMax; zoomLevel++) { Point mLowerRight = getMapTileFromCoordinates(mBB.getLatSouth() , mBB.getLonEast(), zoomLevel); Point mUpperLeft = getMapTileFromCoordinates(mBB.getLatNorth() , mBB.getLonWest(), 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 % 10 == 0) { if (isCancelled()) { return errors; } publishProgress(tileCounter, zoomLevel); } } } } } else if (mGeoPoints != null) { GeoPoint prevPoint = null, wayPoint; double d, leadCoef, brng, latRad, lonRad, prevLatRad, prevLonRad; Point tile, prevTile = null, lastPoint; ArrayList<Point> tilePoints = new ArrayList<>(); boolean foundTilePoint; for (int zoomLevel = mZoomMin; zoomLevel <= mZoomMax; zoomLevel++) { final int mapTileUpperBound = 1 << zoomLevel; for (GeoPoint geoPoint : mGeoPoints) { d = TileSystem.GroundResolution(geoPoint.getLatitude(), zoomLevel); if (tileCounter != 0) { if (prevPoint != null) { leadCoef = (geoPoint.getLatitude() - prevPoint.getLatitude()) / (geoPoint.getLongitude() - prevPoint.getLongitude()); if (geoPoint.getLongitude() > prevPoint.getLongitude()) { brng = Math.PI / 2 - Math.atan(leadCoef); } else { brng = 3 * Math.PI / 2 - Math.atan(leadCoef); } wayPoint = new GeoPoint(prevPoint.getLatitude(), prevPoint.getLongitude()); while ((((geoPoint.getLatitude() > prevPoint.getLatitude()) && (wayPoint.getLatitude() < geoPoint.getLatitude())) || (geoPoint.getLatitude() < prevPoint.getLatitude()) && (wayPoint.getLatitude() > geoPoint.getLatitude())) && (((geoPoint.getLongitude() > prevPoint.getLongitude()) && (wayPoint.getLongitude() < geoPoint.getLongitude())) || ((geoPoint.getLongitude() < prevPoint.getLongitude()) && (wayPoint.getLongitude() > geoPoint.getLongitude())))) { lastPoint = new Point(); TileSystem.LatLongToPixelXY(geoPoint.getLatitude(), geoPoint.getLongitude(), zoomLevel, lastPoint); prevLatRad = wayPoint.getLatitude() * Math.PI / 180.0; prevLonRad = wayPoint.getLongitude() * Math.PI / 180.0; latRad = Math.asin(Math.sin(prevLatRad) * Math.cos(d / GeoConstants.RADIUS_EARTH_METERS) + Math.cos(prevLatRad) * Math.sin(d / GeoConstants.RADIUS_EARTH_METERS) * Math.cos(brng)); lonRad = prevLonRad + Math.atan2(Math.sin(brng) * Math.sin(d / GeoConstants.RADIUS_EARTH_METERS) * Math.cos(prevLatRad), Math.cos(d / GeoConstants.RADIUS_EARTH_METERS) - Math.sin(prevLatRad) * Math.sin(latRad)); wayPoint.setLatitude(((latRad * 180.0 / Math.PI))); wayPoint.setLongitude(((lonRad * 180.0 / Math.PI))); tile = getMapTileFromCoordinates(wayPoint.getLatitude(), wayPoint.getLongitude(), zoomLevel); if (!tile.equals(prevTile)) { //Log.d(Constants.APP_TAG, "New Tile lat " + tile.x + " lon " + tile.y); int ofsx = tile.x >= 0 ? 0 : -tile.x; int ofsy = tile.y >= 0 ? 0 : -tile.y; for (int xAround = tile.x + ofsx; xAround <= tile.x + 1 + ofsx; xAround++) { for (int yAround = tile.y + ofsy; yAround <= tile.y + 1 + ofsy; yAround++) { Point tileAround = new Point(xAround, yAround); foundTilePoint = false; /** * The following is only necessary to correctly update progress * as we cannot know if a tile is already downloaded or not. * Normally loadTile() will only download a tile if necessary */ for (Point inList : tilePoints) { if (tileAround.equals(inList.x, inList.y)) { foundTilePoint = true; break; } } if (!foundTilePoint) { final int tileY = MyMath.mod(tileAround.y, mapTileUpperBound); final int tileX = MyMath.mod(tileAround.x, mapTileUpperBound); final MapTile tileToDownload = new MapTile(zoomLevel, tileX, tileY); //Drawable currentMapTile = mTileProvider.getMapTile(tile); boolean ok = loadTile(tileSource, tileToDownload); if (!ok) { errors++; } tileCounter++; if (tileCounter % 10 == 0) { if (isCancelled()) { return errors; } publishProgress(tileCounter, zoomLevel); } tilePoints.add(0, tileAround); } } } prevTile = tile; } } } } else { tile = getMapTileFromCoordinates(geoPoint.getLatitude(), geoPoint.getLongitude(), zoomLevel); prevTile = tile; //Log.d(Constants.APP_TAG, "New Tile lat " + tile.x + " lon " + tile.y); int ofsx = tile.x >= 0 ? 0 : -tile.x; int ofsy = tile.y >= 0 ? 0 : -tile.y; for (int xAround = tile.x + ofsx; xAround <= tile.x + 1 + ofsx; xAround ++) { for (int yAround = tile.y + ofsy; yAround <= tile.y + 1 + ofsy; yAround ++) { Point tileAround = new Point(xAround, yAround); final int tileY = MyMath.mod(tileAround.y, mapTileUpperBound); final int tileX = MyMath.mod(tileAround.x, mapTileUpperBound); final MapTile tileToDownload = new MapTile(zoomLevel, tileX, tileY); //Drawable currentMapTile = mTileProvider.getMapTile(tile); boolean ok = loadTile(tileSource, tileToDownload); if (!ok) { errors++; } tileCounter++; if (tileCounter % 10 == 0) { if (isCancelled()) { return errors; } publishProgress(tileCounter, zoomLevel); } tilePoints.add(0, tileAround); } } } prevPoint = geoPoint; } } Log.d(IMapView.LOGTAG, "downloaded " + tilePoints.size() + " tiles"); } return errors; } } //DownloadingTask /** * Remove all cached tiles in the specified area. * * @param ctx * @param bb * @param zoomMin * @param zoomMax */ public CleaningTask cleanAreaAsync(Context ctx, BoundingBox bb, int zoomMin, int zoomMax) { CleaningTask task = new CleaningTask(ctx, bb, zoomMin, zoomMax); task.execute(); mPendingTasks.add(task); return task; } /** * Remove all cached tiles covered by the GeoPoints list. * * @param ctx * @param geoPoints * @param zoomMin * @param zoomMax */ public CleaningTask cleanAreaAsync(Context ctx, ArrayList<GeoPoint> geoPoints, int zoomMin, int zoomMax) { BoundingBox extendedBounds = extendedBoundsFromGeoPoints(geoPoints,zoomMin); CleaningTask task = new CleaningTask(ctx, extendedBounds, zoomMin, zoomMax); task.execute(); mPendingTasks.add(task); return task; } /** * */ public BoundingBox extendedBoundsFromGeoPoints(ArrayList<GeoPoint> geoPoints, int minZoomLevel) { BoundingBox bb = BoundingBox.fromGeoPoints(geoPoints); Point mLowerRight = getMapTileFromCoordinates(bb.getLatSouth() , bb.getLonEast() , minZoomLevel); GeoPoint lowerRightPoint = getCoordinatesFromMapTile(mLowerRight.x+1, mLowerRight.y+1, minZoomLevel); Point mUpperLeft = getMapTileFromCoordinates(bb.getLatNorth() , bb.getLonWest(), minZoomLevel); GeoPoint upperLeftPoint = getCoordinatesFromMapTile(mUpperLeft.x-1, mUpperLeft.y-1, minZoomLevel); BoundingBox extendedBounds = new BoundingBox(upperLeftPoint.getLatitude(), upperLeftPoint.getLongitude(), lowerRightPoint.getLatitude(), lowerRightPoint.getLongitude()); return extendedBounds; } public class CleaningTask extends CacheManagerTask { public CleaningTask(Context pCtx, BoundingBox pBB, final int pZoomMin, final int pZoomMax) { super(pCtx, pBB, pZoomMin, pZoomMax); showUI=true; } @Override protected void onPreExecute() { super.onPreExecute(); if (mProgressDialog!=null) { 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!=null && mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } mPendingTasks.remove(this); } @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.getLatSouth(), mBB.getLonEast() , zoomLevel); Point mUpperLeft = getMapTileFromCoordinates(mBB.getLatNorth() , mBB.getLonWest() , zoomLevel); final int mapTileUpperBound = 1 << zoomLevel; //Get all the MapTiles from the upper left to the lower right: //In case we used GeoPoint list, we also have to take care of the tiles around the area. int ofsy = mUpperLeft.y > 0 ? -1 : 0; int ofsx = mUpperLeft.x > 0 ? -1 : 0; for (int y = mUpperLeft.y + ofsy; y <= mLowerRight.y + 2 + ofsy; y++) { for (int x = mUpperLeft.x + ofsx; x <= mLowerRight.x + 2 + ofsx; x++) { final int tileY = MyMath.mod(y, mapTileUpperBound); final int tileX = MyMath.mod(x, mapTileUpperBound); final MapTile tile = new MapTile(zoomLevel, tileX, tileY); if (mTileWriter.exists(tileSource, tile)){ if (mTileWriter.remove(tileSource, tile)) 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. * Note that this method currently takes a while. */ public long currentCacheUsage() { //return TileWriter.getUsedCacheSpace(); //returned value is not stable! Increase and decrease, for unknown reasons. return directorySize(Configuration.getInstance().getOsmdroidTileCache()); } /** * @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 Configuration.getInstance().getTileFileSystemCacheMaxBytes(); } /** * @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; } }