org.osmdroid.tileprovider.MapTileProviderBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of osmdroid-android Show documentation
Show all versions of osmdroid-android Show documentation
An Android library to display OpenStreetMap views.
// Created by plusminus on 21:46:22 - 25.09.2008
package org.osmdroid.tileprovider;
import java.util.HashMap;
import microsoft.mappoint.TileSystem;
import org.osmdroid.tileprovider.constants.OpenStreetMapTileProviderConstants;
import org.osmdroid.tileprovider.modules.MapTileModuleProviderBase;
import org.osmdroid.tileprovider.tilesource.ITileSource;
import org.osmdroid.util.TileLooper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
/**
* This is an abstract class. The tile provider is responsible for:
*
* - determining if a map tile is available,
* - notifying the client, via a callback handler
*
* see {@link MapTile} for an overview of how tiles are served by this provider.
*
* @author Marc Kurtz
* @author Nicolas Gramlich
*
*/
public abstract class MapTileProviderBase implements IMapTileProviderCallback,
OpenStreetMapTileProviderConstants {
private static final Logger logger = LoggerFactory.getLogger(MapTileProviderBase.class);
protected final MapTileCache mTileCache;
protected Handler mTileRequestCompleteHandler;
protected boolean mUseDataConnection = true;
private ITileSource mTileSource;
public abstract Drawable getMapTile(MapTile pTile);
public abstract void detach();
/**
* Gets the minimum zoom level this tile provider can provide
*
* @return the minimum zoom level
*/
public abstract int getMinimumZoomLevel();
/**
* Gets the maximum zoom level this tile provider can provide
*
* @return the maximum zoom level
*/
public abstract int getMaximumZoomLevel();
/**
* Sets the tile source for this tile provider.
*
* @param pTileSource
* the tile source
*/
public void setTileSource(final ITileSource pTileSource) {
mTileSource = pTileSource;
clearTileCache();
}
/**
* Gets the tile source for this tile provider.
*
* @return the tile source
*/
public ITileSource getTileSource() {
return mTileSource;
}
/**
* Creates a {@link MapTileCache} to be used to cache tiles in memory.
*/
public MapTileCache createTileCache() {
return new MapTileCache();
}
public MapTileProviderBase(final ITileSource pTileSource) {
this(pTileSource, null);
}
public MapTileProviderBase(final ITileSource pTileSource,
final Handler pDownloadFinishedListener) {
mTileCache = this.createTileCache();
mTileRequestCompleteHandler = pDownloadFinishedListener;
mTileSource = pTileSource;
}
/**
* Called by implementation class methods indicating that they have completed the request as
* best it can. The tile is added to the cache, and a MAPTILE_SUCCESS_ID message is sent.
*
* @param pState
* the map tile request state object
* @param pDrawable
* the Drawable of the map tile
*/
@Override
public void mapTileRequestCompleted(final MapTileRequestState pState, final Drawable pDrawable) {
// put the tile in the cache
putTileIntoCache(pState, pDrawable);
// tell our caller we've finished and it should update its view
if (mTileRequestCompleteHandler != null) {
mTileRequestCompleteHandler.sendEmptyMessage(MapTile.MAPTILE_SUCCESS_ID);
}
if (DEBUGMODE) {
logger.debug("MapTile request complete: " + pState.getMapTile());
}
}
/**
* Called by implementation class methods indicating that they have failed to retrieve the
* requested map tile. a MAPTILE_FAIL_ID message is sent.
*
* @param pState
* the map tile request state object
*/
@Override
public void mapTileRequestFailed(final MapTileRequestState pState) {
if (mTileRequestCompleteHandler != null) {
mTileRequestCompleteHandler.sendEmptyMessage(MapTile.MAPTILE_FAIL_ID);
}
if (DEBUGMODE) {
logger.debug("MapTile request failed: " + pState.getMapTile());
}
}
/**
* Called by implementation class methods indicating that they have produced an expired result
* that can be used but better results may be delivered later. The tile is added to the cache,
* and a MAPTILE_SUCCESS_ID message is sent.
*
* @param pState
* the map tile request state object
* @param pDrawable
* the Drawable of the map tile
*/
@Override
public void mapTileRequestExpiredTile(MapTileRequestState pState, Drawable pDrawable) {
// Put the expired tile into the cache
putExpiredTileIntoCache(pState, pDrawable);
// tell our caller we've finished and it should update its view
if (mTileRequestCompleteHandler != null) {
mTileRequestCompleteHandler.sendEmptyMessage(MapTile.MAPTILE_SUCCESS_ID);
}
if (DEBUGMODE) {
logger.debug("MapTile request complete: " + pState.getMapTile());
}
}
protected void putTileIntoCache(MapTileRequestState pState, Drawable pDrawable) {
final MapTile tile = pState.getMapTile();
if (pDrawable != null) {
mTileCache.putTile(tile, pDrawable);
}
}
protected void putExpiredTileIntoCache(MapTileRequestState pState, Drawable pDrawable) {
final MapTile tile = pState.getMapTile();
if (pDrawable != null && !mTileCache.containsTile(tile)) {
mTileCache.putTile(tile, pDrawable);
}
}
public void setTileRequestCompleteHandler(final Handler handler) {
mTileRequestCompleteHandler = handler;
}
public void ensureCapacity(final int pCapacity) {
mTileCache.ensureCapacity(pCapacity);
}
public void clearTileCache() {
mTileCache.clear();
}
/**
* Whether to use the network connection if it's available.
*/
@Override
public boolean useDataConnection() {
return mUseDataConnection;
}
/**
* Set whether to use the network connection if it's available.
*
* @param pMode
* if true use the network connection if it's available. if false don't use the
* network connection even if it's available.
*/
public void setUseDataConnection(final boolean pMode) {
mUseDataConnection = pMode;
}
/**
* Recreate the cache using scaled versions of the tiles currently in it
* @param pNewZoomLevel the zoom level that we need now
* @param pOldZoomLevel the previous zoom level that we should get the tiles to rescale
* @param pViewPort the view port we need tiles for
*/
public void rescaleCache(final int pNewZoomLevel, final int pOldZoomLevel, final Rect pViewPort) {
if (pNewZoomLevel == pOldZoomLevel) {
return;
}
final long startMs = System.currentTimeMillis();
logger.info("rescale tile cache from "+ pOldZoomLevel + " to " + pNewZoomLevel);
final int tileSize = getTileSource().getTileSizePixels();
final int worldSize_2 = TileSystem.MapSize(pNewZoomLevel) >> 1;
final Rect viewPort = new Rect(pViewPort);
viewPort.offset(worldSize_2, worldSize_2);
final ScaleTileLooper tileLooper = pNewZoomLevel > pOldZoomLevel
? new ZoomInTileLooper(pOldZoomLevel)
: new ZoomOutTileLooper(pOldZoomLevel);
tileLooper.loop(null, pNewZoomLevel, tileSize, viewPort);
final long endMs = System.currentTimeMillis();
logger.info("Finished rescale in " + (endMs - startMs) + "ms");
}
private abstract class ScaleTileLooper extends TileLooper {
/** new (scaled) tiles to add to cache
* NB first generate all and then put all in cache,
* otherwise the ones we need will be pushed out */
protected final HashMap mNewTiles;
protected final int mOldZoomLevel;
protected int mDiff;
protected int mTileSize_2;
protected Rect mSrcRect;
protected Rect mDestRect;
protected Paint mDebugPaint;
public ScaleTileLooper(final int pOldZoomLevel) {
mOldZoomLevel = pOldZoomLevel;
mNewTiles = new HashMap();
mSrcRect = new Rect();
mDestRect = new Rect();
mDebugPaint = new Paint();
}
@Override
public void initialiseLoop(final int pZoomLevel, final int pTileSizePx) {
mDiff = Math.abs(pZoomLevel - mOldZoomLevel);
mTileSize_2 = pTileSizePx >> mDiff;
}
@Override
public void handleTile(final Canvas pCanvas, final int pTileSizePx, final MapTile pTile, final int pX, final int pY) {
// Get tile from cache.
// If it's found then no need to created scaled version.
// If not found (null) them we've initiated a new request for it,
// and now we'll create a scaled version until the request completes.
final Drawable requestedTile = getMapTile(pTile);
if (requestedTile == null) {
try {
handleTile(pTileSizePx, pTile, pX, pY);
} catch(final OutOfMemoryError e) {
logger.error("OutOfMemoryError rescaling cache");
}
}
}
@Override
public void finaliseLoop() {
// now add the new ones, pushing out the old ones
while (!mNewTiles.isEmpty()) {
final MapTile tile = mNewTiles.keySet().iterator().next();
final Bitmap bitmap = mNewTiles.remove(tile);
final ExpirableBitmapDrawable drawable = new ExpirableBitmapDrawable(bitmap);
drawable.setState(new int[] { ExpirableBitmapDrawable.EXPIRED });
Drawable existingTile = mTileCache.getMapTile(tile);
if (existingTile == null || ExpirableBitmapDrawable.isDrawableExpired(existingTile))
putExpiredTileIntoCache(new MapTileRequestState(tile,
new MapTileModuleProviderBase[0], null), drawable);
}
}
protected abstract void handleTile(int pTileSizePx, MapTile pTile, int pX, int pY);
}
private class ZoomInTileLooper extends ScaleTileLooper {
public ZoomInTileLooper(final int pOldZoomLevel) {
super(pOldZoomLevel);
}
@Override
public void handleTile(final int pTileSizePx, final MapTile pTile, final int pX, final int pY) {
// get the correct fraction of the tile from cache and scale up
final MapTile oldTile = new MapTile(mOldZoomLevel, pTile.getX() >> mDiff, pTile.getY() >> mDiff);
final Drawable oldDrawable = mTileCache.getMapTile(oldTile);
if (oldDrawable instanceof BitmapDrawable) {
final Bitmap oldBitmap = ((BitmapDrawable)oldDrawable).getBitmap();
final int xx = (pX % (1 << mDiff)) * mTileSize_2;
final int yy = (pY % (1 << mDiff)) * mTileSize_2;
mSrcRect.set(xx, yy, xx + mTileSize_2, yy + mTileSize_2);
mDestRect.set(0, 0, pTileSizePx, pTileSizePx);
final Bitmap bitmap = Bitmap.createBitmap(pTileSizePx, pTileSizePx, Bitmap.Config.RGB_565);
final Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(oldBitmap, mSrcRect, mDestRect, null);
if (DEBUGMODE) {
logger.debug("Created scaled tile: " + pTile);
mDebugPaint.setTextSize(40);
canvas.drawText("scaled", 50, 50, mDebugPaint);
}
mNewTiles.put(pTile, bitmap);
}
}
}
private class ZoomOutTileLooper extends ScaleTileLooper {
private static final int MAX_ZOOM_OUT_DIFF = 4;
public ZoomOutTileLooper(final int pOldZoomLevel) {
super(pOldZoomLevel);
}
@Override
protected void handleTile(final int pTileSizePx, final MapTile pTile, final int pX, final int pY) {
if (mDiff >= MAX_ZOOM_OUT_DIFF){
return;
}
// get many tiles from cache and make one tile from them
final int xx = pTile.getX() << mDiff;
final int yy = pTile.getY() << mDiff;
final int numTiles = 1 << mDiff;
Bitmap bitmap = null;
Canvas canvas = null;
for(int x = 0; x < numTiles; x++) {
for(int y = 0; y < numTiles; y++) {
final MapTile oldTile = new MapTile(mOldZoomLevel, xx + x, yy + y);
final Drawable oldDrawable = mTileCache.getMapTile(oldTile);
if (oldDrawable instanceof BitmapDrawable) {
final Bitmap oldBitmap = ((BitmapDrawable)oldDrawable).getBitmap();
if (oldBitmap != null) {
if (bitmap == null) {
bitmap = Bitmap.createBitmap(pTileSizePx, pTileSizePx, Bitmap.Config.RGB_565);
canvas = new Canvas(bitmap);
canvas.drawColor(Color.LTGRAY);
}
mDestRect.set(
x * mTileSize_2, y * mTileSize_2,
(x + 1) * mTileSize_2, (y + 1) * mTileSize_2);
if (oldBitmap != null) {
canvas.drawBitmap(oldBitmap, null, mDestRect, null);
mTileCache.mCachedTiles.remove(oldBitmap);
}
}
}
}
}
if (bitmap != null) {
mNewTiles.put(pTile, bitmap);
if (DEBUGMODE) {
logger.debug("Created scaled tile: " + pTile);
mDebugPaint.setTextSize(40);
canvas.drawText("scaled", 50, 50, mDebugPaint);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy