osmdroid API解读(九)

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();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值