All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mapbox.mapboxsdk.tileprovider.MapTileLayerArray Maven / Gradle / Ivy

There is a newer version: 9.2.1
Show newest version
package com.mapbox.mapboxsdk.tileprovider;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import com.mapbox.mapboxsdk.geometry.BoundingBox;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.tileprovider.modules.MapTileModuleLayerBase;
import com.mapbox.mapboxsdk.tileprovider.modules.NetworkAvailabilityCheck;
import com.mapbox.mapboxsdk.tileprovider.tilesource.ITileLayer;
import com.mapbox.mapboxsdk.util.BitmapUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import uk.co.senab.bitmapcache.CacheableBitmapDrawable;

/**
 * This top-level tile provider allows a consumer to provide an array of modular asynchronous tile
 * providers to be used to obtain map tiles. When a tile is requested, the
 * {@link MapTileLayerArray} first checks the {@link MapTileCache} (synchronously) and returns
 * the tile if available. If not, then the {@link MapTileLayerArray} returns null and sends the
 * tile request through the asynchronous tile request chain. Each asynchronous tile provider
 * returns
 * success/failure to the {@link MapTileLayerArray}. If successful, the
 * {@link MapTileLayerArray} passes the result to the base class. If failed, then the next
 * asynchronous tile provider is called in the chain. If there are no more asynchronous tile
 * providers in the chain, then the failure result is passed to the base class. The
 * {@link MapTileLayerArray} provides a mechanism so that only one unique tile-request can be in
 * the map tile request chain at a time.
 *
 * @author Marc Kurtz
 */
public class MapTileLayerArray extends MapTileLayerBase {

    protected final HashMap mWorking;

    protected final List mTileProviderList;

    protected final List mUnaccessibleTiles;

    protected final NetworkAvailabilityCheck mNetworkAvailabilityCheck;

    /**
     * Creates an {@link MapTileLayerArray} with no tile providers.
     *
     * @param pRegisterReceiver a {@link IRegisterReceiver}
     */
    protected MapTileLayerArray(final Context context, final ITileLayer pTileSource,
            final IRegisterReceiver pRegisterReceiver) {
        this(context, pTileSource, pRegisterReceiver, null);
    }

    /**
     * Creates an {@link MapTileLayerArray} with the specified tile providers.
     *
     * @param aRegisterReceiver a {@link IRegisterReceiver}
     * @param pTileProviderArray an array of {@link com.mapbox.mapboxsdk.tileprovider.modules.MapTileModuleLayerBase}
     */
    public MapTileLayerArray(final Context context, final ITileLayer pTileSource,
            final IRegisterReceiver aRegisterReceiver,
            final MapTileModuleLayerBase[] pTileProviderArray) {
        super(context, pTileSource);

        mWorking = new HashMap();
        mUnaccessibleTiles = new ArrayList();

        mNetworkAvailabilityCheck = new NetworkAvailabilityCheck(context);

        mTileProviderList = new ArrayList();
        if (pTileProviderArray != null) {
            mCacheKey = pTileProviderArray[0].getCacheKey();
            Collections.addAll(mTileProviderList, pTileProviderArray);
        }
    }

    @Override
    public void detach() {
        if (getTileSource() != null) {
            getTileSource().detach();
        }

        synchronized (mTileProviderList) {
            for (final MapTileModuleLayerBase tileProvider : mTileProviderList) {
                tileProvider.detach();
            }
        }

        synchronized (mWorking) {
            mWorking.clear();
        }
    }

    private boolean networkAvailable() {
        return mNetworkAvailabilityCheck == null || mNetworkAvailabilityCheck.getNetworkAvailable();
    }

    /**
     * Checks whether this tile is unavailable and the system is offline.
     *
     * @param pTile the tile in question
     * @return whether the tile is unavailable
     */
    private boolean tileUnavailable(final MapTile pTile) {
        if (mUnaccessibleTiles.size() > 0) {
            if (networkAvailable()) {
                mUnaccessibleTiles.clear();
            } else if (mUnaccessibleTiles.contains(pTile)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Drawable getMapTile(final MapTile pTile, final boolean allowRemote) {
 //       Log.d(TAG, "getMapTile() with pTile (CacheKey) = '" + pTile.getCacheKey() + "'; allowRemote = '" + allowRemote + "'");
        if (tileUnavailable(pTile)) {
            Log.d(TAG, "MapTileLayerArray.getMapTile() tileUnavailable: " + pTile);
            return null;
        }

        CacheableBitmapDrawable tileDrawable = mTileCache.getMapTileFromMemory(pTile);

        if (tileDrawable != null && tileDrawable.isBitmapValid() && !BitmapUtils.isCacheDrawableExpired(tileDrawable)) {
            tileDrawable.setBeingUsed(true);
//            Log.d(TAG, "Found tile(" + pTile.getCacheKey() + ") in memory, so returning for drawing.");
            return tileDrawable;
        } else if (allowRemote) {
//            Log.d(TAG, "Tile not found in memory so will load from remote.");
            boolean alreadyInProgress = false;
            synchronized (mWorking) {
                alreadyInProgress = mWorking.containsKey(pTile);
            }

            if (!alreadyInProgress) {
//                Log.d(TAG, "MapTileLayerArray.getMapTile() requested but not in cache, trying from async providers: " + pTile);

                final MapTileRequestState state;

                synchronized (mTileProviderList) {
                    final MapTileModuleLayerBase[] providerArray = new MapTileModuleLayerBase[mTileProviderList.size()];
                    state = new MapTileRequestState(pTile, mTileProviderList.toArray(providerArray), this);
                }

                synchronized (mWorking) {
                    // Check again
                    alreadyInProgress = mWorking.containsKey(pTile);
                    if (alreadyInProgress) {
                        return null;
                    }
                    mWorking.put(pTile, state);
                }

                final MapTileModuleLayerBase provider = findNextAppropriateProvider(state);
                if (provider != null) {
                    provider.loadMapTileAsync(state);
                } else {
                    mapTileRequestFailed(state);
                }
            }
            return tileDrawable;
        }
/*
        else
        {
//            Log.w(TAG, "Tile not found in memory, and not allowed to load from remote source.");
        }
*/
        return null;
    }

    @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 MapTileModuleLayerBase nextProvider = findNextAppropriateProvider(aState);
        if (nextProvider != null) {
            nextProvider.loadMapTileAsync(aState);
        } else {
            synchronized (mWorking) {
                mWorking.remove(aState.getMapTile());
            }
            if (!networkAvailable()) {
                mUnaccessibleTiles.add(aState.getMapTile());
            }
            super.mapTileRequestFailed(aState);
        }
    }

    @Override
    public void mapTileRequestExpiredTile(MapTileRequestState aState,
            CacheableBitmapDrawable 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 MapTileModuleLayerBase nextProvider = findNextAppropriateProvider(aState);
        if (nextProvider != null) {
            nextProvider.loadMapTileAsync(aState);
        } else {
            synchronized (mWorking) {
                mWorking.remove(aState.getMapTile());
            }
        }
    }

    /**
     * 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 MapTileModuleLayerBase findNextAppropriateProvider(final MapTileRequestState aState) {
        MapTileModuleLayerBase 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().getZ();
                providerCantServiceZoomlevel = zoomLevel > provider.getMaximumZoomLevel()
                        || zoomLevel < provider.getMinimumZoomLevel();
            }
        } while ((provider != null) && (providerDoesntExist
                || providerCantGetDataConnection
                || providerCantServiceZoomlevel));
        return provider;
    }

    public boolean getProviderExists(final MapTileModuleLayerBase provider) {
        synchronized (mTileProviderList) {
            return mTileProviderList.contains(provider);
        }
    }

    @Override
    public float getMinimumZoomLevel() {
        float result = MINIMUM_ZOOMLEVEL;
        synchronized (mTileProviderList) {
            for (final MapTileModuleLayerBase tileProvider : mTileProviderList) {
                result = Math.max(result, tileProvider.getMinimumZoomLevel());
            }
        }
        return result;
    }

    @Override
    public float getMaximumZoomLevel() {
        float result = MAXIMUM_ZOOMLEVEL;
        synchronized (mTileProviderList) {
            for (final MapTileModuleLayerBase tileProvider : mTileProviderList) {
                result = Math.min(result, tileProvider.getMaximumZoomLevel());
            }
        }
        return result;
    }

    @Override
    public void setTileSource(final ITileLayer aTileSource) {
        super.setTileSource(aTileSource);
        mUnaccessibleTiles.clear();
        synchronized (mTileProviderList) {
            mTileProviderList.clear();
        }
    }

    @Override
    public boolean hasNoSource() {
        synchronized (mTileProviderList) {
            return mTileProviderList.size() == 0;
        }
    }

    @Override
    public BoundingBox getBoundingBox() {
        BoundingBox result = null;
        synchronized (mTileProviderList) {
            for (final MapTileModuleLayerBase tileProvider : mTileProviderList) {
                BoundingBox providerBox = tileProvider.getBoundingBox();
                if (result == null) {
                    result = providerBox;
                } else {
                    result = result.union(providerBox);
                }
            }
        }
        return result;
    }

    @Override
    public LatLng getCenterCoordinate() {
        float latitude = 0;
        float longitude = 0;
        int nb = 0;
        synchronized (mTileProviderList) {
            for (final MapTileModuleLayerBase tileProvider : mTileProviderList) {
                LatLng providerCenter = tileProvider.getCenterCoordinate();
                if (providerCenter != null) {
                    latitude += providerCenter.getLatitude();
                    longitude += providerCenter.getLongitude();
                    nb++;
                }
            }
        }
        if (nb > 0) {
            latitude /= nb;
            longitude /= nb;
            return new LatLng(latitude, longitude);
        }
        return null;
    }

    @Override
    public float getCenterZoom() {
        float centerZoom = 0;
        int nb = 0;
        synchronized (mTileProviderList) {
            for (final MapTileModuleLayerBase tileProvider : mTileProviderList) {
                centerZoom += tileProvider.getCenterZoom();
                nb++;
            }
        }
        if (centerZoom > 0) {
            return centerZoom / nb;
        }

        return (getMaximumZoomLevel() + getMinimumZoomLevel()) / 2;
    }

    @Override
    public int getTileSizePixels() {
        int result = 0;
        synchronized (mTileProviderList) {
            for (final MapTileModuleLayerBase tileProvider : mTileProviderList) {
                result += tileProvider.getTileSizePixels();
                break;
            }
        }
        return result;
    }

    private static final String TAG = "MapTileLayerArray";
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy