osmdroid API解读(九)
补充说明
瓦片源用于描述瓦片的编号系统、瓦片来源、图片大小、图片格式等。IFilesystemCache接口下的*Writer用于写瓦片到本地。IArchiveFile的实现用于解析本地不同类型的数据。tile provider将前者组织起来,并提供给MapView提供瓦片以及瓦片信息。
往往瓦片提供者并不是单独使用的,而是将多个瓦片提供者组织起来同时使用,关于如何联合使用以及如何使用这就是一些要解析的内容。
osmdroid-android org.osmdroid.tileprovider 包(二)
1. IMapTileProviderCallback
瓦片提供者状态MapTileRequestState回调接口
public interface IMapTileProviderCallback {
//瓦片请求完成时回调方法 aState表示回调该接口的实体,aDrawable是请求结果
void mapTileRequestCompleted(MapTileRequestState aState, final Drawable aDrawable);
//瓦片请求失败是回调 aState表示回调该接口的实体
void mapTileRequestFailed(MapTileRequestState aState);
//请求到过期瓦片 aState表示回调该接口的实体,aDrawable是请求结果
void mapTileRequestExpiredTile(MapTileRequestState aState, final Drawable aDrawable);
//网络连接时返回true,否则返回false
public boolean useDataConnection();
}
2. MapTileRequestState
完整的一次瓦片请求时所需要持有的信息:
- 持有要请求瓦片的瓦片信息mMapTile
- 要请求的瓦片源提供者队列mProviderQueue
请求完成回调信息mCallback
public class MapTileRequestState { private final List<MapTileModuleProviderBase> mProviderQueue;//欲请求瓦片的瓦片提供者队列 private final MapTile mMapTile;//要请求的瓦片 private final IMapTileProviderCallback mCallback;//请求结束回调接口 private int index; private MapTileModuleProviderBase mCurrentProvider; @Deprecated public MapTileRequestState(final MapTile mapTile, final MapTileModuleProviderBase[] providers, final IMapTileProviderCallback callback) { mProviderQueue = new ArrayList<>(); Collections.addAll(mProviderQueue, providers); mMapTile = mapTile; mCallback = callback; } public MapTileRequestState(final MapTile mapTile, final List<MapTileModuleProviderBase> providers, final IMapTileProviderCallback callback) { mProviderQueue = providers; mMapTile = mapTile; mCallback = callback; } public MapTile getMapTile() { return mMapTile; } public IMapTileProviderCallback getCallback() { return mCallback; } public boolean isEmpty() { return mProviderQueue == null || index >= mProviderQueue.size(); } public MapTileModuleProviderBase getNextProvider() { mCurrentProvider = isEmpty() ? null : mProviderQueue.get(index ++); return mCurrentProvider; } public MapTileModuleProviderBase getCurrentProvider() { return mCurrentProvider; } }
3. MapTileProviderBase
该抽象类用于:
- 确定地图瓦片的可用性
通过回调handler通知客户端
public abstract class MapTileProviderBase implements IMapTileProviderCallback {
protected final MapTileCache mTileCache; protected Handler mTileRequestCompleteHandler; protected boolean mUseDataConnection = true; protected Drawable mTileNotFoundImage = null; private ITileSource mTileSource; //设法获取一个MapTile对应的Drawable,如果没有相应的瓦片对应图片返回null。并设法从已知的tile source中请求。 public abstract Drawable getMapTile(MapTile pTile); //此类的子类必须调用该方法以阻止内存泄漏 public void detach(){ ... } //获取最大、最小缩放等级 public abstract int getMinimumZoomLevel(); public abstract int getMaximumZoomLevel(); //Get & Set 瓦片源 public void setTileSource(final ITileSource pTileSource) { mTileSource = pTileSource; clearTileCache(); } public ITileSource getTileSource() { return mTileSource; } //创建MapTileCache用于内存中缓存瓦片 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; } //设置瓦片载入失败时的显示图片,默认为灰色网格 public void setTileLoadFailureImage(final Drawable drawable){ this.mTileNotFoundImage = drawable; } //请求成功时,将瓦片放入缓存,并发送MAPTILE_SUCCESS_ID给handler @Override public void mapTileRequestCompleted(final MapTileRequestState pState, final Drawable pDrawable) { // put the tile in the cache putTileIntoCache(pState.getMapTile(), pDrawable, ExpirableBitmapDrawable.UP_TO_DATE); // tell our caller we've finished and it should update its view if (mTileRequestCompleteHandler != null) { mTileRequestCompleteHandler.sendEmptyMessage(MapTile.MAPTILE_SUCCESS_ID); } if (Configuration.getInstance().isDebugTileProviders()) { Log.d(IMapView.LOGTAG,"MapTileProviderBase.mapTileRequestCompleted(): " + pState.getMapTile()); } } //请求失败,MAPTILE_FAIL_ID信息发送给handler @Override public void mapTileRequestFailed(final MapTileRequestState pState) { if (mTileNotFoundImage!=null) { putTileIntoCache(pState.getMapTile(), mTileNotFoundImage, ExpirableBitmapDrawable.NOT_FOUND); if (mTileRequestCompleteHandler != null) { mTileRequestCompleteHandler.sendEmptyMessage(MapTile.MAPTILE_SUCCESS_ID); } } else { if (mTileRequestCompleteHandler != null) { mTileRequestCompleteHandler.sendEmptyMessage(MapTile.MAPTILE_FAIL_ID); } } if (Configuration.getInstance().isDebugTileProviders()) { Log.d(IMapView.LOGTAG,"MapTileProviderBase.mapTileRequestFailed(): " + pState.getMapTile()); } } //查询到过期瓦片,将瓦片放入缓存,并发送MAPTILE_SUCCESS_ID给handler @Override public void mapTileRequestExpiredTile(MapTileRequestState pState, Drawable pDrawable) { putTileIntoCache(pState.getMapTile(), pDrawable, ExpirableBitmapDrawable.getState(pDrawable)); // tell our caller we've finished and it should update its view if (mTileRequestCompleteHandler != null) { mTileRequestCompleteHandler.sendEmptyMessage(MapTile.MAPTILE_SUCCESS_ID); } if (Configuration.getInstance().isDebugTileProviders()) { Log.d(IMapView.LOGTAG,"MapTileProviderBase.mapTileRequestExpiredTile(): " + pState.getMapTile()); } } //将瓦片放入缓存 protected void putTileIntoCache(final MapTile pTile, final Drawable pDrawable, final int pState) { if (pDrawable == null) { return; } final Drawable before = mTileCache.getMapTile(pTile); if (before != null) { final int stateBefore = ExpirableBitmapDrawable.getState(before); if (stateBefore > pState) { return; } } ExpirableBitmapDrawable.setState(pDrawable, pState); mTileCache.putTile(pTile, pDrawable); } //将过期瓦片放入缓存 @Deprecated protected void putExpiredTileIntoCache(MapTileRequestState pState, Drawable pDrawable) { putTileIntoCache(pState.getMapTile(), pDrawable, ExpirableBitmapDrawable.EXPIRED); } public void setTileRequestCompleteHandler(final Handler handler) { mTileRequestCompleteHandler = handler; } public void ensureCapacity(final int pCapacity) { mTileCache.ensureCapacity(pCapacity); } //清除缓存 public void clearTileCache() { mTileCache.clear(); } //是否使用网络 @Override public boolean useDataConnection() { return mUseDataConnection; } public void setUseDataConnection(final boolean pMode) { mUseDataConnection = pMode; } public void rescaleCache(final Projection pProjection, final double pNewZoomLevel, final double pOldZoomLevel, final Rect pViewPort) { ... } //缩放时瓦片设置 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<MapTile, Bitmap> mNewTiles = new HashMap<>(); protected int mOldTileZoomLevel; protected int mTileSize; protected int mDiff; protected int mTileSize_2; protected Rect mSrcRect; protected Rect mDestRect; protected Paint mDebugPaint; private boolean isWorth; public void loop(final double pZoomLevel, final RectL pViewPortMercator, final double pOldZoomLevel, final int pTileSize) { mSrcRect = new Rect(); mDestRect = new Rect(); mDebugPaint = new Paint(); mOldTileZoomLevel = TileSystem.getInputTileZoomLevel(pOldZoomLevel); mTileSize = pTileSize; loop(pZoomLevel, pViewPortMercator); } @Override public void initialiseLoop() { mDiff = Math.abs(mTileZoomLevel - mOldTileZoomLevel); mTileSize_2 = mTileSize >> mDiff; isWorth = mDiff != 0; } @Override public void handleTile(final MapTile pTile, final int pX, final int pY) { if (!isWorth) { return; } // 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 { computeTile(pTile, pX, pY); } catch(final OutOfMemoryError e) { Log.e(IMapView.LOGTAG,"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); putScaledTileIntoCache(tile, bitmap); } } protected abstract void computeTile(MapTile pTile, int pX, int pY); /** * * @since 5.6.5 * @param pTile * @param pBitmap */ protected void putScaledTileIntoCache(final MapTile pTile, final Bitmap pBitmap) { final ReusableBitmapDrawable drawable = new ReusableBitmapDrawable(pBitmap); putTileIntoCache(pTile, drawable, ExpirableBitmapDrawable.SCALED); if (Configuration.getInstance().isDebugMode()) { Log.d(IMapView.LOGTAG, "Created scaled tile: " + pTile); mDebugPaint.setTextSize(40); final Canvas canvas = new Canvas(pBitmap); canvas.drawText("scaled", 50, 50, mDebugPaint); } } } //放大时瓦片设置 private class ZoomInTileLooper extends ScaleTileLooper { @Override public void computeTile(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(mOldTileZoomLevel, pTile.getX() >> mDiff, pTile.getY() >> mDiff); final Drawable oldDrawable = mTileCache.getMapTile(oldTile); if (oldDrawable instanceof BitmapDrawable) { final Bitmap bitmap = MapTileApproximater.approximateTileFromLowerZoom( (BitmapDrawable)oldDrawable, pTile, mDiff); if (bitmap != null) { mNewTiles.put(pTile, bitmap); } } } } //缩小时瓦片设置 private class ZoomOutTileLooper extends ScaleTileLooper { private static final int MAX_ZOOM_OUT_DIFF = 4; @Override protected void computeTile(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(mOldTileZoomLevel, 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 = MapTileApproximater.getTileBitmap(mTileSize); canvas = new Canvas(bitmap); canvas.drawColor(Color.LTGRAY); } mDestRect.set( x * mTileSize_2, y * mTileSize_2, (x + 1) * mTileSize_2, (y + 1) * mTileSize_2); canvas.drawBitmap(oldBitmap, null, mDestRect, null); mTileCache.mCachedTiles.remove(oldTile); } } } } if (bitmap != null) { mNewTiles.put(pTile, bitmap); } } } public abstract IFilesystemCache getTileWriter(); //瓦片队列尺寸 public abstract long getQueueSize();
}
4. MapTileProviderArray
上层的瓦片提供者,允许提供一组异步的瓦片提供者用于获取瓦片。当请求瓦片时,本类实体会先同步查询内部的MapTileCache并返回可利用的瓦片。如果没有,异步请求瓦片。每个异步请求瓦片的提供者会发送success/failure给本类对象,如果成功,传递给基类,失败则继续下一个tile provider的查询。如果最后还是没有查询到相应瓦片,就传failure给基类。本类提供了一次请求一个瓦片的瓦片请求链。
public class MapTileProviderArray extends MapTileProviderBase {
protected final HashMap<MapTile, MapTileRequestState> mWorking;
protected IRegisterReceiver mRegisterReceiver=null;
protected final List<MapTileModuleProviderBase> mTileProviderList;
protected MapTileProviderArray(final ITileSource pTileSource,
final IRegisterReceiver pRegisterReceiver) {
this(pTileSource, pRegisterReceiver, new MapTileModuleProviderBase[0]);
}
public MapTileProviderArray(final ITileSource pTileSource,
final IRegisterReceiver aRegisterReceiver,
final MapTileModuleProviderBase[] pTileProviderArray) {
super(pTileSource);
mWorking = new HashMap<MapTile, MapTileRequestState>();
mRegisterReceiver=aRegisterReceiver;
mTileProviderList = new ArrayList<MapTileModuleProviderBase>();
Collections.addAll(mTileProviderList, pTileProviderArray);
}
@Override
public void detach() {
synchronized (mTileProviderList) {
for (final MapTileModuleProviderBase tileProvider : mTileProviderList) {
tileProvider.detach();
}
}
mTileCache.clear();
synchronized (mWorking) {
mWorking.clear();
}
clearTileCache();
if (mRegisterReceiver!=null) {
mRegisterReceiver.destroy();
mRegisterReceiver = null;
}
super.detach();
}
protected boolean isDowngradedMode() {
return false;
}
@Override
public Drawable getMapTile(final MapTile pTile) {
final Drawable tile = mTileCache.getMapTile(pTile);
if (tile != null) {
if (ExpirableBitmapDrawable.getState(tile) == ExpirableBitmapDrawable.UP_TO_DATE) {
return tile; // best scenario ever
}
if (isDowngradedMode()) {
return tile; // best we can, considering
}
}
if (mWorking.containsKey(pTile)) { // already in progress
return tile;
}
if (Configuration.getInstance().isDebugTileProviders()) {
Log.d(IMapView.LOGTAG,"MapTileProviderArray.getMapTile() requested but not in cache, trying from async providers: "
+ pTile);
}
final MapTileRequestState state = new MapTileRequestState(pTile, mTileProviderList, this);
synchronized (mWorking) {
// Check again
if (mWorking.containsKey(pTile)) {
return tile;
}
mWorking.put(pTile, state);
}
final MapTileModuleProviderBase provider = findNextAppropriateProvider(state);
if (provider != null) {
provider.loadMapTileAsync(state);
} else {
mapTileRequestFailed(state);
}
return tile;
}
@Override
public void mapTileRequestCompleted(final MapTileRequestState aState, final Drawable aDrawable) {
synchronized (mWorking) {
mWorking.remove(aState.getMapTile());
}
super.mapTileRequestCompleted(aState, aDrawable);
}
@Override
public void mapTileRequestFailed(final MapTileRequestState aState) {
final MapTileModuleProviderBase nextProvider = findNextAppropriateProvider(aState);
if (nextProvider != null) {
nextProvider.loadMapTileAsync(aState);
} else {
synchronized (mWorking) {
mWorking.remove(aState.getMapTile());
}
super.mapTileRequestFailed(aState);
}
}
@Override
public void mapTileRequestExpiredTile(MapTileRequestState aState, Drawable aDrawable) {
// Call through to the super first so aState.getCurrentProvider() still contains the proper
// provider.
super.mapTileRequestExpiredTile(aState, aDrawable);
// Continue through the provider chain
final MapTileModuleProviderBase nextProvider = findNextAppropriateProvider(aState);
if (nextProvider != null) {
nextProvider.loadMapTileAsync(aState);
} else {
synchronized (mWorking) {
mWorking.remove(aState.getMapTile());
}
}
}
@Override
public IFilesystemCache getTileWriter() {
return null;
}
@Override
public long getQueueSize() {
if (mWorking!=null)
return mWorking.size();
return -1;
}
/**
* We want to not use a provider that doesn't exist anymore in the chain, and we want to not use
* a provider that requires a data connection when one is not available.
*/
protected MapTileModuleProviderBase findNextAppropriateProvider(final MapTileRequestState aState) {
MapTileModuleProviderBase provider = null;
boolean providerDoesntExist = false, providerCantGetDataConnection = false, providerCantServiceZoomlevel = false;
// The logic of the while statement is
// "Keep looping until you get null, or a provider that still exists
// and has a data connection if it needs one and can service the zoom level,"
do {
provider = aState.getNextProvider();
// Perform some checks to see if we can use this provider
// If any of these are true, then that disqualifies the provider for this tile request.
if (provider != null) {
providerDoesntExist = !this.getProviderExists(provider);
providerCantGetDataConnection = !useDataConnection()
&& provider.getUsesDataConnection();
int zoomLevel = aState.getMapTile().getZoomLevel();
providerCantServiceZoomlevel = zoomLevel > provider.getMaximumZoomLevel()
|| zoomLevel < provider.getMinimumZoomLevel();
}
} while ((provider != null)
&& (providerDoesntExist || providerCantGetDataConnection || providerCantServiceZoomlevel));
return provider;
}
public boolean getProviderExists(final MapTileModuleProviderBase provider) {
synchronized (mTileProviderList) {
return mTileProviderList.contains(provider);
}
}
@Override
public int getMinimumZoomLevel() {
int result = microsoft.mappoint.TileSystem.getMaximumZoomLevel();
synchronized (mTileProviderList) {
for (final MapTileModuleProviderBase tileProvider : mTileProviderList) {
if (tileProvider.getMinimumZoomLevel() < result) {
result = tileProvider.getMinimumZoomLevel();
}
}
}
return result;
}
@Override
public int getMaximumZoomLevel() {
int result = OpenStreetMapTileProviderConstants.MINIMUM_ZOOMLEVEL;
synchronized (mTileProviderList) {
for (final MapTileModuleProviderBase tileProvider : mTileProviderList) {
if (tileProvider.getMaximumZoomLevel() > result) {
result = tileProvider.getMaximumZoomLevel();
}
}
}
return result;
}
@Override
public void setTileSource(final ITileSource aTileSource) {
super.setTileSource(aTileSource);
synchronized (mTileProviderList) {
for (final MapTileModuleProviderBase tileProvider : mTileProviderList) {
tileProvider.setTileSource(aTileSource);
clearTileCache();
}
}
}
}
5. OfflineTileProvider
载入本地的瓦片文件源
public class OfflineTileProvider extends MapTileProviderArray implements IMapTileProviderCallback {
//本地缓存瓦片文件源
IArchiveFile[] archives;
/**
* Creates a {@link MapTileProviderBasic}.
* throws with the source[] is null or empty
*/
public OfflineTileProvider(final IRegisterReceiver pRegisterReceiver, File[] source
)
throws Exception {
super(FileBasedTileSource.getSource(source[0].getName()), pRegisterReceiver);
List<IArchiveFile> files = new ArrayList<IArchiveFile>();
for (int i=0; i < source.length; i++){
IArchiveFile temp=ArchiveFileFactory.getArchiveFile(source[i]);
if (temp!=null)
files.add(temp);
else{
Log.w(IMapView.LOGTAG, "Skipping " + source[i] + ", no tile provider is registered to handle the file extension");
}
}
archives = new IArchiveFile[files.size()];
archives=files.toArray(archives);
final MapTileFileArchiveProvider mapTileFileArchiveProvider = new MapTileFileArchiveProvider(pRegisterReceiver, getTileSource(), archives);
mTileProviderList.add(mapTileFileArchiveProvider);
final MapTileApproximater approximationProvider = new MapTileApproximater();
mTileProviderList.add(approximationProvider);
approximationProvider.addProvider(mapTileFileArchiveProvider);
}
public IArchiveFile[] getArchives(){
return archives;
}
public void detach() {
if (archives!=null){
for (int i=0; i < archives.length; i++){
archives[i].close();
}
}
super.detach();
}
}