com.mapbox.mapboxsdk.tileprovider.MapTileLayerArray Maven / Gradle / Ivy
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