OSMDroid 从资产文件夹加载自定义离线图块
Posted
技术标签:
【中文标题】OSMDroid 从资产文件夹加载自定义离线图块【英文标题】:OSMDroid Loading Custom Offline tiles From Assets Folder 【发布时间】:2012-09-23 12:09:56 【问题描述】:我想知道是否有可能做这样的事情。我知道需要修改一些现有代码才能实现这一点,但我想知道是否有人对在哪里查看以及如何执行此操作有任何指导。
我在地图上的特定区域放置了一些自定义图块,以替代 OSM 图块提供者,但需要将它们存储在 /assets/ 文件夹中。有什么想法吗?
【问题讨论】:
【参考方案1】:我使用 nexts 类来做到这一点。
import java.io.InputStream;
import org.osmdroid.ResourceProxy.string;
import org.osmdroid.tileprovider.util.StreamUtils;
import android.content.res.AssetManager;
import android.graphics.drawable.Drawable;
public class AssetsTileSource extends CustomBitmapTileSourceBase
private final AssetManager mAssetManager;
public AssetsTileSource(final AssetManager assetManager, final String aName, final string aResourceId,
final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels,
final String aImageFilenameEnding)
super(aName, aResourceId, aZoomMinLevel, aZoomMaxLevel, aTileSizePixels, aImageFilenameEnding);
mAssetManager = assetManager;
@Override
public Drawable getDrawable(final String aFilePath)
InputStream inputStream = null;
try
inputStream = mAssetManager.open(aFilePath);
if (inputStream != null)
final Drawable drawable = getDrawable(inputStream);
return drawable;
catch (final Throwable e)
// Tile does not exist in assets folder.
// Ignore silently
finally
if (inputStream != null)
StreamUtils.closeStream(inputStream);
return null;
MapTileFileAssetsProvider.java
public class MapTileFileAssetsProvider extends MapTileModuleProviderBase
protected ITileSource mTileSource;
public MapTileFileAssetsProvider(final ITileSource pTileSource)
super(OpenStreetMapTileProviderConstants.NUMBER_OF_TILE_FILESYSTEM_THREADS, OpenStreetMapTileProviderConstants.TILE_FILESYSTEM_MAXIMUM_QUEUE_SIZE);
mTileSource = pTileSource;
@Override
public boolean getUsesDataConnection()
return false;
@Override
protected String getName()
return "Assets Folder Provider";
@Override
protected String getThreadGroupName()
return "assetsfolder";
@Override
protected Runnable getTileLoader()
return new TileLoader();
@Override
public int getMinimumZoomLevel()
return mTileSource != null ? mTileSource.getMinimumZoomLevel() : MAXIMUM_ZOOMLEVEL;
@Override
public int getMaximumZoomLevel()
return mTileSource != null ? mTileSource.getMaximumZoomLevel() : MINIMUM_ZOOMLEVEL;
@Override
public void setTileSource(final ITileSource pTileSource)
mTileSource = pTileSource;
private class TileLoader extends MapTileModuleProviderBase.TileLoader
@Override
public Drawable loadTile(final MapTileRequestState pState) throws CantContinueException
if (mTileSource == null)
return null;
final MapTile pTile = pState.getMapTile();
String path = mTileSource.getTileRelativeFilenameString(pTile);
Drawable drawable;
try
drawable = mTileSource.getDrawable(path);
catch (final LowMemoryException e)
// low memory so empty the queue
throw new CantContinueException(e);
return drawable;
还有
import java.io.File;
import java.io.InputStream;
import java.util.Random;
import org.osmdroid.ResourceProxy;
import org.osmdroid.ResourceProxy.string;
import org.osmdroid.tileprovider.ExpirableBitmapDrawable;
import org.osmdroid.tileprovider.MapTile;
import org.osmdroid.tileprovider.constants.OpenStreetMapTileProviderConstants;
import org.osmdroid.tileprovider.tilesource.ITileSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
public abstract class CustomBitmapTileSourceBase implements ITileSource,
OpenStreetMapTileProviderConstants
private static final Logger logger = LoggerFactory.getLogger(CustomBitmapTileSourceBase.class);
private static int globalOrdinal = 0;
private final int mMinimumZoomLevel;
private final int mMaximumZoomLevel;
private final int mOrdinal;
protected final String mName;
protected final String mImageFilenameEnding;
protected final Random random = new Random();
private final int mTileSizePixels;
private final string mResourceId;
public CustomBitmapTileSourceBase(final String aName, final string aResourceId,
final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels,
final String aImageFilenameEnding)
mResourceId = aResourceId;
mOrdinal = globalOrdinal++;
mName = aName;
mMinimumZoomLevel = aZoomMinLevel;
mMaximumZoomLevel = aZoomMaxLevel;
mTileSizePixels = aTileSizePixels;
mImageFilenameEnding = aImageFilenameEnding;
@Override
public int ordinal()
return mOrdinal;
@Override
public String name()
return mName;
public String pathBase()
return mName;
public String imageFilenameEnding()
return mImageFilenameEnding;
@Override
public int getMinimumZoomLevel()
return mMinimumZoomLevel;
@Override
public int getMaximumZoomLevel()
return mMaximumZoomLevel;
@Override
public int getTileSizePixels()
return mTileSizePixels;
@Override
public String localizedName(final ResourceProxy proxy)
return proxy.getString(mResourceId);
@Override
public Drawable getDrawable(final String aFilePath)
try
// default implementation will load the file as a bitmap and create
// a BitmapDrawable from it
final Bitmap bitmap = BitmapFactory.decodeFile(aFilePath);
if (bitmap != null)
return new ExpirableBitmapDrawable(bitmap);
else
// if we couldn't load it then it's invalid - delete it
try
new File(aFilePath).delete();
catch (final Throwable e)
logger.error("Error deleting invalid file: " + aFilePath, e);
catch (final OutOfMemoryError e)
logger.error("OutOfMemoryError loading bitmap: " + aFilePath);
System.gc();
return null;
@Override
public String getTileRelativeFilenameString(final MapTile tile)
final StringBuilder sb = new StringBuilder();
sb.append(pathBase());
sb.append('/');
sb.append(tile.getX());
sb.append('_');
sb.append(tile.getY());
sb.append('_');
sb.append(tile.getZoomLevel());
sb.append(imageFilenameEnding());
return sb.toString();
@Override
public Drawable getDrawable(final InputStream aFileInputStream)
try
// default implementation will load the file as a bitmap and create
// a BitmapDrawable from it
final Bitmap bitmap = BitmapFactory.decodeStream(aFileInputStream);
if (bitmap != null)
return new ExpirableBitmapDrawable(bitmap);
System.gc();
catch (final OutOfMemoryError e)
logger.error("OutOfMemoryError loading bitmap");
System.gc();
//throw new LowMemoryException(e);
return null;
public final class LowMemoryException extends Exception
private static final long serialVersionUID = 146526524087765134L;
public LowMemoryException(final String pDetailMessage)
super(pDetailMessage);
public LowMemoryException(final Throwable pThrowable)
super(pThrowable);
修改 getTileRelativeFilenameString() 方法以获取您的图块(我使用下一种格式:x_y_zoom.png)
例子:
mapView = new MapView(getApplicationContext(), 256);
mapView.setClickable(true);
mapView.setTag("Mapa");
mapView.setTileSource(TileSourceFactory.MAPNIK);
mapView.setMultiTouchControls(true);
mapView.setUseDataConnection(true);
MapTileModuleProviderBase moduleProvider =
new MapTileFileAssetsProvider(ASSETS_TILE_SOURCE);
SimpleRegisterReceiver simpleReceiver =
new SimpleRegisterReceiver(getApplicationContext());
MapTileProviderArray tileProviderArray =
new MapTileProviderArray(ASSETS_TILE_SOURCE, simpleReceiver,
new MapTileModuleProviderBase[] moduleProvider );
TilesOverlay tilesOverlay =
new TilesOverlay(tileProviderArray, getApplicationContext());
mapView.getOverlays().add(tilesOverlay);
【讨论】:
如何在放大/缩小的同时应用坐标导航? 这些类很好用,但例子还不够完整。对于 ASSETS_TILE_SOURCE,它不是常量,而是来自 AssetsTileSource 类的新对象。创建 tileProviderArray 后,我在使用可以将其作为参数的构造函数创建 mapView 时使用 in ,例如MapView mapView = new MapView(this, 256, resourceProxy, tileProviderArray)。让 MapView 与图块源相关联对我来说更直观,而不是与叠加层相关联......也许只是我。 @jzafrila 感谢样品。Bitmap.decodeStream()
不会在主 UI 线程上导致 GC_FOR_ALLOC
吗?
@zIronManBox 对此不确定,我没有遇到任何问题,并且按预期工作。也许它可以改善这一点。【参考方案2】:
而不是直接从资产中读取,我将压缩的地图块(遵循 osmdroid 地图块目录结构格式)复制/部署到 osmdroid 地图块目录中,然后声明 3 个块提供者、存档、缓存和在线提供者。
public class MapTileProviderAssets extends MapTileProviderArray
implements IMapTileProviderCallback
private static final String LOG_TAG = "MapTileProviderAssets";
private static final String ASSETS_MAP_DIRECTORY = "map";
private static final String SDCARD_PATH = Environment.getExternalStorageDirectory().getPath();
private static final String OSMDROID_MAP_FILE_SOURCE_DIRECTORY = "osmdroid";
private static final String OSMDROID_MAP_FILE_SOURCE_DIRECTORY_PATH =
SDCARD_PATH + "/" + OSMDROID_MAP_FILE_SOURCE_DIRECTORY;
public MapTileProviderAssets(final Context pContext)
this(pContext, TileSourceFactory.DEFAULT_TILE_SOURCE);
public MapTileProviderAssets(final Context pContext, final ITileSource pTileSource)
this(pContext, new SimpleRegisterReceiver(pContext),
new NetworkAvailabliltyCheck(pContext), pTileSource);
public MapTileProviderAssets(final Context pContext, final IRegisterReceiver pRegisterReceiver,
final INetworkAvailablityCheck aNetworkAvailablityCheck,
final ITileSource pTileSource)
super(pTileSource, pRegisterReceiver);
final TileWriter tileWriter = new TileWriter();
// copy assets delivered in apk into osmdroid map source dir
// load zip archive first, then cache, then online
final List<String> zipArchivesRelativePathInAssets =
listArchives(pContext.getAssets(), ASSETS_MAP_DIRECTORY);
for (final String zipFileRelativePathInAssets : zipArchivesRelativePathInAssets)
final String copiedFilePath = copyAssetFile(
pContext.getAssets(), zipFileRelativePathInAssets,
OSMDROID_MAP_FILE_SOURCE_DIRECTORY);
Log.d(LOG_TAG, String.format(
"Archive zip file copied into map source directory %s", copiedFilePath));
// list zip files in map archive directory
final Set<String> setZipFileArchivesPath = new HashSet<String>();
FileTools.listFiles(setZipFileArchivesPath, new File(
OSMDROID_MAP_FILE_SOURCE_DIRECTORY_PATH), ".zip", true);
final Set<IArchiveFile> setZipFileArchives = new HashSet<IArchiveFile>();
for (final String zipFileArchivesPath : setZipFileArchivesPath)
final File zipfile = new File(zipFileArchivesPath);
final IArchiveFile archiveFile = ArchiveFileFactory.getArchiveFile(zipfile);
if (archiveFile != null)
setZipFileArchives.add(archiveFile);
setZipFileArchives.add(archiveFile);
Log.d(LOG_TAG, String.format(
"Archive zip file %s added to map source ", zipFileArchivesPath));
final MapTileFileArchiveProvider archiveProvider;
Log.d(LOG_TAG, String.format(
"%s archive zip files will be used as source", setZipFileArchives.size()));
if (setZipFileArchives.size() > 0)
final IArchiveFile[] pArchives =
setZipFileArchives.toArray(new IArchiveFile[setZipFileArchives.size()]);
archiveProvider = new MapTileFileArchiveProvider(
pRegisterReceiver, pTileSource, pArchives);
else
archiveProvider = new MapTileFileArchiveProvider(
pRegisterReceiver, pTileSource);
mTileProviderList.add(archiveProvider);
// cache
final MapTileFilesystemProvider fileSystemProvider =
new MapTileFilesystemProvider(pRegisterReceiver, pTileSource);
mTileProviderList.add(fileSystemProvider);
// online tiles
final MapTileDownloader downloaderProvider =
new MapTileDownloader(pTileSource, tileWriter, aNetworkAvailablityCheck);
mTileProviderList.add(downloaderProvider);
public static List<String> listArchives(final AssetManager assetManager,
final String subDirectory)
final List<String> listArchives = new ArrayList<String>();
try
final String[] lstFiles = assetManager.list(subDirectory);
if (lstFiles != null && lstFiles.length > 0)
for (final String file : lstFiles)
if (isZip(file))
listArchives.add(subDirectory + "/" + file);
// filter files (xxxxx.xxx format) and parse only directories,
// with out this all files are parsed and
// the process is VERY slow
// WARNNING: we could have directories with dot for versioning
else if (isDirectory(file)) // (file.lastIndexOf(".") != (file.length() - 4))
listArchives(assetManager, subDirectory + "/" + file);
catch (final IOException e)
Log.w(LOG_TAG, String.format("List error: can't list %s, exception %s",
subDirectory, Log.getStackTraceString(e)));
catch (final Exception e)
Log.w(LOG_TAG, String.format("List error: can't list %s, exception %s",
subDirectory, Log.getStackTraceString(e)));
return listArchives;
private static boolean isZip(final String file)
return file.endsWith(".zip");
private static boolean isDirectory(final String file)
return file.lastIndexOf(".") != (file.length() - 4);
private static String copyAssetFile(final AssetManager assetManager,
final String assetRelativePath,
final String destinationDirectoryOnSdcard)
InputStream in = null;
OutputStream out = null;
final String newfilePath = SDCARD_PATH + "/" +
destinationDirectoryOnSdcard + "/" + assetRelativePath;
final File newFile = new File(newfilePath);
// copy file only if it doesn't exist yet
if (!newFile.exists())
Log.d(LOG_TAG, String.format(
"Copy %s map archive in assets into %s", assetRelativePath, newfilePath));
try
final File directory = newFile.getParentFile();
if (!directory.exists())
if (directory.mkdirs())
// Log.d(LOG_TAG, "Directory created: " + directory.getAbsolutePath());
in = assetManager.open(assetRelativePath);
out = new FileOutputStream(newfilePath);
copyFile(in, out);
in.close();
in = null;
out.flush();
out.close();
out = null;
catch (final Exception e)
Log.e(LOG_TAG, "Exception during copyAssetFile: " + Log.getStackTraceString(e));
return newfilePath;
private static void copyFile(final InputStream in, final OutputStream out) throws IOException
final byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1)
out.write(buffer, 0, read);
【讨论】:
您的代码不完整。有一种方法不可访问:它是 FileTools.listFiles。我已经评论它并添加了这一行来替换它: setZipFileArchivesPath.add("name_of_your_map_file.zip"); ....我仍然不确定是否必须包含字符串 .zip 作为文件名。我会自己检查。 是的,FileTools.listFiles 的代码不存在,因为它很容易实现。该方法只是简单地列出输入的地图存档目录的所有 zip 文件。以上是关于OSMDroid 从资产文件夹加载自定义离线图块的主要内容,如果未能解决你的问题,请参考以下文章
osmdroid:加载自定义tilesource时显示的乱码