package org.osmdroid.tileprovider.tilesource; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.Log; import org.osmdroid.api.IMapView; import org.osmdroid.tileprovider.BitmapPool; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.ReusableBitmapDrawable; import org.osmdroid.tileprovider.util.Counters; import java.io.File; import java.io.InputStream; import java.util.Random; public abstract class BitmapTileSourceBase implements ITileSource { private static int globalOrdinal = 0; private final int mMinimumZoomLevel; private final int mMaximumZoomLevel; private final int mOrdinal; protected String mName; protected String mCopyright; protected final String mImageFilenameEnding; protected final Random random = new Random(); private final int mTileSizePixels; //private final string mResourceId; /** * Constructor * @param aName a human-friendly name for this tile source. this name is also used on the file system, to keep the characters linux file system friendly * @param aZoomMinLevel the minimum zoom level this tile source can provide * @param aZoomMaxLevel the maximum zoom level this tile source can provide * @param aTileSizePixels the tile size in pixels this tile source provides * @param aImageFilenameEnding the file name extension used when constructing the filename */ public BitmapTileSourceBase(final String aName, final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels, final String aImageFilenameEnding) { this(aName, aZoomMinLevel, aZoomMaxLevel, aTileSizePixels, aImageFilenameEnding, null); } /** * Constructor * @param aName a human-friendly name for this tile source. this name is also used on the file system, to keep the characters linux file system friendly * @param aZoomMinLevel the minimum zoom level this tile source can provide * @param aZoomMaxLevel the maximum zoom level this tile source can provide * @param aTileSizePixels the tile size in pixels this tile source provides * @param aImageFilenameEnding the file name extension used when constructing the filename */ public BitmapTileSourceBase(final String aName, final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels, final String aImageFilenameEnding, final String aCopyrightNotice) { mOrdinal = globalOrdinal++; mName = aName; mMinimumZoomLevel = aZoomMinLevel; mMaximumZoomLevel = aZoomMaxLevel; mTileSizePixels = aTileSizePixels; mImageFilenameEnding = aImageFilenameEnding; mCopyright = aCopyrightNotice; } @Override public int ordinal() { return mOrdinal; } @Override public String name() { return mName; } public String pathBase() { return mName; } public String imageFilenameEnding() { return mImageFilenameEnding; } @Override public int getMinimumZoomLevel() { return mMinimumZoomLevel; } @Override public int getMaximumZoomLevel() { return mMaximumZoomLevel; } @Override public int getTileSizePixels() { return mTileSizePixels; } @Override public Drawable getDrawable(final String aFilePath) throws LowMemoryException { //Log.d(IMapView.LOGTAG, aFilePath + " attempting to load bitmap"); try { // default implementation will load the file as a bitmap and create // a BitmapDrawable from it BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); BitmapPool.getInstance().applyReusableOptions(bitmapOptions); final Bitmap bitmap; //fix for API 15 see https://github.com/osmdroid/osmdroid/issues/227 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) bitmap=BitmapFactory.decodeFile(aFilePath); else bitmap = BitmapFactory.decodeFile(aFilePath, bitmapOptions); if (bitmap != null) { return new ReusableBitmapDrawable(bitmap); } else { File bmp = new File(aFilePath); if (bmp.exists()) { // if we couldn't load it then it's invalid - delete it Log.d(IMapView.LOGTAG, aFilePath + " is an invalid image file, deleting..."); try { new File(aFilePath).delete(); } catch (final Throwable e) { Log.e(IMapView.LOGTAG, "Error deleting invalid file: " + aFilePath, e); } } else Log.d(IMapView.LOGTAG, "Request tile: " + aFilePath + " does not exist"); } } catch (final OutOfMemoryError e) { Log.e(IMapView.LOGTAG,"OutOfMemoryError loading bitmap: " + aFilePath); System.gc(); throw new LowMemoryException(e); } catch (final Exception e){ Log.e(IMapView.LOGTAG,"Unexpected error loading bitmap: " + aFilePath,e); Counters.tileDownloadErrors++; System.gc(); } return null; } @Override public String getTileRelativeFilenameString(final MapTile tile) { final StringBuilder sb = new StringBuilder(); sb.append(pathBase()); sb.append('/'); sb.append(tile.getZoomLevel()); sb.append('/'); sb.append(tile.getX()); sb.append('/'); sb.append(tile.getY()); sb.append(imageFilenameEnding()); return sb.toString(); } @Override public Drawable getDrawable(final InputStream aFileInputStream) throws LowMemoryException { try { // default implementation will load the file as a bitmap and create // a BitmapDrawable from it BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); BitmapPool.getInstance().applyReusableOptions(bitmapOptions); final Bitmap bitmap = BitmapFactory.decodeStream(aFileInputStream, null, bitmapOptions); if (bitmap != null) { return new ReusableBitmapDrawable(bitmap); } } catch (final OutOfMemoryError e) { Log.e(IMapView.LOGTAG,"OutOfMemoryError loading bitmap"); System.gc(); throw new LowMemoryException(e); } catch (Exception ex) { Log.w(IMapView.LOGTAG,"#547 Error loading bitmap" + pathBase(), ex); } return null; } public static final class LowMemoryException extends Exception { private static final long serialVersionUID = 146526524087765134L; public LowMemoryException(final String pDetailMessage) { super(pDetailMessage); } public LowMemoryException(final Throwable pThrowable) { super(pThrowable); } } @Override public String getCopyrightNotice(){ return mCopyright; } }